大家好,我卡颂。

React的代码量能够说是相当宏大。在如此宏大的库中是否存在文档中未提及,然而理论存在的性能呢?

答案是必定的。

本文将向你介绍3个文档中未提及的暗藏彩蛋性能。

欢送退出人类高质量前端交换群,带飞

ref cleanup

在以后React中,Ref存在两种数据结构:

  1. <T>(instance: T) => void
  2. {current: T}

对于大部分需要,咱们会应用第二种数据结构。同时,他也是useRefcreateRef返回的数据结构。

第一种数据结构次要用于DOM监控,比方在上面的例子中,div的尺寸会反映到height状态中:

function MeasureExample() {  const [height, setHeight] = useState(0);  const measuredRef = useCallback(node => {    if (node !== null) {      setHeight(node.getBoundingClientRect().height);    }  }, []);  return (    <div ref={measuredRef}>Hello 卡颂</div>  );}

但在下面的例子中,DOM的尺寸变动无奈实时反映到height状态。为了反映实时变动,须要应用监控DOM的原生API,比方:

  • ResizeObserver,监控DOM尺寸变动
  • IntersectionObserver,监控DOM可视区域变动
  • MutationObserver,监控DOM树变动

这些API通常是事件驱动,这就波及到当不须要监控后,解绑事件

既然事件绑定是在ref回调中进行的,很天然的,解绑事件也应该在ref回调中进行。比方,用ResizeObserver革新上述例子:

function MeasureExample() {  const [entry, setEntry] = useState();    const measuredRef = useCallback((node) => {    const observer = new ResizeObserver(([entry]) => {      setEntry(entry)    })    observer.observe(node)    // 解绑事件    return () => {      observer.disconnect()    }  }, [])  return (    <div ref={measuredRef}>Hello 卡颂</div>  );}

在这个场景中,咱们心愿函数类型的ref能够返回一个新函数,用于解绑事件(相似useEffect回调的返回值)。

实际上,在19年的#issues 15176中就有人提出这个问题。在去年底的#pull 25686中相干改变曾经合并到React main分支。

以后React文档中Ref的局部还未提及这个性能改变。可能在将来的某个小版本中,会上线这个性能。

Module Component

你感觉上面的函数组件能渲染出hello么:

function Child() {  return {    render() {      return "hello";    }  };}

答案是 —— 能够,见Module Component在线示例。

其实这是一种上古期间就存在的组件模式,叫做Module Component(即函数组件返回带有render属性的对象)。

当遇到Module ComponentReact会将对应函数组件(上例中的Child组件)转换为Class Component,后续更新流程与Class Component无异。

Module Component预计会在将来的某个版本被移除(见#pull 15145),所以文档中并未提及。

要说有啥理论作用,没准能够给你共事带来点小小困惑......

开启全局并发更新

v18中,只有应用并发个性(比方useTransiton)触发的更新才是并发更新,其余状况触发的更新都是同步更新。

如何能力不应用并发个性,又能全局开启并发更新呢?答案是在我的项目中退出上面这行咒语:

React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentBatchConfig.transition = {  帅哥: '卡颂'};

比方,对于如下例子,渲染一个耗时的列表(每个Item组件render会耗时5ms):

function App() {  return (    <ul className="App">      {Array.from({ length: 100 }).map((_, i) => (        <Item key={i} num={i}>          {i}        </Item>      ))}    </ul>  );}function Item({ children }) {  const cur = performance.now();  while (performance.now() - cur < 5) {}  return <li>{children}</li>;}
并发示例地址

不加上咒语时的渲染火炬图如下,整个更新流程在一个宏工作中,耗时513ms:

加上咒语时的渲染火炬图如下,整个更新流程被工夫切片,每个切片5ms左右:

咒语为什么会起作用呢?

ReactReactDOM中都存在变量__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,这个变量的作用是 —— 在不同包之间共享数据。

比方,所有Hook都是从React包中导出的,但Hook的具体实现在ReactDOM包中。为了在他们之间共享Hook,就须要一个媒介,这就是__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED

相似的,更新相干数据也须要在ReactReactDOM间共享,其中就包含 —— 更新是否是并发更新。

当咱们赋值React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentBatchConfig.transition后,React会认为以后更新是并发更新。

通过这种形式,就能全局开启并发更新。

当然,我并不倡议你随便更改__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED下的数据,毕竟这个变量的名字还是挺唬人的。

总结

以上便是3个React中的暗藏彩蛋性能。其实除了他们之外,React中还有很多没有裸露进去的API,比方相似VueKeep-AliveOffscreen Component

以后要想体验Offscreen Component只能通过Suspense间接体验(Suspense可能在pending与挂载组件间切换就是利用Offscreen Component)。

还有什么你晓得的React暗藏性能?欢送在评论区探讨~