我已徹底拿捏 React Compiler,原來(lái)它是元素級(jí)細(xì)粒度更新。原理性能優(yōu)秀實(shí)踐都在這七千字里
說(shuō)實(shí)話現(xiàn)在我很激動(dòng)。
從 React Compiler 開(kāi)源到現(xiàn)在我連續(xù)研究分析 React Compiler 已經(jīng)四天時(shí)間了,這期間我積累了大量的使用心得,整體感受就是它真的太強(qiáng)了!
現(xiàn)在我迫不及待地想跟大家分享 React Compiler 的深度使用體驗(yàn)。
這篇文章我會(huì)結(jié)合三個(gè)實(shí)踐案例為大家解讀 React Compiler 到底強(qiáng)在哪,這可能會(huì)有一點(diǎn)難理解,不過(guò)道友們請(qǐng)放心,我會(huì)做好知識(shí)鋪墊,盡量用更簡(jiǎn)單的方式來(lái)表達(dá)。內(nèi)容梗概如下:
- 如何查看編譯之后的代碼
- Symbol.for() 基礎(chǔ)介紹
- 實(shí)現(xiàn)原理詳細(xì)分析
- 實(shí)踐案例一:counter 遞增
- 實(shí)踐案例二:渲染成本昂貴的子組件
- 實(shí)踐案例三:Tab 切換
- 強(qiáng)悍的性能表現(xiàn):超細(xì)粒度緩存式/記憶化更新
- 項(xiàng)目開(kāi)發(fā)中,最佳實(shí)踐應(yīng)該怎么做
經(jīng)過(guò)驗(yàn)證發(fā)現(xiàn)由于 React19 之前的版本內(nèi)部不包含 compiler-runtime,因此無(wú)法正常使用,我猜測(cè)可能會(huì)在以后提供插件來(lái)支持編譯老版本的項(xiàng)目。目前我是在 React 19 RC 版本中結(jié)合 Compiler。不過(guò)好消息是將項(xiàng)目升級(jí)到 React 19 難度并不高。許多三方庫(kù)也已經(jīng)積極的適配了 React 19。
一、如何查看編譯之后的代碼
通常情況下,你只需要在合適的位置打印一個(gè) log。然后我們就可以通過(guò)下圖所示的位置,在 console 面板中,點(diǎn)擊跳轉(zhuǎn)到編譯之后的代碼。
當(dāng)然,我們可以直接在 Sources 面板中查看。
除此之外,你也可以把代碼拷貝到 React Compiler Playground。這是一個(gè)在線的代碼編譯轉(zhuǎn)換工具。我們可以利用這個(gè)工具方便的將代碼轉(zhuǎn)換成 Compiler 編譯之后的代碼,學(xué)習(xí)非常方便。
React Compiler Playground 的在線地址如下。除此之外,如果你存在任何疑問(wèn),完整的鏈接可以包含你的具體案例,在溝通和交流上非常方便。你可以在 react 的 issue 里看到大量 Compiler 不支持的騷操作。
https://playground.react.dev/
知道了怎么查看編譯之后的代碼之后,那我們就需要看得懂才行。因此接下來(lái)。
二、Symbol.for
我本來(lái)最初的想法是看懂編譯之后的代碼不是很有必要。但是有的時(shí)候會(huì)出現(xiàn)一些情況,程序運(yùn)行的結(jié)果跟我預(yù)想的不一樣。
出現(xiàn)這種迷惑行為的時(shí)候就感覺(jué)賊困惑,為啥會(huì)這樣呢?布吉島 ~,如何調(diào)整我自己的寫(xiě)法呢?也不知道。我很不喜歡這種一臉懵逼的感覺(jué)。
看是得看懂才行。雖然這個(gè)代碼很不像是正常人應(yīng)該去閱讀的代碼。先來(lái)感受一下編譯之后的代碼長(zhǎng)什么樣。
在 Compiler 編譯后的代碼中,有一個(gè)比較少見(jiàn)的語(yǔ)法會(huì)頻繁出現(xiàn):Symbol.for,我先把這個(gè)知識(shí)點(diǎn)科普一下。
Symbol 在 JavaScript 中,是一種基礎(chǔ)數(shù)據(jù)類(lèi)型。我們常常用 Symbol 來(lái)創(chuàng)建全局唯一值。例如,下面兩個(gè)變量,雖然寫(xiě)法是一樣的,但是他們的比較結(jié)果并不相等。
var a = Symbol('hello')
var b = Symbol('hello')
a === b // false
Symbol.for 則不同,Symbol.for 傳入相同字符串時(shí),它不會(huì)重復(fù)創(chuàng)建不同的值。而是在后續(xù)的調(diào)用中,讀取之前已經(jīng)創(chuàng)建好的值。因此下面的代碼對(duì)比結(jié)果為 true。
var a = Symbol.for('for')
var b = Symbol.for('for')
a === b // true
或者我們用另外一種說(shuō)法來(lái)表達(dá)這種創(chuàng)建 -> 讀取的過(guò)程。
// 創(chuàng)建一個(gè) symbol 并放入 symbol 注冊(cè)表中,鍵為 "foo"
Symbol.for("foo");
// 從 symbol 注冊(cè)表中讀取鍵為"foo"的 symbol
Symbol.for("foo");
在 Compiler 編譯后的代碼中,組件依賴 useMemoCache 來(lái)緩存所有運(yùn)算表達(dá)式,包括組件、函數(shù)等。在下面的例子中,useMemoCache 傳入?yún)?shù)為 12,說(shuō)明在該組件中,有 12 個(gè)單位需要被緩存。
在初始化時(shí),會(huì)默認(rèn)給所有的緩存變量初始一個(gè)值。
$ = useMemoCache(12)
for (let $i = 0; $i < 12; $i += 1) {
$[$i] = Symbol.for("react.memo_cache_sentinel");
}
那么,組件就可以根據(jù)緩存值是否等于 Symbol.for 的初始值,來(lái)判斷某一段內(nèi)容是否被初始化過(guò)。如果相等,則沒(méi)有被初始化。
如下:
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <div id="tips">Tab 切換</div>;
$[1] = t1;
} else {
t1 = $[1];
}
三、緩存原理詳細(xì)分析
我們需要重新詳細(xì)解讀一下上面那段代碼。這是整個(gè)編譯原理的核心理論。對(duì)于每一段可緩存內(nèi)容,這里以一個(gè)元素為例。
<div id="tips">Tab 切換</div>
我們會(huì)先聲明一個(gè)中間變量,用于接收元素對(duì)象。
let t1
但是在接收之前,我們需要判斷一下是否已經(jīng)初始化過(guò)。如果沒(méi)有初始化,那么則執(zhí)行如下邏輯,創(chuàng)建該元素對(duì)象。創(chuàng)建完成之后,賦值給 t1,并緩存在 $[1] 中。
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <div id="tips">Tab 切換</div>;
$[1] = t1;
}
如果已經(jīng)初始化過(guò),那么就直接讀取之前緩存在 $[1] 中的值即可。
...
} else {
t1 = $[1];
}
這樣,當(dāng)函數(shù)組件多次執(zhí)行時(shí),該元素組件就永遠(yuǎn)只會(huì)創(chuàng)建一次,而不會(huì)多次創(chuàng)建。
i
這里需要注意的是,判斷成本非常低,但是創(chuàng)建元素的成本會(huì)偏高,因此這種置換是非常劃算的,我們后續(xù)會(huì)明確用數(shù)據(jù)告訴大家判斷的成本
對(duì)于一個(gè)函數(shù)組件中聲明的函數(shù)而言,緩存的邏輯會(huì)根據(jù)情況不同有所變化。這里主要分為兩種情況,一種情況是函數(shù)內(nèi)部不依賴外部狀態(tài),例如:
function __clickHanler(index) {
tabRef.current[index].appeared = true
setCurrent(index)
}
那么編譯緩存邏輯與上面的元素是完全一致的,代碼如下:
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = function __clickHanler(index) {
tabRef.current[index].appeared = true;
setCurrent(index);
};
$[0] = t0;
} else {
t0 = $[0];
}
另外一種情況是有依賴外部狀態(tài),例如:
const [counter, setCounter] = useState(0)
// 此時(shí)依賴 counter,注意區(qū)分他們的細(xì)微差別
function __clickHanler() {
console.log(counter)
setCounter(counter + 1)
}
那么編譯結(jié)果,則只需要把是否重新初始化的判斷條件調(diào)整一下即可。
let t0;
if ($[0] !== counter) {
t0 = function __clickHanler() {
console.log(counter);
setCounter(counter + 1);
};
$[0] = counter;
$[1] = t0;
} else {
t0 = $[1];
}
這樣,當(dāng) counter 發(fā)生變化,t0 就會(huì)重新賦值,而不會(huì)采用緩存值,從而完美的繞開(kāi)了閉包問(wèn)題。
除此在外,無(wú)論是函數(shù)、還是組件元素的緩存判斷條件,都會(huì)優(yōu)先考慮外部條件,使用 Symbol.for 來(lái)判斷時(shí),則表示沒(méi)有其他任何值的變化會(huì)影響到該緩存結(jié)果。
例如,一個(gè)組件元素如下所示:
<button notallow={__clickHanler}>counter++</button>
此時(shí)它的渲染結(jié)果受到 __clickHanler 的影響,因此,判斷條件則不會(huì)使用 Symbol.for,編譯結(jié)果如下:
let t2;
if ($[3] !== __clickHanler) {
t2 = <button onClick={__clickHanler}>counter++</button>;
$[3] = __clickHanler;
$[4] = t2;
} else {
t2 = $[4];
}
又例如下面這個(gè)元素組件,他的渲染結(jié)果受到 counter 的影響。
<div className="counter">
counter: {counter}
</div>
因此,它的編譯結(jié)果為:
let t3;
if ($[5] !== counter) {
t3 = <div className="counter">counter: {counter}</div>;
$[5] = counter;
$[6] = t3;
} else {
t3 = $[6];
}
對(duì)于這樣的編譯細(xì)節(jié)的理解至關(guān)重要。在以后的開(kāi)發(fā)中,我們就可以完全不用擔(dān)心閉包問(wèn)題而導(dǎo)致程序出現(xiàn)你意想不到的結(jié)果了。
所有的可緩存對(duì)象,全部都是這個(gè)類(lèi)似的邏輯。他的粒度細(xì)到每一個(gè)函數(shù),每一個(gè)元素。這一點(diǎn)意義非凡,它具體代表著什么,我們?cè)诤罄m(xù)聊性能優(yōu)化的時(shí)候再來(lái)明確。
不過(guò)需要注意的是,對(duì)于 map 的循環(huán)語(yǔ)法,在編譯結(jié)果中,緩存的是整個(gè)結(jié)果,而不是渲染出來(lái)的每一個(gè)元素。
{tabs.map((item, index) => {
return (
<item.component
appearder={item.appeared}
key={item.title}
selected={current === index}
/>
)
})}
編譯結(jié)果表現(xiàn)如下:
let t4;
if ($[7] !== current) {
t4 = tabs.map((item_0, index_1) => (
<item_0.component
appearder={item_0.appeared}
key={item_0.title}
selected={current === index_1}
/>
));
$[7] = current;
$[8] = t4;
} else {
t4 = $[8];
}
?
對(duì)這種情況的了解非常重要,因?yàn)橛械臅r(shí)候我們需要做更極限的性能優(yōu)化時(shí),map 循環(huán)可能無(wú)法滿足我們的需求。因?yàn)榇藭r(shí)循環(huán)依然在執(zhí)行,后面的案例中我們會(huì)更具體的分析 Map 的表現(xiàn)
目前這個(gè)階段,我們最主要的是關(guān)心程序執(zhí)行邏輯與預(yù)想的要保持一致,因此接下來(lái),我們利用三個(gè)案例,來(lái)分析編譯之后的執(zhí)行過(guò)程。
四、實(shí)踐案例一:counter 遞增
通過(guò)上面對(duì) Compiler 渲染結(jié)果的理解,我們應(yīng)該已經(jīng)大概知道下面這段代碼最終會(huì)渲染成什么樣。我們目前要思考的問(wèn)題就是,這個(gè)例子,編譯之后,收益表現(xiàn)在哪里?
function Index() {
const [counter, setCounter] = useState(0)
function __clickHanler() {
console.log(counter)
setCounter(counter + 1)
}
return (
<div>
<div id='tips'>基礎(chǔ)案例,state 遞增</div>
<button onClick={__clickHanler}>counter++</button>
<div className="counter">counter: {counter}</div>
</div>
)
}
一起來(lái)分析一下,當(dāng)我們點(diǎn)擊按鈕時(shí),此時(shí) counter 增加,因此 __clickHanler 無(wú)法緩存,需要重新創(chuàng)建,那么 button 按鈕和 counter 標(biāo)簽都無(wú)法緩存。
此時(shí),只有 tips 元素可以被緩存。但是 tips 元素本身是一個(gè)基礎(chǔ)元素,在原本的邏輯中,經(jīng)歷一個(gè)簡(jiǎn)單的判斷就能知道不需要重新創(chuàng)建節(jié)點(diǎn)因此本案例的編譯之后收益非常有限。
編譯代碼結(jié)果如下:
function Index() {
const $ = _c(10);
const [counter, setCounter] = useState(0);
let t0;
if ($[0] !== counter) {
t0 = function __clickHanler() {
console.log(counter);
setCounter(counter + 1);
};
$[0] = counter;
$[1] = t0;
} else {
t0 = $[1];
}
const __clickHanler = t0;
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <div id="tips">基礎(chǔ)案例,state 遞增</div>;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] !== __clickHanler) {
t2 = <button onClick={__clickHanler}>counter++</button>;
$[3] = __clickHanler;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== counter) {
t3 = <div className="counter">counter: {counter}</div>;
$[5] = counter;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] !== t2 || $[8] !== t3) {
t4 = (
<div>
{t1}
{t2}
{t3}
</div>
);
$[7] = t2;
$[8] = t3;
$[9] = t4;
} else {
t4 = $[9];
}
return t4;
}
五、實(shí)踐案例二:昂貴的子組件
在上面一個(gè)例子的基礎(chǔ)之上,我們新增一個(gè)子組件。該子組件的渲染非常耗時(shí)。
function Expensive() {
var cur = performance.now()
while (performance.now() - cur < 1000) {
// block 1000ms
}
console.log('hellow')
return (
<div>我是一個(gè)耗時(shí)組件</div>
)
}
父組件中引入該子組件,其他邏輯完全一致。
function Index() {
const [counter, setCounter] = useState(0)
function __clickHanler() {
setCounter(counter + 1)
}
return (
<div>
<div id='tips'>基礎(chǔ)案例,state 遞增</div>
<button notallow={__clickHanler}>counter++</button>
<div className="counter">counter: {counter}</div>
+ <Expensive />
</div>
)
}
我們?cè)谥啊窻eact 知命境」的學(xué)習(xí)中,對(duì)于性能優(yōu)化已經(jīng)有非常深厚的積累。因此我們知道,在這種情況之下,由于父組件的狀態(tài)發(fā)生了變化,導(dǎo)致子組件 Expensive 會(huì)在 counter 遞增時(shí)重復(fù)執(zhí)行。從而導(dǎo)致頁(yè)面渲染時(shí)非常卡頓。
編譯之后,針對(duì)這一段邏輯的優(yōu)化代碼如下:
let t4;
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
t4 = <Expensive />;
$[7] = t4;
} else {
t4 = $[7];
}
正如代碼所表達(dá)的一樣,由于這一個(gè)組件,并沒(méi)有依賴任何外部狀態(tài),因此只需要在初始化時(shí)賦值一次即可。后續(xù)直接使用緩存值。
因此,在這個(gè)案例中,Compiler 編譯之后的優(yōu)化效果非常明顯,收益巨大。
六、實(shí)踐案例三:Tab 切換
這個(gè)案例會(huì)非常的復(fù)雜,經(jīng)驗(yàn)稍微欠缺一點(diǎn)的前端開(kāi)發(fā)可能都實(shí)現(xiàn)不了。我們先來(lái)看一下我想要實(shí)現(xiàn)的演示效果。
從演示效果上來(lái)看,這是一個(gè)普通的 tab 切換。但是先別急,我還有要求。我希望能實(shí)現(xiàn)極限的性能優(yōu)化。
- 我希望首次渲染時(shí),頁(yè)面渲染更少的內(nèi)容,因此此時(shí),只能先渲染默認(rèn)的 Panel。其他 Panel 需要在點(diǎn)擊對(duì)應(yīng)的按鈕時(shí),才渲染出來(lái)。
- 在切換過(guò)程中,我希望能夠緩存已經(jīng)渲染好的 Panel,只需要在樣式上做隱藏,而不需要在后續(xù)的交互中重復(fù)渲染內(nèi)容
- 當(dāng)四個(gè)頁(yè)面都渲染出來(lái)之后,再做切換時(shí),此時(shí)只會(huì)有兩個(gè)頁(yè)面會(huì)發(fā)生變化,上一個(gè)選中的頁(yè)面與下一個(gè)選中的頁(yè)面。另外的頁(yè)面不參與交互,則不應(yīng)該 re-render。
?
這個(gè)案例和要求不算特別難,但是對(duì)綜合能力的要求還是蠻高的,大家有空可以自己嘗試實(shí)現(xiàn)一下,看看能不能完全達(dá)到要求。
具體的完整實(shí)現(xiàn)我們會(huì)在后續(xù)的直播中跟大家分享。大家可以加我好友「icanmeetu」然后進(jìn) React19 討論群,React19 相關(guān)的直播消息會(huì)第一時(shí)間在群內(nèi)公布。
這里,我主要想跟大家分享的就是 map 方法的小細(xì)節(jié)。有如下代碼:
{tabs.map((item, index) => {
return (
<item.component
appearder={item.appeared}
key={item.title}
selected={current === index}
/>
)
})}
它的編譯結(jié)果表現(xiàn)如下:
let t4;
if ($[7] !== current) {
t4 = tabs.map((item_0, index_1) => (
<item_0.component
appearder={item_0.appeared}
key={item_0.title}
selected={current === index_1}
/>
));
$[7] = current;
$[8] = t4;
} else {
t4 = $[8];
}
我們會(huì)發(fā)現(xiàn),此時(shí)編譯緩存的是整個(gè) map 表達(dá)式,但是由于 map 表達(dá)式又依賴于 current,因此,在我們點(diǎn)擊切換的交互過(guò)程中,每一次的 current 都會(huì)發(fā)生變化,那么這里針對(duì) map 表達(dá)式的緩存就沒(méi)有了任何意義。
但是實(shí)際上,我們可以觀察到,我們有 4 個(gè) Panel,點(diǎn)擊切換的交互發(fā)生時(shí),實(shí)際上只有兩個(gè) Pannel 發(fā)生了變化。因此,最極限的優(yōu)化是,只有這兩個(gè)組件對(duì)應(yīng)的函數(shù)需要重新 re-render,那么我們的代碼應(yīng)該怎么寫(xiě)呢?
其實(shí)非常簡(jiǎn)單,那就是不用 map,將數(shù)組拆開(kāi)直接手寫(xiě),代碼如下:
let c1 = tabRef.current[0]
let c2 = tabRef.current[1]
let c3 = tabRef.current[2]
let c4 = tabRef.current[3]
<c1.component appearder={c1.appeared} selected={current === 0}/>
<c2.component appearder={c2.appeared} selected={current === 1}/>
<c3.component appearder={c3.appeared} selected={current === 2}/>
<c4.component appearder={c4.appeared} selected={current === 3}/>
然后,我們就會(huì)發(fā)現(xiàn),在編譯結(jié)果中,不再緩存 map 表達(dá)式的結(jié)果,而是緩存每一個(gè)組件。
let t5;
if ($[7] !== c1.component || $[8] !== c1.appeared || $[9] !== t4) {
t5 = <c1.component appearder={c1.appeared} selected={t4} />;
$[7] = c1.component;
$[8] = c1.appeared;
$[9] = t4;
$[10] = t5;
} else {
t5 = $[10];
}
?
這樣做的收益在特定場(chǎng)景下的收益將會(huì)非常高。
七、強(qiáng)悍的性能:細(xì)粒度記憶化更新
經(jīng)過(guò)上面的學(xué)習(xí),想必各位道友對(duì) React Compiler 的工作機(jī)制已經(jīng)有了非常深刻的理解。此時(shí),我們就需要分析一下,這樣的記憶化更新機(jī)制,到底有多強(qiáng)。
首先明確一點(diǎn),和 Vue 等其他框架的依賴收集不同,React Compiler 依然不做依賴收集。
React 依然通過(guò)從根節(jié)點(diǎn)自上而下的 diff 來(lái)找出需要更新的節(jié)點(diǎn)。在這個(gè)過(guò)程中,我們會(huì)通過(guò)大量的判斷來(lái)決定使用緩存值??梢悦鞔_的是,Compiler 編譯之后的代碼,緩存命中的概率非常高,幾乎所有應(yīng)該緩存的元素和函數(shù)都會(huì)被緩存起來(lái)。
因此,React Compiler 也能夠在不做依賴收集的情況下,做到元素級(jí)別的超級(jí)細(xì)粒度更細(xì)。但是,這樣做的代價(jià)就是,React 需要經(jīng)歷大量的判斷來(lái)決定是否需要使用緩存結(jié)果。
所以這個(gè)時(shí)候,我們就需要明確,我所謂的大量判斷的時(shí)間成本,到底有多少?它會(huì)不會(huì)導(dǎo)致新的性能問(wèn)題?
可以看到,Compiler 編譯之后的代碼中,幾乎所有的比較都是使用了全等比較,因此,我們可以寫(xiě)一個(gè)例子來(lái)感知一下,超大量的全等比較到底需要花費(fèi)多少時(shí)間。
測(cè)試代碼如下:
var cur = performance.now()
for(let i = 0; i < 1000000; i++) {
'xxx' == 'xx'
}
var now = performance.now()
console.log(now - cur)
執(zhí)行結(jié)果,比較 100 萬(wàn)次,只需要花費(fèi)不到 1.3 毫秒。這太強(qiáng)了啊。我們很難有項(xiàng)目能夠達(dá)到 1000,000 次的比較級(jí)別,甚至許多達(dá)到 10000 都難。那也就意味著,這里大量的比較成本,落實(shí)到你的項(xiàng)目中,幾乎可以忽略不計(jì)。
為了對(duì)比具體的效果,我們可以判斷一下依賴收集的時(shí)間成本。
首先是使用數(shù)組來(lái)收集依賴。依然是 100 萬(wàn)次收集,具體執(zhí)行結(jié)果如下。耗時(shí) 8 毫秒。
使用 Map 來(lái)收集依賴。100 萬(wàn)次依賴收集耗時(shí) 54 ms。
使用 WeakMap 來(lái)收集依賴,那就更慢了。100萬(wàn)次依賴收集耗時(shí) 200 毫秒。
?
WeakMap 的 key 不能是一個(gè) number 類(lèi)型。
數(shù)據(jù)展示給大家了,具體強(qiáng)不強(qiáng),大家自行判斷。
?
這里我要明確的是,這樣的性能表現(xiàn),在之前版本的項(xiàng)目中,合理運(yùn)用 useCallback/memo 也能做到。只是由于對(duì) React 底層默認(rèn)命中規(guī)則不理解,導(dǎo)致大多數(shù)人不知道如何優(yōu)化到這種程度。React Compiler 極大的簡(jiǎn)化了這個(gè)過(guò)程。
八、React Compiler 最佳實(shí)踐
有許多騷操作,React Compiler 并不支持,例如下面這種寫(xiě)法。
{[1, 2, 3, 4, 5].map((counter) => {
const [number, setNumber] = useState(0)
return (
<div key={`hello${counter}`} onClick={() => setNumber(number + 1)}>
number: {number}
</div>
)
})}
這個(gè)操作騷歸騷,但是真的有大佬想要這樣寫(xiě)。React 之前的版本依然不支持這種寫(xiě)法。不過(guò)好消息是,React 19 支持了...
但是 React Compiler 并不支持。對(duì)于這些不支持的語(yǔ)法,React Compiler 的做法就是直接跳過(guò)不編譯,而直接沿用原組件寫(xiě)法。
因此,React Compiler 的最佳實(shí)踐我總結(jié)了幾條
- 1、不再使用 useCallback、useMemo、Memo 等緩存函數(shù)
- 2、丟掉閉包的心智負(fù)擔(dān),放心使用即可
- 3、引入嚴(yán)格模式
- 4、在你不熟悉的時(shí)候引入 eslint-plugin-react-compiler
- 5、當(dāng)你熟練之后,棄用它,因?yàn)橛械臅r(shí)候我們就是不想讓它編譯我們的組件
- 6、更多的使用 use 與 Action 來(lái)處理異步邏輯
- 7、盡可能少地使用 useEffect
這里,一個(gè)小小的彩蛋就是,當(dāng)你不希望你的組件被 Compiler 編譯時(shí),你只需要使用 var 來(lái)聲明狀態(tài)即可。因?yàn)檫@不符合它的語(yǔ)法規(guī)范
var [counter, setCounter] = useState(0)
而你改成 const/let,它就會(huì)又重新編譯該組件??煽匦耘c自由度非常高。