【实战】 九、深入React 状态管理与Redux机制(五) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十)

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、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 来管理了,而 reduxcontext 是竞争关系,那可以尝试将之前管理登录状态的 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 AuthForminitUser(视频中对应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 = useDispatch()

不能将类型“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"?

最终运行结果:登录,注册,登出功能都正常,只有登录保持不正常


部分引用笔记还在草稿阶段,敬请期待。。。

你可能感兴趣的:(react.js,前端,前端框架)