簡單回答就是 生命週期,但在脆上有批 React 仔永遠當它不存在的在賣課,確實也蠻擔心的,但你深入到一定的程度,就會發現那是一個賣課的話術。
經歷我們前面的實作,你應該也能深刻體會到生命週期的意思,這篇就來代你釐清三個重點:
createEffect
、signal.set
),正確放在哪裡。TL;DR
- 在 React 裡,render 必須純(Function Component 的概念):不要在 render 期做副作用或寫入 signals。
- 副作用分工:
- UI 相關(DOM 量測、動畫)→
useLayoutEffect
/useEffect
- 資料流相關(依信號值觸發業務、請求)→ signals 的
createEffect
(透過適配器管理生命週期)- 訂閱一律走
useSyncExternalStore
(下一篇會寫),避免 tearing。
scheduler
在 microtask 時間點合併重跑 effect
。createEffect
;交給 hook 幫你管。useSyncExternalStore
做 快照 + 訂閱,React 能在 commit 前重取快照,防 tearing。createEffect
function Bad() {
// render 期創建外部 effect,破壞純度與可預測性
createEffect(() => {
console.log("value", someSignal.get());
});
return <>{/**...你的UI */}</>;
}
signal.set
function Bad() {
const v = someSignal.get();
if (v < 0) someSignal.set(0); // render 期間寫入,會導致無窮重渲染
return <>{/**...你的UI */}</>;
}
get()
值塞進閉包長期使用function Bad() {
const v = someSignal.get(); // 這是當下快照
const onClick = () => console.log(v); // 之後永遠印舊值
return <button onClick={onClick}>log</button>;
}
useSyncExternalStore
包一個 useSignalValue(src)
。
peek()
(不建立 React→signal 依賴,但在 stale 時會 lazy 重算)。createEffect
監聽來源變動,並在清理時解除。useLayoutEffect
/ useEffect
;把 signals 的值傳給 React,由 React 掌控 DOM 時機。這邊先不實作,給個概念讓大家先思考。
React 在事件處理內會自動批次 setState
;我們的 batch
只影響 signals 的 effect 合併重跑,兩者互不衝突。
範例:
batch(() => {
a.set(10)
b.set(20)
a.set(30)
});
// our effect 只有在 microtask 重跑一次
// React 如果有 setState 也會在事件內批次 render 一次
事情 | 放哪裡 | 說明 |
---|---|---|
讀寫 DOM、量測、動畫 | useLayoutEffect / useEffect |
由 React 的 Commit 管理時機 |
根據信號值觸發業務副作用 | createEffect (透過 hook 訂閱) |
我們的 scheduler 會合併重跑 |
多次更新只想重跑一次 | batch 或 transaction |
影響 signals 的 effect,不影響 React 的 commit |
讓 React 訂閱外部狀態 | useSyncExternalStore |
避免 tearing,支援 Concurrent |
測試或示範想立刻重跑 our effects | flushSync() (我們的) |
立刻清空 scheduler 佇列 |
function Bad() {
const [state, setState] = useState();
const v = someSignal.get();
useEffect(() => {
// 這樣訂閱常會 tearing 或重複更新
const stop = createEffect(() => {
someSignal.get();
setState(Date.now());
});
return () => stop();
}, []);
return <div>{v}</div>; // v 不是 React 管控的快照,要使用 state 做封包
}
useSyncExternalStore
取得快照function Good() {
// 下一篇會實作這個hook
const v = useSignalValue(someSignal);
return <div>{v}</div>; // v 是 React 管控的同步快照,透過custom hook的方式整理
}
有了上述的概念,你應該對 React 的狀態管理和更新機制,有了比較正確的認知。
我們的目的不是要取代 React 的狀態,而是教你如何把 signals 當外部資料源,在 Concurrent 模式下不撕裂、少重跑、行為可預期。
下一篇,我們來實作能在 React 環境下正確使用前面建立的 signal 系統。