• React 高级教程


    在这里插入图片描述



    前言

    在现代前端开发中,React已经成为了一种无法忽视的技术。它的函数式编程模式,以及强大的Hooks API,为我们提供了一种全新的编程范式,使得我们可以更加灵活、高效地构建用户界面。

    在这篇文章中,我们将深入探讨React的函数式编程,以及Hooks的定义和原理。我们将详细介绍一些常见的Hooks,如useState、useEffect、useCallback、useMemo、useContext和useReducer等,帮助你更好地理解和使用这些强大的工具。无论你是React的新手,还是有一定经验的开发者,我相信你都能在这篇文章中找到有价值的内容。让我们一起,深入探索React的世界。

    setState

    setState 本身代码的执行肯定是同步的,这里的异步是指是多个 state 会合成到一起进行批量更新。

    函数式编程

    这篇文章写的真的太好了,一定要读:简明 JavaScript 函数式编程——入门篇
    总结一下: 函数式编程有两个核心概念。

    • 数据不可变(无副作用): 它要求你所有的数据都是不可变的,这意味着如果你想修改一个对象,那你应该创建一个新的对象用来修改,而不是修改已有的对象。
    • 无状态: 主要是强调对于一个函数,不管你何时运行,它都应该像第一次运行一样,给定相同的输入,给出相同的输出,完全不依赖外部状态的变化。

    纯函数带来的意义。

    • 便于测试和优化:这个意义在实际项目开发中意义非常大,由于纯函数对于相同的输入永远会返回相同的结果,因此我们可以轻松断言函数的执行结果,同时也可以保证函数的优化不会影响其他代码的执行。
    • 可缓存性:因为相同的输入总是可以返回相同的输出,因此,我们可以提前缓存函数的执行结果。
    • 更少的 Bug:使用纯函数意味着你的函数中不存在指向不明的 this,不存在对全局变量的引用,不存在对参数的修改,这些共享状态往往是绝大多数 bug 的源头。

    Hooks

    用动画和实战打开 React Hooks(一):useState 和 useEffect - 掘金
    用动画和实战打开 React Hooks(二):自定义 Hook 和 useCallback - 掘金
    用动画和实战打开 React Hooks(三):useReducer 和 useContext - 掘金

    1.png

    2.png

    • Hooks **只能用于 React 函数式组件,**组件的渲染的状态、事件处理函数每一次都是独立的。

    My Hooks

    function useBodyScrollPosition() {
      const [scrollPosition, setScrollPosition] = useState(null);
    
      useEffect(() => {
        const handleScroll = () => setScrollPosition(window.scrollY);
        document.addEventListener('scroll', handleScroll);
        return () =>
          document.removeEventListener('scroll', handleScroll);
      }, []);
    
      return scrollPosition;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 表面上:一个命名格式为 useXXX 的函数,但不是 React 函数式组件
    • 本质上:内部通过使用 React 自带的一些 Hook (例如 useState 和 useEffect )来实现某些通用的逻辑
    • 推荐的库:React Use 、aHooks、Umi Hooks

    useState

    定义

    const [state, setState] = useState(initialValue);
    
    • 1

    其中 state 就是一个状态变量,setState 是一个用于修改状态的 Setter 函数,而 initialValue 则是状态的初始值

    原理

    1.png

    1. 在初次渲染时,我们通过 useState 定义了多个状态;
    2. 每调用一次 useState ,都会在组件之外生成一条 Hook 记录,同时包括状态值(用 useState 给定的初始值初始化)和修改状态的 Setter 函数;
    3. 多次调用 useState 生成的 Hook 记录形成了一条链表
    4. 触发 onClick 回调函数,调用 setS2 函数修改 s2 的状态,不仅修改了 Hook 记录中的状态值,还即将触发重渲染

    函数式更新

    reduce 方法

    const nums = [1, 2, 3]
    const value = nums.reduce((acc, next) => acc + next, 0)
    
    • 1
    • 2
    • next 是遍历数组nums的值,acc默认值自定义,新的值是函数的返回值
    • 特点是只返回一个值,不修改输入值

    react 源码

    function basicStateReducer(state, action) {
      return typeof action === 'function' ? action(state) : action;
    }
    
    • 1
    • 2
    • 3

    1.png

    2.png

    • 详细实现可以看链接文章的动态图
    • 静态更新值直接返回结果
    • 函数更新值会先把当前数据状态传入函数并计算结果返回

    useEffect

    定义

    useEffect(() => {
      const intervalId = setInterval(doSomething(), 1000);
      return () => clearInterval(intervalId);
    });
    
    • 1
    • 2
    • 3
    • 4
    • effectFn 是一个执行某些可能具有副作用的 Effect 函数(例如数据获取、设置/销毁定时器等),它可以返回一个清理函数(Cleanup),例如大家所熟悉的 setInterval 和 clearInterval
      • 每个 Effect 必然在渲染之后执行,因此不会阻塞渲染,提高了性能
      • 在运行每个 Effect 之前,运行前一次渲染的 Effect Cleanup 函数(如果有的话)
      • 当组件销毁时,运行最后一次 Effect 的 Cleanup 函数
    • 依赖数组选项:
      • 不写,每一次渲染后执行
      • [],只会在组件初次渲染执行
      • [a,b]会在依赖项发生变化执行
      • 判断数据是否发生改变使用 Object.is,对于基本数据类型可以直接判断(和===相似,不同点在于NAN为true,0和-0为false),但对于对象类型就判断在堆内存的引用地址是否发生改变

    原理

    1.png

    1. useState 和 useEffect 在每次调用时都被添加到 Hook 链表中;
    2. useEffect 还会额外地在一个队列中添加一个等待执行的 Effect 函数;
    3. 在渲染完成后,依次调用 Effect 队列中的每一个 Effect 函数。

    无限循环

    • 依赖项在事件的内部更新,导致触发渲染 => 触发 Effect => 修改状态 => 触发重渲染的无限循环
    • 要正确传递依赖项,特别是被useState 定义和引用的数据类型

    useCallback

    定义

    const memoizedCallback = useCallback(callback, deps);
    
    • 1
    • useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。
    • 第一个参数 callback 就是需要记忆的函数,第二个参数就是大家熟悉的 deps 参数
    • 在 Memoization 的上下文中,这个 deps 的作用相当于缓存中的键(Key),如果键没有改变,那么就直接返回缓存中的函数,如果有改变,就生成新的函数并缓存
    • 在大多数情况下,我们都是传入空数组 [] 作为 deps 参数,这样 useCallback 返回的就始终是同一个函数,永远不会更新

    原理

    1.png

    2.png

    • 组件状态更新或者props改变会导致组件重新渲染,如果需要传递函数给子组件,那么一般的函数每次就会定义新的函数,导致子组件渲染(无法使用 memo 跳过)
    • 引用数据类型函数永远指向相同的堆内存地址,可以保证函数在初始化定义之后就不改变

    useMemo

    定义

    const cachedValue = useMemo(calculateValue, dependencies)
    
    • 1
    • useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果

    比较

    • useMemo 用于缓存结果,useCallback用于缓存值
    function useCallback(fn, dependencies) {
      return useMemo(() => fn, dependencies);
    }
    
    • 1
    • 2
    • 3

    Redux

    1.png
    堪称 React 版本的 Pinia,这才是你该选的 React 状态管理库! - 掘金

    • redux 是通过全局的状态管理来实现数据的通信
    • 和之前使用的 vuex 差不多,但现在有更好的状态管理库可以使用

    useReducer

    定义

    const [state, dispatch] = useReducer(reducer, initialArg, init);
    
    • 1
    1. 第一个参数 reducer 显然是必须的,它的形式跟 Redux 中的 Reducer 函数完全一致,即 (state, action) => newState。
    2. 第二个参数 initialArg 就是状态的初始值。
    3. 第三个参数 init 是一个可选的用于懒初始化(Lazy Initialization)的函数,这个函数返回初始化后的状态。

    使用

    // Reducer 函数
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        default:
          throw new Error();
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(reducer, { count: 0 });
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: 'increment' })}>+</button>
        </>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 定义函数和初始状态,获取 state 和 dispatch 函数
    • 通过传入参数(带上类型),在reducer函数中修改并返回新的值

    应用

    • 简单的状态修改更新 useState 够用
    • 对于需要维护的状态本身比较复杂,多个状态之间相互依赖、修改状态的过程比较复杂可以使用
    • 例子如下:
    // 用于懒初始化的函数
    function init(initialState) {
      return {
        past: [],
        present: initialState,
        future: [],
      };
    }
    
    // Reducer 函数
    function reducer(state, action) {
      const { past, future, present } = state;
      switch (action.type) {
        case 'UNDO':
          return {
            past: past.slice(0, past.length - 1),
            present: past[past.length - 1],
            future: [present, ...future],
          };
        case 'REDO':
          return {
            past: [...past, present],
            present: future[0],
            future: future.slice(1),
          };
        default:
          return state;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    useContext

    使用 Context 深层传递参数 – React 中文文档

    1.png

    • Context 使组件向其下方的整个树提供信息,会穿过中间的任何组件
    • 通过 useContext ,我们就可以轻松地让所有组件都能获取到 dispatch 函数
  • 相关阅读:
    FPGA之旅设计99例之第二十一例----VGA串口SDRAM显示图片
    一篇文章全面解析Modbus协议中的消息帧
    java常见的三种定时任务调度框架,写得太棒了
    Java shp 转 GeoJson
    10000阅读量感言
    CentOS7安装GmSSL过程记录
    数据安全峰会2022 | 美创DSM获颁“数据安全产品能力验证计划”评测证书
    python异步编程之asyncio初识
    Linux组管理和权限管理
    基于Spring Boot项目构建流水线
  • 原文地址:https://blog.csdn.net/qq_53673551/article/details/134406882