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 - 掘金

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

My Hooks

javascript 复制代码
function useBodyScrollPosition() {
  const [scrollPosition, setScrollPosition] = useState(null);

  useEffect(() => {
    const handleScroll = () => setScrollPosition(window.scrollY);
    document.addEventListener('scroll', handleScroll);
    return () =>
      document.removeEventListener('scroll', handleScroll);
  }, []);

  return scrollPosition;
}
  • 表面上:一个命名格式为 useXXX 的函数,但不是 React 函数式组件
  • 本质上:内部通过使用 React 自带的一些 Hook (例如 useState 和 useEffect )来实现某些通用的逻辑
  • 推荐的库:React Use 、aHooks、Umi Hooks

useState

定义

javascript 复制代码
const [state, setState] = useState(initialValue);

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

原理

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

函数式更新

reduce 方法

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

react 源码

javascript 复制代码
function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}
  • 详细实现可以看链接文章的动态图
  • 静态更新值直接返回结果
  • 函数更新值会先把当前数据状态传入函数并计算结果返回

useEffect

定义

javascript 复制代码
useEffect(() => {
  const intervalId = setInterval(doSomething(), 1000);
  return () => clearInterval(intervalId);
});
  • effectFn 是一个执行某些可能具有副作用 的 Effect 函数(例如数据获取、设置/销毁定时器等),它可以返回一个清理函数 (Cleanup),例如大家所熟悉的 setInterval 和 clearInterval
    • 每个 Effect 必然在渲染之后执行,因此不会阻塞渲染,提高了性能
    • 在运行每个 Effect 之前,运行前一次渲染的 Effect Cleanup 函数(如果有的话)
    • 当组件销毁时,运行最后一次 Effect 的 Cleanup 函数
  • 依赖数组选项:
    • 不写,每一次渲染后执行
    • \],只会在组件初次渲染执行

    • 判断数据是否发生改变使用 Object.is,对于基本数据类型可以直接判断(和===相似,不同点在于NAN为true,0和-0为false),但对于对象类型就判断在堆内存的引用地址是否发生改变

原理

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

无限循环

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

useCallback

定义

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

原理

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

useMemo

定义

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

比较

  • useMemo 用于缓存结果,useCallback用于缓存值
javascript 复制代码
function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}

Redux


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

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

useReducer

定义

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

使用

javascript 复制代码
// 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>
    </>
  );
}
  • 定义函数和初始状态,获取 state 和 dispatch 函数
  • 通过传入参数(带上类型),在reducer函数中修改并返回新的值

应用

  • 简单的状态修改更新 useState 够用
  • 对于需要维护的状态本身比较复杂,多个状态之间相互依赖、修改状态的过程比较复杂可以使用
  • 例子如下:
javascript 复制代码
// 用于懒初始化的函数
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;
  }
}

useContext

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

  • Context 使组件向其下方的整个树提供信息,会穿过中间的任何组件
  • 通过 useContext ,我们就可以轻松地让所有组件都能获取到 dispatch 函数
相关推荐
山北雨夜漫步9 分钟前
机器学习 Day09 线性回归
人工智能·机器学习·线性回归
夕秋一梦18 分钟前
vue项目本地调试使用https
前端·vue.js·https
小破孩呦21 分钟前
动态列表的数据渲染、新增、编辑等功能开发及数据处理
前端·javascript·elementui
成长ing1213821 分钟前
点击音效系统
前端·cocos creator
熟悉不过22 分钟前
cesium项目之cesiumlab地形数据加载
前端·javascript·vue.js·cesium·webgis·cesiumlab
神经毒素32 分钟前
WEB安全--XSS--DOM破坏
前端·web安全·xss
shelly聊AI1 小时前
Meta上新Llama 4,到底行不行?
人工智能·llama
不简说1 小时前
sv-print可视化打印组件不完全指南③
前端·javascript·vue.js
前端摸鱼杭小哥1 小时前
Vue 开发者狂喜!我在 React 中完美复刻了 v-if/v-for 指令
前端·vue.js·react.js
kovli1 小时前
红宝书第四讲:JavaScript原始值与引用值行为差异详解
前端·javascript