共计 2760 个字符,预计需要花费 7 分钟才能阅读完成。
大家好,我卡颂。
React
的代码量能够说是相当宏大。在如此宏大的库中是否存在 文档中未提及,然而理论存在的性能 呢?
答案是必定的。
本文将向你介绍 3 个文档中未提及的暗藏彩蛋性能。
欢送退出人类高质量前端交换群,带飞
ref cleanup
在以后 React
中,Ref
存在两种数据结构:
<T>(instance: T) => void
{current: T}
对于大部分需要,咱们会应用第二种数据结构。同时,他也是 useRef
、createRef
返回的数据结构。
第一种数据结构次要用于 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 Component
,React
会将对应函数组件(上例中的 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 左右:
咒语为什么会起作用呢?
在 React
、ReactDOM
中都存在变量__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
。
相似的,更新 相干数据也须要在 React
与ReactDOM
间共享,其中就包含 —— 更新是否是并发更新。
当咱们赋值 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
,比方相似Vue
中Keep-Alive
的Offscreen Component
。
以后要想体验 Offscreen Component
只能通过 Suspense
间接体验(Suspense
可能在 pending
与挂载组件间切换就是利用Offscreen Component
)。
还有什么你晓得的 React
暗藏性能?欢送在评论区探讨~