大家好,我卡颂。
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
隐藏功能?欢迎在评论区讨论~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。