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

这篇具有很好参考价值的文章主要介绍了【实战】 九、深入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 应用:实现项目列表

  • 二、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 !== 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 使用函数形式 直接使用历史状态 避免外界状态的使用,减少依赖
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 | 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 } = 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):

...
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]
  );
  ...
};

部分引用笔记还在草稿阶段,敬请期待。。。文章来源地址https://www.toymoban.com/news/detail-622223.html

到了这里,关于【实战】 九、深入React 状态管理与Redux机制(二) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十七)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包