版本
一、React 16.0
为了解决之前大型 React 应用一次更新遍历大量虚拟 DOM 带来个卡顿问题,React 重写了核心模块 Reconciler ,启用了 Fiber 架构;为了在让节点渲染到指定容器内,更好的实现弹窗功能,推出 createPortal API;为了捕获渲染中的异常,引入 componentDidCatch 钩子,划分了错误边界。
二、React 16.2
推出 Fragment ,解决数组元素问题。
三、React 16.3
增加 React.createRef() API,可以通过 React.createRef 取得 Ref 对象。增加 React.forwardRef() API,解决高阶组件 ref 传递问题;推出新版本 context api,迎接Provider / Consumer 时代;增加 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 生命周期 。
四、React 16.6
增加 React.memo() API,用于控制子组件渲染;增加 React.lazy() API 实现代码分割;增加 contextType 让类组件更便捷的使用context;增加生命周期 getDerivedStateFromError 代替 componentDidCatch 。
五、React 16.8
全新 React-Hooks 支持,使函数组件也能做类组件的一切事情。
六、React 17
事件绑定由 document 变成 container ,移除事件池等。
七、React18
7.1 并发模式
React 18
引入新的根 API
root = createRoot(); root.render()
, 用于替换之前的版本的 ReactDOM.render()
方法。createRoot()
开启 React
并发模式。在并发模式下:
-
开启自动批处理
-
通过
useTransition
、useDeferredValue
开启并发特性, 进而开启 并发更新:React
并发更新利用fiber
结构和时间切片的机制, 将一个大任务分解成多个小任务,然后按照任务的优先级和线程的占用情况,对任务进行调度。从同步不可中断更新变成了异步可中断更新。调度逻辑如下:-
对于每个更新,为其分配一个优先级
lane
,用于区分其紧急程度 -
通过
Fiber
结构将不紧急的更新拆分成多段更新,并通过宏任务的方式将其合理分配到浏览器的帧当中。这样就能使得紧急任务能够插入进来 -
高优先级的更新会打断低优先级的更新,等高优先级更新完成后,再开始低优先级更新
-
低优先级任务如果一直得不到执行, 下一轮会以同步的优先级执行, 防止低优先级任务饿死。
-
并发更新下, 会发生组件状态与外部数据源不同步而导致渲染不一致, 进而渲染撕裂的问提。useSyncExternalStore
通过在 subscribe
和 getSnapshot
中实现精细的控制,保证了组件中的状态与外部数据源保持同步。这种机制确保了即使在应用中存在高频率的状态更新或并发模式下也不会出现状态不一致的情况。在 Concurrent Mode
下, React
可能会中断、暂停或重启渲染过程, useSyncExternalStore
可以确保即使在这种情况下,通过 getSnapshot
获取的状态仍然是最新的,有效避免了因组件状态与外部数据源不同步产生的问题。
7.2 自动批处理
React.js 18.x
之前, React
在合成事件、生命周期钩子(除 componentDidUpdate
除外)将会批量处理更新。但是在 Promise
结果、setTimeout
或者原生事件处理程序中, 更新将会以同步的方式处理。批量处理原理为: 通过全局变量 executionContext
控制 React
执行上下文,指示 React
开启同步或者异步更新。executionContext
一开始被初始化为 NoContext
,因此 React
默认是同步更新的。在合成事件、生命周期钩子(除 componentDidUpdate
除外), 中, 一开始这个变量会赋值为一个 BatchedContext
, 在此期间, 如果多次同步调用更新函数, 会走批量更新逻辑, 构造更新队列, 将多个更新函数加入到更新队列, 等到当前环境执行完毕后, 将 executionContext
恢复为之前状态, 根据更新队列进行调度更新。如果异步调用更新函数, 此时 executionContext
已经恢复之前状态, 直接开始更新任务。
React.js 18.x
之后, 几乎所有更新, 包括 Promise.then
、setTimeout
或者原生事件处理程序等, 都将自动批处理。批量处理逻辑为: 在同一环境中, 无论同步调用还是异步调用, 多次更新函数任务的优先级是相同的, 属于同一批任务。这一批任务都通过 scheduleUpdateOnFiber
标记相同的优先级之后, 开始通过 ensureRootIsScheduled
进行调度更新, 对比上次等待的更新和本次更新的优先级, 如果相等, 则会终止这个这任务的调度流程, 复用已有的调度任务。因 此, 多次触发更新只有第一次会进入到调度中
7.3 引入新的 API
客户端渲染 createRoot
: 用于客户端创建 React
渲染的根节点, 有 render
和 unmount
两个方法
水合服务端渲染 hydrateRoot
: 主要作用是在客户端接管从服务器端渲染的应用程序,并将应用程序的状态与服务器端保持一致。
7.4 引入新的 Hooks
React 18
引入 useTransition
、useDeferredValue
、useSyncExternalStore
等新 Hooks
。
-
useTransition
: 用于在不阻塞UI
的情况下, 更新其他状态。 -
useDeferredValue
: 用于延迟更新UI
的某些部分 -
useSyncExternalStore
: 用于订阅外部的store
7.5 开发环境严格模式
当你使用严格模式时,React
会对每个组件进行两次渲染,以便你观察一些意想不到的结果, 目的在于帮助开发人员发现意外的副作用。重复渲染有助于暴露出依赖于副作用的不一致行为(例如,在组件的不同渲染阶段中读写到全局变量), 确保应用能够适应React未来可能引入的并发(Concurrent
)模式。严格模式如下所示:
import React from 'react';
import App from './App.tsx';
import ReactDOM from 'react-dom/client';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
React.js 18
之前, 取消了其中一次渲染的控制台日志,以便让日志更容易阅读。
React.js 18
之后, 为了解决社区对这个问题的困惑,官方取消了这个限制, 所以会有两次渲染日志。