🍂落页
登 录

React 18 笔记

  • Fiber 架构
  • 新 Suspense 下的 SSR 框架
  • API - react
  • API - react-dom
  • 跳出机制
  • 个人对 React 的理解
  • 发布博客
🍂落页
TALAXY
跳出机制
🍂落页
TALAXY

跳出机制

jotai 是一个管理 React 全局状态的第三方库。我在学习的时候发现它一个“缺陷”:即便是最简单的代码,也会导致二次“渲染”:

import { atom, useAtomValue } from 'jotai';

const countAtom = atom(0);

const Page = () => {
    const count = useAtomValue(count);
    return <div>{count}</div>;
}

我去 jotai 的 GitHub 查了相关的问题,确实有不少人提问,而官方在 issue 1015 和 issue 1444 给了解释:jotai 的实现使用了 useReducer ,二次“渲染”是由 useReducer 引起的。

在 React 18 中,去除了 useReducer 的 跳出机制(eager bailout),来修复 React 中的一些其他问题。跳出机制是指:在更新状态时,如果状态值相同,则不去触发一个新的更新。比如 useState 仍保持着这一行为机制。而对于 useReducer 举个简单例子:

import { useReducer } from 'react';

const initialState = 0;
const reducer = (state, action) => {
    switch(action) {
        case 'reset':
            return initialState;
        // other actions...
    }
};

const Page = () => {
    console.log('rendering...');    // 检测是否被渲染
    const [count, dispatch] = useReducer(reducer, initialState);
    return <button onClick={() => dispatch('reset')}>Reset</button>;
}

当点击按钮时,在 React 17 中是不会触发 console 打印的,而在 18 中每次点击都会触发。但这并不意味着 Page 在每次点击按钮时都被重复渲染了。React 之所以能自信地把 useReducer 的跳出机制移除,是因为 React 背后还有 Fiber 调度,Fiber 会比对新旧参数来确定是否真的提交渲染工作,即应用到 DOM 上。

通过 Fiber ,React 将渲染工作分为了两个阶段:

  • 一阶段:比对组件树(fiber 树),确定所需的更新操作;
  • 二阶段:将更新操作应用到 DOM 上。

在 React 18 中,引入了渲染中断机制,即旧的渲染工作可以被新的渲染工作打断。这意味着组件的一次更新可能确实进入过一阶段,但不会进入二阶段。而上面的例子中,console 打印只能表明渲染工作进入了一阶段。而如果要确定是否进入了二阶段,可以用一个不带依赖项的 useEffect ,它代表组件确实被更新了,而不仅仅只是被渲染:

const Page = () => {
    console.log('rendering...');    // 检测是否被渲染
    const [count, dispatch] = useReducer(reducer, initialState);
    useEffect(() => console.log('render committed'));   // 检测是否被更新
    return <button onClick={() => dispatch('reset')}>Reset</button>;
}

TALAXY 落于 2024年6月16日 。