【实战】 九、深入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.配置redux-toolkit
      • 10.应用 redux-toolkit 管理模态框


学习内容来源: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.配置redux-toolkit

  • Redux Toolkit

redux-toolkit 是对 redux 的二次封装,主要解决三大痛点:

  • 配置复杂
  • 需要增加的包太多
  • 需要太多模板代码

由于项目最终不会使用到 redux,因此接下来新开一个分支用作学习开发,创建分支 redux-toolkit

安装依赖:

npm i react-redux @reduxjs/toolkit # --force

新建 src\store\index.tsx

import { configureStore } from "@reduxjs/toolkit"

export const rootReducer = {}

export const store = configureStore({
  reducer: rootReducer
})

export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

新建 src\screens\ProjectList\projectList.slice.ts

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

interface State {
  projectModalOpen: boolean;
}

const initialState: State = {
  projectModalOpen: false
}

export const projectListSlice = createSlice({
  name: 'projectListSlice',
  initialState,
  reducers: {
    openProjectModal(state, action) {},
    closeProjectModal(state, action) {}
  }
})

问:为什么这里可以直接给 state 的属性赋值?
答:redux 借助内置的 immer 来处理使其变为不可变数据的同时,创建“影子状态”最终整体替换原状态

10.应用 redux-toolkit 管理模态框

完善 src\screens\ProjectList\projectList.slice.ts

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

interface State {
  projectModalOpen: boolean;
}

const initialState: State = {
  projectModalOpen: false,
};

export const projectListSlice = createSlice({
  name: "projectListSlice",
  initialState,
  reducers: {
    openProjectModal(state) {
      state.projectModalOpen = true
    },
    closeProjectModal(state) {
      state.projectModalOpen = false
    },
  },
});

export const projectListActions = projectListSlice.actions;

export const selectProjectModalOpen = (state: RootState) => state.projectList.projectModalOpen

后续使用方式:

  • 引入
    • import { useDispatch, useSelector } from "react-redux";
    • import { projectListActions, selectProjectModalOpen } from "../projectList.slice";
  • 使用 hook 拿到 dispatchconst dispatch = useDispatch()
  • 使用 dispatch 调用打开模态框:() => dispatch(projectListActions.openProjectModal())
  • 使用 dispatch 调用关闭模态框:() => dispatch(projectListActions.closeProjectModal())
  • 使用 hook 获取模态框当前开闭状态:useSelector(selectProjectModalOpen)
    • useSelector 用来读根状态树

修改 src\store\index.tsx(引入 projectListSlice):

import { configureStore } from "@reduxjs/toolkit";
import { projectListSlice } from "screens/ProjectList/projectList.slice";

export const rootReducer = {
  projectList: projectListSlice.reducer,
};

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

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
  • ReturnType 用来读取函数返回值的类型

之前在 AuthenticatedApp(子封装模态框组件引入的地方) 创建状态量(const [isOpen, setIsOpen] = useState(false))分别一层一层传入使用到模态框的地方

现在不用啦,接下来开始使用 reduxdispatch 更改模态框状态:

编辑 src\authenticated-app.tsx

...
export const AuthenticatedApp = () => {
-  const [isOpen, setIsOpen] = useState(false);
  ...
  return (
    
-       setIsOpen(true)}>
-            创建项目
-          
-        }
-      />
+      
      
setIsOpen(true)} - > - 创建项目 - - } - /> + } /> ...
- setIsOpen(false)} /> +
); }; - const PageHeader = (props: { projectButton: JSX.Element }) => { + const PageHeader = () => { ... return (
... - + 用户 ...
); }; ...

编辑 src\screens\ProjectList\index.tsx

+ import { useDispatch } from "react-redux";
+ import { ButtonNoPadding } from "components/lib";
+ import { projectListActions } from "./projectList.slice";

- export const ProjectList = ({
-   projectButton,
- }: {
-   projectButton: JSX.Element;
- }) => {
+ export const ProjectList = () => {
  ...
+   const dispatch = useDispatch()

  return (
    
      
        

项目列表

- {projectButton} + dispatch(projectListActions.openProjectModal())}> + 创建项目 +
...
); }; ...

编辑 src\screens\ProjectList\components\List.tsx

...
+ import { useDispatch } from "react-redux";
+ import { projectListActions } from "../projectList.slice";


interface ListProps extends TableProps {
  users: User[];
  refresh?: () => void;
-  projectButton: JSX.Element;
}

// type PropsType = Omit
export const List = ({ users, ...props }: ListProps) => {
+  const dispatch = useDispatch()
  ...
  return (
     {
+            const items: MenuProps["items"] = [
+              {
+                key: "edit",
+                label: "编辑",
+                onClick: () => dispatch(projectListActions.openProjectModal()),
+              },
+            ];
            return (
-               props.projectButton}>
+              
                ...
              
            );
          },
        },
      ]}
      {...props}
    >
); };

编辑 src\screens\ProjectList\components\ProjectModal.tsx

...
+ import { useDispatch, useSelector } from "react-redux";
+ import { projectListActions, selectProjectModalOpen } from "../projectList.slice";

- export const ProjectModal = ({
-   isOpen,
-   onClose,
- }: {
-   isOpen: boolean;
-   onClose: () => void;
- }) => {
+ export const ProjectModal = () => {
+   const dispatch = useDispatch()
+   const projectModalOpen = useSelector(selectProjectModalOpen)

  return (
-     
+      dispatch(projectListActions.closeProjectModal())}
+       open={projectModalOpen}
+       width="100%"
+     >
      

Project Modal

- +
); };

编辑 src\screens\ProjectList\components\ProjectPopover.tsx

...
+ import { useDispatch } from "react-redux";
+ import { projectListActions } from "../projectList.slice";
+ import { ButtonNoPadding } from "components/lib";

- export const ProjectPopover = ({
-   projectButton,
- }: {
-   projectButton: JSX.Element;
- }) => {
+ export const ProjectPopover = () => {
+   const dispatch = useDispatch()
  ...

  const content = (
    
      收藏项目
      
        {starProjects?.map((project) => (
-           
+           
            
          
        ))}
      
      
-      {projectButton}
+       dispatch(projectListActions.openProjectModal())}>
+        创建项目
+      
    
  );
  ...
};
...

现在访问页面会发现有报错:

could not find react-redux context value; please ensure the component is wrapped in a <Provider>

这是因为没有将 reduxstore 绑定到全局 context

编辑 src\context\index.tsx

...
+ import { store } from "store";
+ import { Provider } from "react-redux";

export const AppProvider = ({ children }: { children: ReactNode }) => {
  return (
+   
      
        {children}
      
+   
  );
};

再次访问页面,功能 OK 了


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

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