likes
comments
collection
share

「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上

作者站长头像
站长
· 阅读数 4
  • 状态管理我们选择的是redux
  • 持久化是@react-native-async-storage
  • 样式我们用tailwind

状态管理

安装依赖:

pnpm i @reduxjs/toolkit react-redux

新建store/slices/user.slice.ts

import { createSlice } from "@reduxjs/toolkit";

const token = "";
const initialState = { token };

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    userLogout: (state) => {
      state.token = "";
    },

    userLogin: (state, action) => {
      state.token = action.payload;
    },
  },
});

export const { userLogout, userLogin } = userSlice.actions;

export default userSlice.reducer;

新建store/index.ts,因为我们用的是ts所以需要提前导出一些type以供后续页面使用

import { configureStore } from "@reduxjs/toolkit";

import userReducer from "./slices/user.slice";

export * from "./slices/user.slice";

export const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

我们来封装一下hooks,新建hooks/useRedux.ts

import { useDispatch, useSelector } from "react-redux";

export const useAppDispatch = () => useDispatch();
export const useAppSelector = useSelector;

hooks/index.ts中导出

export * from "./useRedux";

app/_layout.tsx中引入状态管理

import { Stack } from "expo-router";
import { useState } from "react";
import { Provider } from "react-redux";

import { AuthProvider } from "../context/auth";
import { store } from "../store";

export default function Layout() {
  const [loadedUser, setLoadedUser] = useState(null);

  return (
    <Provider store={store}>
      <AuthProvider userCredentials={loadedUser}>
        <Stack>
          <Stack.Screen
            name="(main)/(tabs)"
            options={{
              headerShown: false,
            }}></Stack.Screen>
        </Stack>
      </AuthProvider>
    </Provider>
  );
}

我们在app/(main)/(tabs)/profile.tsx中使用一下试试:

import { View, Text, Pressable } from "react-native";

import { userLogin, userLogout } from "../../../store";
import { useAppDispatch, useAppSelector } from "../../../hooks";

export default function Profile() {
  const dispatch = useAppDispatch();
  const { token } = useAppSelector((state) => state.user);

  const onLogIn = async () => {
    dispatch(userLogin("123456"));
  };

  const onLogout = async () => {
    dispatch(userLogout());
  };

  return (
    <View>
      <Text>Profile screen</Text>
      <Pressable onPress={onLogIn}>
        <Text>Log in</Text>
      </Pressable>
      <Pressable onPress={onLogout}>
        <Text>Log out</Text>
      </Pressable>
    </View>
  );
}

页面如下,可以看到点击登录过后user.token从''变为了123456,退出登录又变为空了,可以证明状态管理已经成功接入

「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上

持久化存储

安装依赖:

pnpm i @react-native-async-storage/async-storage

我们改变一下store/slices/user.slice.ts

import AsyncStorage from "@react-native-async-storage/async-storage";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

const token = "";
const initialState = { token, status: "idle" };

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    setToken: (state, action) => {
      state.token = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(setTokenAsync.pending, (state, action) => {
        state.status = "loading";
      })
      .addCase(setTokenAsync.rejected, (state, action) => {
        state.status = "failed";
      })
      .addCase(setTokenAsync.fulfilled, (state, action) => {
        state.status = "succeeded";
      });
  },
});

export const setTokenAsync = createAsyncThunk(
  "user/setTokenAsync",
  async (token: string, { dispatch, getState }) => {
    try {
      await AsyncStorage.setItem("token", token);
      dispatch(setToken(token));
      return true;
    } catch (err) {
      console.error(err);
      return false;
    }
  },
);

export const { setToken } = userSlice.actions;

export default userSlice.reducer;

这里用到了异步reducer,会根据请求状态,返回不同的status

然后我们在根布局中,每次进入页面获取本地token没有token的话进行登录操作

修改app/_layout.tsx

import AsyncStorage from "@react-native-async-storage/async-storage";
import { Stack } from "expo-router";
import { useEffect, useState } from "react";
import { Text, View, StyleSheet } from "react-native";
import { Provider } from "react-redux";

