学习内容来源: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 应用:实现项目列表
- 三、 TS 应用:JS神助攻 - 强类型
- 四、 JWT、用户认证与异步请求(上)
- 四、 JWT、用户认证与异步请求(下)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)
- 六、用户体验优化 - 加载中和错误状态处理(上)
- 六、用户体验优化 - 加载中和错误状态处理(中)
- 六、用户体验优化 - 加载中和错误状态处理(下)
- 七、Hook,路由,与 URL 状态管理(上)
- 七、Hook,路由,与 URL 状态管理(中)
- 七、Hook,路由,与 URL 状态管理(下)
- 八、用户选择器与项目编辑功能(上)
- 八、用户选择器与项目编辑功能(下)
- 九、深入React 状态管理与Redux机制(一)
- 九、深入React 状态管理与Redux机制(二)
- 九、深入React 状态管理与Redux机制(三)
- 九、深入React 状态管理与Redux机制(四)
既然模态框使用 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.actions
export const selectUser = (state: RootState) => state.auth.user
export 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 而非 useHttp
const data = await http("me", { token });
user = data.user;
}
return user
};
- const AuthContext = React.createContext<
- | {
- user: User | null;
- login: (form: AuthForm) => Promise;
- register: (form: AuthForm) => Promise;
- logout: () => Promise;
- }
- | undefined
- >(undefined);
- AuthContext.displayName = "AuthContext";
export const AuthProvider = ({ children }: { children: ReactNode }) => {
// 这里要考虑到初始值的类型与后续值类型,取并组成一个泛型
const {
- data: user,
error,
isLoading,
isReady,
isError,
run,
- setData: setUser,
} = useAsync();
+ // const dispatch: (...args: unknown[]) => Promise = 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 ;
}
if (isError) {
return ;
}
- return (
-
- );
+ return
+ { children }
+
};
export const useAuth = () => {
- const context = React.useContext(AuthContext);
- if (!context) {
- throw new Error("useAuth 必须在 AuthProvider 中使用");
- }
- return context;
+ // 这种写法有报错
+ // const dispatch: (...args: unknown[]) => Promise = 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
:
不能将类型“Dispatch”分配给类型“(...args: unknown[]) => Promise”。
参数“action”和“args” 的类型不兼容。
不能将类型“unknown”分配给类型“AnyAction”
替换为 const dispatch: AppDispatch = useDispatch()
后正常
useMount(async () => run(dispatch(await initUser())))
中不加 async..await
的话会有如下报错提示:
没有与此调用匹配的重载。
第 1 个重载(共 3 个),“(thunkAction: ThunkAction, { projectList: State; auth: State; }, undefined, AnyAction>): Promise”,出现以下错误。
类型“Promise”的参数不能赋给类型“ThunkAction, { projectList: State; auth: State; }, undefined, AnyAction>”的参数。
类型“Promise”提供的内容与签名“(dispatch: ThunkDispatch<{ projectList: State; auth: State; }, undefined, AnyAction>, getState: () => { projectList: State; auth: State; }, extraArgument: undefined): Promise<...>”不匹配。
第 2 个重载(共 3 个),“(action: AnyAction): AnyAction”,出现以下错误。
类型“Promise”的参数不能赋给类型“AnyAction”的参数。
类型 "Promise" 中缺少属性 "type",但类型 "AnyAction" 中需要该属性。
第 3 个重载(共 3 个),“(action: AnyAction | ThunkAction, { projectList: State; auth: State; }, undefined, AnyAction>): AnyAction | Promise<...>”,出现以下错误。
类型“Promise”的参数不能赋给类型“AnyAction | ThunkAction, { projectList: State; auth: State; }, undefined, AnyAction>”的参数。
不能将类型“Promise”分配给类型“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"?
最终运行结果:登录,注册,登出功能都正常,只有登录保持不正常
部分引用笔记还在草稿阶段,敬请期待。。。