「3」expo-shopping:状态管理、数据持久化、样式配置、部署在vercel上
- 状态管理我们选择的是
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,退出登录又变为空了,可以证明状态管理已经成功接入
持久化存储
安装依赖:
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的变化
样式
安装依赖:
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>
);
}
效果如下:
但是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端的样式也出来了
在vercel上面部署
顺便我们也可以在vercel上部署了,
我们也可以直接在vercel上面配置更简单:
项目地址如下:expo-shopping.vercel.app/
转载自:https://juejin.cn/post/7358647510518235163