文章目录
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、TS 应用:JS神助攻 - 强类型
- 四、JWT、用户认证与异步请求
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
- 六、用户体验优化 - 加载中和错误状态处理
- 七、Hook,路由,与 URL 状态管理
- 八、用户选择器与项目编辑功能
- 九、深入React 状态管理与Redux机制
- 1&2
- 3.合并组件状态,实现useUndo
- 4.用useReducer进行状态管理
学习内容来源: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.合并组件状态,实现useUndo
功能描述:
可以对一个数字进行不断地赋值,同时记录下历史值;可以通过undo对当前值进行撤销操作,一步步地回到最初值。在进行撤销操作的同时,记录下undo掉的值;通过redo可以回到undo之前的值,不断地redo最终可以回到执行所有撤销操作之前的值。
代码实现
使用useState实现该hook,由于多个state值之前相互引用。因此useCallback的依赖项中会填入多个state作为依赖项,但是在useCallback的回调函数中,在调用时会更新state值导致页面重新渲染,useCallback的回调函数也被重新定义了。
接下来模仿实现 use-undo
- use-undo-demo - CodeSandbox
- use-undo - npm
新建 src\utils\use-undo.ts
:
import { useState } from "react";export const useUndo = <T>(initialPresent: T) => {// 记录历史操作的合集const [past, setPast] = useState<T[]>([])const [present, setPresent] = useState(initialPresent)// 记录未来操作的合集const [future, setFuture] = useState<T[]>([])const canUndo = past.length !== 0const canRedo = future.length !== 0const undo = () => {if (!canUndo) returnconst previous = past[past.length - 1]const newPast = past.slice(0, past.length - 1)setPast(newPast)setPresent(previous)setFuture([present, ...future])}const redo = () => {if (!canRedo) returnconst next = future[0]const newFuture = future.slice(1)setFuture(newFuture)setPresent(next)setPast([...past, present])}const set = (newPresent: T) => {if (newPresent === present) {return}setPast([...past, present])setPresent(newPresent)setFuture([])}const reset = (newPresent: T) => {setPast([])setPresent(newPresent)setFuture([])}return [{past, present, future},{redo, undo, set, reset, canRedo, canUndo}] as const
}
现需要对代码做以下优化:
- 使用 useCallback 避免 造成之前的那种 依赖不等导致的循环渲染
- 将用到的 变量合并声明 后续也同步改动
- SetXXX 使用函数形式 直接使用历史状态 避免外界状态的使用,减少依赖
import { useCallback, useState } from "react";export const useUndo = <T>(initialPresent: T) => {// 合并声明const [state, setState] = useState<{past: T[],present: T,future: T[]}>({past: [],present: initialPresent,future: []})// 另一种写法// const [state, setState] = useState({// past: [] as T[],// present: initialPresent as T,// future: [] as T[]// })const canUndo = state.past.length !== 0const canRedo = state.future.length !== 0const undo = useCallback(() => {setState(currentState => {const { past, present, future } = currentStateif (past.length === 0) return currentStateconst previous = past[past.length - 1]const newPast = past.slice(0, past.length - 1)return {past: newPast,present: previous,future: [present, ...future]}})}, [])const redo = useCallback(() => {setState(currentState => {const { past, present, future } = currentStateif (future.length === 0) return currentStateconst next = future[0]const newFuture = future.slice(1)return {past: [...past, present],present: next,future: newFuture}})}, [])const set = useCallback((newPresent: T) => {setState(currentState => {const { past, present } = currentStateif (newPresent === present) {return currentState}return {past: [...past, present],present: newPresent,future: []}})}, [])const reset = useCallback((newPresent: T) => {setState({past: [],present: newPresent,future: []})}, [])return [state,{redo, undo, set, reset, canRedo, canUndo}] as const
}
4.用useReducer进行状态管理
替代方案:
useReducer
作为useState
的替代方案。它接收一个形如 (state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
相比较于useState
,useReducer
具有如下优点:
state
中的状态值之间相互关联;- 下一个
state
的更新依赖于之前的state
。
- useReducer | Hook API 索引 – React
下面使用 useReducer
再对 use-undo
进行改写
编辑 src\utils\use-undo.ts
:
import { useCallback, useReducer, useState } from "react";const UNDO = 'UNDO'
const REDO = 'REDO'
const SET = 'SET'
const RESET = 'RESET'type State<T> = {past: T[];present: T;future: T[];
}type Action<T> = { newPresent?: T, type: typeof UNDO | typeof REDO | typeof SET | typeof RESET }const undoReducer = <T>(state: State<T>, action: Action<T>) => {const { past, present, future } = stateconst { newPresent, type } = actionswitch(type) {case UNDO: {if (past.length === 0) return state;const previous = past[past.length - 1];const newPast = past.slice(0, past.length - 1);return {past: newPast,present: previous,future: [present, ...future],};}case REDO: {if (future.length === 0) return state;const next = future[0];const newFuture = future.slice(1);return {past: [...past, present],present: next,future: newFuture,};}case SET: {if (newPresent === present) {return state;}return {past: [...past, present],present: newPresent,future: [],};}case RESET: {return {past: [],present: newPresent,future: [],}}default: return state}
}export const useUndo = <T>(initialPresent: T) => {const [state, dispatch] = useReducer(undoReducer, {past: [],present: initialPresent,future: [],} as State<T>)const canUndo = state.past.length !== 0;const canRedo = state.future.length !== 0;const undo = useCallback(() => dispatch({ type: UNDO }), []);const redo = useCallback(() => dispatch({ type: REDO }), []);const set = useCallback((newPresent: T) => dispatch({newPresent, type: SET}), []);const reset = useCallback((newPresent: T) => dispatch({newPresent, type: RESET}), []);return [state, { redo, undo, set, reset, canRedo, canUndo }] as const;
};
统一状态管理后 虽然代码量多了,但是经过多重封装,层次更加清晰
可以发现 useReducer
相对 useState
适合定义多个相互影响的状态量
鉴于 useReducer
针对复杂的state关系和更新的前后依赖的优势,因此 useAsync
非常适合使用 useReducer
来重构
接下来使用 useReducer
改造一下 与 use-undo
结构类似的 use-async
(src\utils\use-async.ts
):
...
const useSafeDispatch = <T>(dispatch: (...args: T[]) => void) => {const mountedRef = useMountedRef()return useCallback((...args: T[]) => (mountedRef.current ? dispatch(...args) : void 0), [dispatch, mountedRef])
}export const useAsync = <D>(...) => {const config = { ...defaultConfig, ...initialConfig };const [state, dispatch] = useReducer((state: State<D>, action: Partial<State<D>>) => ({...state, ...action}), {...defaultInitialState,...initialState,});const safeDispatch = useSafeDispatch(dispatch);const [rerun, setRerun] = useState(() => () => {});const setData = useCallback((data: D) =>safeDispatch(...),[safeDispatch]);const setError = useCallback((error: Error) =>safeDispatch(...),[safeDispatch]);// run 来触发异步请求const run = useCallback((...) => {...safeDispatch({ stat: "loading" });return promise.then((data) => {setData(data);return data;}).catch(...);},[config.throwOnError, safeDispatch, setData, setError]);...
};
部分引用笔记还在草稿阶段,敬请期待。。。