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

文章目录


学习内容来源: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、用户认证与异步请求


五、CSS 其实很简单 - 用 CSS-in-JS 添加样式


六、用户体验优化 - 加载中和错误状态处理



七、Hook,路由,与 URL 状态管理



八、用户选择器与项目编辑功能


九、深入React 状态管理与Redux机制

1&2

3.合并组件状态,实现useUndo

功能描述:

可以对一个数字进行不断地赋值,同时记录下历史值;可以通过undo对当前值进行撤销操作,一步步地回到最初值。在进行撤销操作的同时,记录下undo掉的值;通过redo可以回到undo之前的值,不断地redo最终可以回到执行所有撤销操作之前的值。

代码实现

使用useState实现该hook,由于多个state值之前相互引用。因此useCallback的依赖项中会填入多个state作为依赖项,但是在useCallback的回调函数中,在调用时会更新state值导致页面重新渲染,useCallback的回调函数也被重新定义了。

接下来模仿实现 use-undo

新建 src\utils\use-undo.ts

js 复制代码
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 !== 0
  const canRedo = future.length !== 0

  const undo = () => {
    if (!canUndo) return

    const previous = past[past.length - 1]
    const newPast = past.slice(0, past.length - 1)

    setPast(newPast)
    setPresent(previous)
    setFuture([present, ...future])
  }

  const redo = () => {
    if (!canRedo) return

    const 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 使用函数形式 直接使用历史状态 避免外界状态的使用,减少依赖
js 复制代码
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 !== 0
  const canRedo = state.future.length !== 0

  const undo = useCallback(() => {
    setState(currentState => {
      const { past, present, future } = currentState
      if (past.length === 0) return currentState
   
      const 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 } = currentState
      if (future.length === 0) return currentState
  
      const 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 } = currentState
      if (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 方法。

相比较于useStateuseReducer 具有如下优点:

  • state中的状态值之间相互关联;
  • 下一个 state的更新依赖于之前的 state

下面使用 useReducer 再对 use-undo 进行改写

编辑 src\utils\use-undo.ts

js 复制代码
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 } = state
  const { newPresent, type } = action

  switch(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):

js 复制代码
...
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]
  );
  ...
};

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

相关推荐
麻花20135 分钟前
WPF学习之路,控件的只读、是否可以、是否可见属性控制
服务器·前端·学习
.5486 分钟前
提取双栏pdf的文字时 输出文件顺序混乱
前端·pdf
jyl_sh14 分钟前
WebKit(适用2024年11月份版本)
前端·浏览器·客户端·webkit
zhanghaisong_20151 小时前
Caused by: org.attoparser.ParseException:
前端·javascript·html·thymeleaf
Eric_见嘉1 小时前
真的能无限试(白)用(嫖)cursor 吗?
前端·visual studio code
DK七七1 小时前
多端校园圈子论坛小程序,多个学校同时代理,校园小程序分展示后台管理源码
开发语言·前端·微信小程序·小程序·php
老赵的博客2 小时前
QSS 设置bug
前端·bug·音视频
Chikaoya2 小时前
项目中用户数据获取遇到bug
前端·typescript·vue·bug
南城夏季2 小时前
蓝领招聘二期笔记
前端·javascript·笔记
Huazie2 小时前
来花个几分钟,轻松掌握 Hexo Diversity 主题配置内容
前端·javascript·hexo