乐趣区

关于前端:聊聊React中的隐藏彩蛋功能

大家好,我卡颂。

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 暗藏性能?欢送在评论区探讨~

退出移动版