文章目录
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、TS 应用:JS神助攻 - 强类型
- 四、JWT、用户认证与异步请求
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
- 六、用户体验优化 - 加载中和错误状态处理
- 七、Hook,路由,与 URL 状态管理
- 八、用户选择器与项目编辑功能
- 九、深入React 状态管理与Redux机制
- 1&2
- 3&4
- 5~8
- 9&10
- 11.用redux-thunk管理登录状态
 
 
学习内容来源:React + React Hook + TS 最佳实践-慕课网
相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:
| 项 | 版本 | 
|---|---|
| react & react-dom | ^18.2.0 | 
| react-router & react-router-dom | ^6.11.2 | 
| antd | ^4.24.8 | 
| @commitlint/cli & @commitlint/config-conventional | ^17.4.4 | 
| eslint-config-prettier | ^8.6.0 | 
| husky | ^8.0.3 | 
| lint-staged | ^13.1.2 | 
| prettier | 2.8.4 | 
| json-server | 0.17.2 | 
| craco-less | ^2.0.0 | 
| @craco/craco | ^7.1.0 | 
| qs | ^6.11.0 | 
| dayjs | ^1.11.7 | 
| react-helmet | ^6.1.0 | 
| @types/react-helmet | ^6.1.6 | 
| react-query | ^6.1.0 | 
| @welldone-software/why-did-you-render | ^7.0.1 | 
| @emotion/react & @emotion/styled | ^11.10.6 | 
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
一、项目起航:项目初始化与配置
- 一、项目起航:项目初始化与配置
二、React 与 Hook 应用:实现项目列表
- 二、React 与 Hook 应用:实现项目列表
三、TS 应用:JS神助攻 - 强类型
- 三、 TS 应用:JS神助攻 - 强类型
四、JWT、用户认证与异步请求
- 四、 JWT、用户认证与异步请求(上)
- 四、 JWT、用户认证与异步请求(下)
五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)
六、用户体验优化 - 加载中和错误状态处理
- 六、用户体验优化 - 加载中和错误状态处理(上)
- 六、用户体验优化 - 加载中和错误状态处理(中)
- 六、用户体验优化 - 加载中和错误状态处理(下)
七、Hook,路由,与 URL 状态管理
- 七、Hook,路由,与 URL 状态管理(上)
- 七、Hook,路由,与 URL 状态管理(中)
- 七、Hook,路由,与 URL 状态管理(下)
八、用户选择器与项目编辑功能
- 八、用户选择器与项目编辑功能(上)
- 八、用户选择器与项目编辑功能(下)
九、深入React 状态管理与Redux机制
1&2
- 九、深入React 状态管理与Redux机制(一)
3&4
- 九、深入React 状态管理与Redux机制(二)
5~8
- 九、深入React 状态管理与Redux机制(三)
9&10
- 九、深入React 状态管理与Redux机制(四)
11.用redux-thunk管理登录状态
既然模态框使用 redux 来管理了,而 redux 与 context 是竞争关系,那可以尝试将之前管理登录状态的 context 改为 redux
新建 src\store\auth.slice.ts:
import { User } from "screens/ProjectList/components/SearchPanel";
import { createSlice } from "@reduxjs/toolkit";
import * as auth from 'auth-provider'
import { AuthForm, initUser as _initUser } from "context/auth-context";
import { AppDispatch, RootState } from "store";interface State {user: User | null
}const initialState: State = {user: null
}export const authSlice = createSlice({name: 'auth',initialState,reducers: {setUser(state, action) {state.user = action.payload}}
})const { setUser } = authSlice.actionsexport const selectUser = (state: RootState) => state.auth.userexport const login = (form: AuthForm) => (dispatch: AppDispatch) => auth.login(form).then(user => dispatch(setUser(user)))
export const register = (form: AuthForm) => (dispatch: AppDispatch) => auth.register(form).then(user => dispatch(setUser(user)))
export const logout = () => (dispatch: AppDispatch) => auth.logout().then(() => dispatch(setUser(null)))
export const initUser = () => (dispatch: AppDispatch) => _initUser().then(user => dispatch(setUser(user)))
需要提前将
src\context\auth-context.tsx中的interface AuthForm和initUser(视频中对应bootstrapUser) 导出
最后在 src\store\index.ts 中统一注册:
...
import { authSlice } from "./auth.slice";// 集中状态注册
export const rootReducer = {projectList: projectListSlice.reducer,auth: authSlice.reducer
};
...
接下来使用 redux 中的 auth 相关功能替换原有 context 提供的 auth 的功能
重构需要找代码上游中集中的一个点:useAuth
修改 src\context\auth-context.tsx :
+ import { ReactNode, useCallback } from "react";
...
+ import * as authStore from 'store/auth.slice'
+ import { useDispatch, useSelector } from "react-redux";
+ import { AppDispatch } from "store";- interface AuthForm {
+ export interface AuthForm {username: string;password: string;
}- const initUser = async () => {
+ export const initUser = async () => {let user = null;const token = auth.getToken();if (token) {// 由于要自定义 token ,这里使用 http 而非 useHttpconst data = await http("me", { token });user = data.user;}return user
};- const AuthContext = React.createContext<
-   | {
-       user: User | null;
-       login: (form: AuthForm) => Promise<void>;
-       register: (form: AuthForm) => Promise<void>;
-       logout: () => Promise<void>;
-     }
-   | undefined
- >(undefined);- AuthContext.displayName = "AuthContext";export const AuthProvider = ({ children }: { children: ReactNode }) => {// 这里要考虑到初始值的类型与后续值类型,取并组成一个泛型const {
-     data: user,error,isLoading,isReady,isError,run,
-     setData: setUser,} = useAsync<User | null>();
+   // const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
+   // const dispatch: AppDispatch = useDispatch()
+ 
+   // 这种写法虽然消除了代码中的报错,但是控制台会有报错
+   // useMount(async () => run(dispatch(await initUser())));
+   // useMount(() => run(dispatch(initUser())));
+   // 还原后登录保持功能已经是不好使的了useMount(() => run(initUser()));-   const login = (form: AuthForm) => auth.login(form).then(setUser);
-   const register = (form: AuthForm) => auth.register(form).then(setUser);
-   const logout = () => auth.logout().then(() => setUser(null));if (isReady || isLoading) {return <FullPageLoading />;}if (isError) {return <FullPageErrorFallback error={error} />;}-   return (
-     <AuthContext.Provider
-       children={children}
-       value={{ user, login, register, logout }}
-     />
-   );
+   return <div>
+     { children }
+   </div>
};export const useAuth = () => {
-   const context = React.useContext(AuthContext);
-   if (!context) {
-     throw new Error("useAuth 必须在 AuthProvider 中使用");
-   }
-   return context;
+   // 这种写法有报错
+   // const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
+   const dispatch: AppDispatch = useDispatch()
+   const user = useSelector(authStore.selectUser)
+   const login = useCallback((form: AuthForm) => dispatch(authStore.login(form)), [dispatch])
+   const register = useCallback((form: AuthForm) => dispatch(authStore.register(form)), [dispatch])
+   const logout = useCallback(() => dispatch(authStore.logout()), [dispatch])
+   // const login = useCallback((form: AuthForm) => authStore.login(form), [])
+   // const register = useCallback((form: AuthForm) => authStore.register(form), [])
+   // const logout = useCallback(() => authStore.logout(), [])
+   // login({ username: '123', password: '123' }).then()
+   return { user, login, register, logout };
};
这种写法会报错 const dispatch: (...args: unknown[]) => Promise<User> = useDispatch():
不能将类型“Dispatch<AnyAction>”分配给类型“(...args: unknown[]) => Promise<User>”。参数“action”和“args” 的类型不兼容。不能将类型“unknown”分配给类型“AnyAction”
替换为 const dispatch: AppDispatch = useDispatch() 后正常
useMount(async () => run(dispatch(await initUser()))) 中不加 async..await 的话会有如下报错提示:
没有与此调用匹配的重载。第 1 个重载(共 3 个),“(thunkAction: ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>): Promise<User | null>”,出现以下错误。类型“Promise<any>”的参数不能赋给类型“ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>”的参数。类型“Promise<any>”提供的内容与签名“(dispatch: ThunkDispatch<{ projectList: State; auth: State; }, undefined, AnyAction>, getState: () => { projectList: State; auth: State; }, extraArgument: undefined): Promise<...>”不匹配。第 2 个重载(共 3 个),“(action: AnyAction): AnyAction”,出现以下错误。类型“Promise<any>”的参数不能赋给类型“AnyAction”的参数。类型 "Promise<any>" 中缺少属性 "type",但类型 "AnyAction" 中需要该属性。第 3 个重载(共 3 个),“(action: AnyAction | ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>): AnyAction | Promise<...>”,出现以下错误。类型“Promise<any>”的参数不能赋给类型“AnyAction | ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>”的参数。不能将类型“Promise<any>”分配给类型“AnyAction”。ts(2769)
auth-context.tsx(41, 31): 是否忘记使用 "await"?
index.d.ts(19, 3): 在此处声明了 "type"。
auth-context.tsx(41, 31): 是否忘记使用 "await"?
auth-context.tsx(41, 31): 是否忘记使用 "await"?
最终运行结果:登录,注册,登出功能都正常,只有登录保持不正常
部分引用笔记还在草稿阶段,敬请期待。。。