import { setToken, store } from "../store";

export default function Layout() {
  const [isReady, setIsReady] = useState(false);
  const [loadedUser, setLoadedUser] = useState<any>(null);

  const getUserFromStorage = async () => {
    const token = await AsyncStorage.getItem("token");

    if (token) {
      setLoadedUser({
        email: "admin@qq.com",
      });
      store.dispatch(setToken(token));
    }
    setIsReady(true);
  };

  useEffect(() => {
    getUserFromStorage();
  }, []);

  if (!isReady) {
    return (
      <View style={styles.loading}>
        <Text>Loading...</Text>
      </View>
    );
  }
  return (
    <Provider store={store}>
      <Stack>
        <Stack.Screen
          name="(main)/(tabs)"
          options={{
            headerShown: false,
          }}></Stack.Screen>
      </Stack>
    </Provider>
  );
}

const styles = StyleSheet.create({
  loading: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
});

修改app/(auth)/login.tsx

import { useRouter } from "expo-router";
import { Pressable, Text, View } from "react-native";

import { useAppDispatch, useAppSelector } from "../../hooks";
import { RootState, setTokenAsync } from "../../store";

export default function Login() {
  const router = useRouter();
  const dispatch = useAppDispatch();

  const { status } = useAppSelector((state: RootState) => state.user);

  const onLogin = async () => {
    try {
      await dispatch(setTokenAsync("123456") as any).unwrap();
      router.back();
    } catch (error) {}
  };

  return (
    <View>
      <Pressable onPress={onLogin}>
        <Text>Login: {status}</Text>
      </Pressable>
    </View>
  );
}

在这里我们点击登录就能看到status的变化

「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上

样式

安装依赖:

pnpm i nativewind
pnpm i tailwindcss@3.3.2 -D

运行以下命令,自动在根目录初始化talwindw.config.js

npx tailwindcss init

添加需要样式的content地址,修改tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

babel.config.js中引入plugin

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: ["nativewind/babel"],
  };
};

现在修改app/(main)/(tabs)/profile.tsx

import { View, Text, Pressable } from "react-native";

import { useAppDispatch, useAppSelector } from "../../../hooks";
import { RootState, setTokenAsync } from "../../../store";

export default function Profile() {
  const dispatch = useAppDispatch();
  const { token } = useAppSelector((state: RootState) => state.user);

  const onLogIn = async () => {
    dispatch(setTokenAsync("123456") as any).unwrap();
  };

  const onLogout = async () => {
    dispatch(setTokenAsync("") as any).unwrap();
  };

  return (
    <View className="flex-1 items-center justify-center bg-gray-500">
      <Text>Profile screen</Text>
      <Pressable onPress={onLogIn}>
        <Text>Log in</Text>
      </Pressable>
      <Pressable onPress={onLogout}>
        <Text>Log out</Text>
      </Pressable>
    </View>
  );
}

效果如下:

「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上

但是web端还不行,我们还需要改进一下

安装依赖:

pnpm i postcss autoprefixer -D

在根目录下面创建postcss.config.js

module.exports = {  
plugins: {  
tailwindcss: {},  
autoprefixer: {},  
},  
};

为了获取className的建议,需要在根目录下面创建nativewind.d.ts

// eslint-disable-next-line @typescript-eslint/triple-slash-reference  
/// <reference types="nativewind/types" />

在根目录下面新建global.css

@tailwind base;  
@tailwind components;  
@tailwind utilities;

并将它引入app/_layout.tsx

import "../global.css";

暂时不清楚components下面是否还会需要引入,需要等到后期使用的时候看看了

目前web端的样式也出来了

「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上

代码地址:github.com/liyunfu1998…

在vercel上面部署

顺便我们也可以在vercel上部署了,

参考地址:docs.expo.dev/distributio…

我们也可以直接在vercel上面配置更简单:

「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上

项目地址如下:expo-shopping.vercel.app/