学习内容来源: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机制(三)
- 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
来处理使其变为不可变数据的同时,创建“影子状态”最终整体替换原状态
完善 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
拿到dispatch
:const 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)
)分别一层一层传入使用到模态框的地方
现在不用啦,接下来开始使用 redux
的 dispatch
更改模态框状态:
编辑 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>
这是因为没有将 redux
的 store
绑定到全局 context
上
编辑 src\context\index.tsx
:
...
+ import { store } from "store";
+ import { Provider } from "react-redux";
export const AppProvider = ({ children }: { children: ReactNode }) => {
return (
+
{children}
+
);
};
再次访问页面,功能 OK 了
部分引用笔记还在草稿阶段,敬请期待。。。