自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React useEvent:磚家說的沒問題

開發(fā) 前端
在 React 18 之前,因為沒有 concurrent,所以 useMemoizedFn 不會有任何問題。在 React 18 之后,我目前也沒看到有什么問題。不過為了穩(wěn)妥起見,后面 ahooks 的 useMemoizedFn 會做一次升級,向官方的 useEvent 看齊。

之前寫了一篇文章《React Hooks 使用誤區(qū),駁官方文檔[1]》,文中拋出了兩個觀點:

  • 不是所有的依賴都必須放到依賴數(shù)組中
  • deps 參數(shù)不能緩解閉包問題

這兩個觀點引起了劇烈的討論,當然大多數(shù)人還是持反對意見的,甚至質(zhì)疑我不會用 Hooks,(⊙o⊙)… 我想說我寫的 Hooks 比你吃的鹽都多(開玩笑 ?? ~)。

然后呢,知乎上來了個提問《如何看待《React Hooks 使用誤區(qū),駁官方文檔》?[2]》,大家依舊是討論激烈,甚至 #黃玄 大佬也親自來回答了。

很多同學極力反對我的觀點,剛開始我還想一爭高下,后來實在沒精力一個一個對線。

這不,React 官方來幫我助陣了?React 官方為啥出 useEvent?就是發(fā)現(xiàn)以前要求的依賴寫法,實在有太大問題,不加一個新的 API,官方示例都沒法寫了 ??。

以前一直覺得 React Hooks 教程,包括 Dan 寫的 useEffect 教程[3],都只是寫了基礎(chǔ)場景,對于稍微復(fù)雜點的場景,都避而不談。因為這些復(fù)雜場景,在之前的規(guī)則下,確實是沒法玩。

什么是 useEvent

關(guān)于 useEvent 是什么,官方 RFC[4] 文檔有非常詳細的解釋,并且目前社區(qū)上也有非常多的文章介紹(其實很多介紹都是有問題的)。接下來用一個官方文檔上的一個例子,來認識一下 useEvent。需求很簡單,我們希望 url 變化的時候,上報下當前 url和 username。

function Page({ route, currentUser }) {
useEffect(() => {
logAnalytics('visit_page', route.url, currentUser.name);
}, [route.url]);
// ...
}

如上代碼,會有 warning,告訴我們 currentUser.name要放到 deps 中。修正后代碼是這樣:

function Page({ route, currentUser }) {
useEffect(() => {
logAnalytics('visit_page', route.url, currentUser.name);
}, [route.url, currentUser.name]);
// ...
}

但這樣明顯滿足不了我們的業(yè)務(wù)需求,因為 currentUser.name變化后,也觸發(fā)了上報請求。

很多杠精就問,為啥你的需求要這樣設(shè)計?為啥 currentUser.name變化后不要上報?你的需求不合理吧?這個你去問 dan 吧!

以前的解決方案可能有兩個:

  • 忽略警告,把 eslint-plugin-react-hooks卸載掉
  • 通過 ref 來標記 currentUser.name
function Page({ route, currentUser }) {
const ref = useRef(currentUser.name);
ref.current = currentUser.name;

useEffect(() => {
logAnalytics('visit_page', route.url, ref.current);
}, [route.url]);
// ...
}

兩個方案都有缺點:

  • 打破了所謂的 React 對 deps 的限制規(guī)則
  • 寫法太麻煩,項目復(fù)雜后要定義無數(shù)個 ref

基于 useEvent 改造起來就很簡單了。

function Page({ route, currentUser }) {
// ? Stable identity
const onVisit = useEvent(visitedUrl => {
logAnalytics('visit_page', visitedUrl, currentUser.name);
});

useEffect(() => {
onVisit(route.url);
}, [route.url]); // ? Re-runs only on route change
// ...
}

useEvent 會將一個函數(shù)「持久化」,同時可以保證函數(shù)內(nèi)部的變量引用永遠是最新的。如果你用過 ahooks 的 useMemoizedFn,實現(xiàn)的效果是幾乎一致的。再強調(diào)下 useEvent 的兩個特性:

  • 函數(shù)地址永遠是不變的
  • 函數(shù)內(nèi)引用的變量永遠是最新的

useEvent 可以用來代替 useCallback,以前這樣寫,在 text 變化的時候,函數(shù)地址會變化。

function Chat() {
const [text, setText] = useState('');

// ?? A different function whenever `text` changes
const onClick = useCallback(() => {
sendMessage(text);
}, [text]);

return <SendButton onClick={onClick} />;
}

通過 useEvent 代替 useCallback 后,不用寫 deps 函數(shù)了,并且函數(shù)地址永遠是固定的,text也永遠是最新的。

function Chat() {
const [text, setText] = useState('');

// ? Always the same function (even if `text` changes)
const onClick = useEvent(() => {
sendMessage(text);
});

return <SendButton onClick={onClick} />;
}

useEvent 是怎么實現(xiàn)的

useEvent 的實現(xiàn)原理比較簡單,但現(xiàn)在看到的社區(qū)上的介紹文章幾乎都有問題。

// (!) Approximate behavior

function useEvent(handler) {
const handlerRef = useRef(null);

// In a real implementation, this would run before layout effects
useLayoutEffect(() => {
handlerRef.current = handler;
});

return useCallback((...args) => {
// In a real implementation, this would throw if called during render
const fn = handlerRef.current;
return fn(...args);
}, []);
}

上面的代碼是官方提供的一個示例代碼,需要重點注意這句注釋 In a real implementation, this would run before layout effects,翻譯過來就是 “在真實的實現(xiàn)中,這里用的 Hooks 執(zhí)行時機在 useLayoutEffect之前”。

這里一定是不能用 useLayoutEffect來更新 ref的,因為子組件的 useLayoutEffect比父組件的執(zhí)行更早,如果這樣用的話,子組件的 useLayoutEffect中訪問到的 ref一定是舊的。

所以官方為了實現(xiàn) useEvent,一定是要加一個在 useLayoutEffect執(zhí)行的 Hooks 的,并且這個 Hooks 應(yīng)該不會開放給普通用戶使用的。

另外 React 要求不要在 render 中直接調(diào)用 useEvent返回的函數(shù),原理也是一樣的,在 render 中訪問的函數(shù)一定是舊的,因為 useLayoutEffect還沒執(zhí)行呢。

useMemoizedFn 和 useEvent 的差異

在 React 18 之前,社區(qū)上有很多類似 useEvent 的實現(xiàn),比如 ahooks[5] 的 useMemoizedFn,類似下面這樣:

function useMemoizedFn(fn) {

const fnRef = useRef(fn);
fnRef.current = useMemo(() => fn, [fn]);

return useCallback((...args) => {
return fnRef.current.apply(args);
}, []);
}

之前很多同學問,為啥不用 useLayoutEffect,是不是有問題?現(xiàn)在應(yīng)該明白了吧?我們需要一個比useLayoutEffect執(zhí)行更早的 Hooks,很遺憾的是之前更沒有,所以只能放到 render 中。

為什么之前官方?jīng)]有提供類似的 Hooks?useMemoizedFn 有問題嗎?之前 React Issue #16956[6] 上對類似的封裝做了很多討論,官方的態(tài)度一直是 “在 concurrent 下可能會存在問題” ,也就是官方也吃不準未來會不會出問題。隨著 React 18 發(fā)布,concurrent 模式穩(wěn)定之后,官方發(fā)現(xiàn),這種寫法不會有問題,索性就自己提供了一個。

在 React 18 之前,因為沒有 concurrent,所以 useMemoizedFn 不會有任何問題。在 React 18 之后,我目前也沒看到有什么問題。不過為了穩(wěn)妥起見,后面 ahooks 的 useMemoizedFn 會做一次升級,向官方的 useEvent 看齊。

最后用知乎上一個同學的評論結(jié)尾“面多了加水,水多了加面”。

參考資料

[1]React Hooks 使用誤區(qū),駁官方文檔: https://zhuanlan.zhihu.com/p/450513902

[2]如何看待《React Hooks 使用誤區(qū),駁官方文檔》?: https://www.zhihu.com/question/508780830

[3]useEffect 教程: https://overreacted.io/a-complete-guide-to-useeffect/

[4]RFC: https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md

[5]ahooks: https://github.com/alibaba/hooks[6]#16956: https://github.com/facebook/react/issues/16956

責任編輯:武曉燕 來源: 前端技術(shù)磚家
相關(guān)推薦

2015-09-06 14:27:11

大數(shù)據(jù)專家忽悠

2021-07-02 05:31:53

ReactSolidJS前端

2021-04-25 09:00:14

項目互聯(lián)網(wǎng)上線

2021-03-01 09:16:10

程序員系統(tǒng)模式

2021-03-07 22:38:10

Git 架構(gòu)代碼

2023-01-11 14:57:10

2011-12-26 17:51:47

2011年度IT博客大IT博客大賽博客

2009-11-02 11:30:44

金山大腳

2021-03-02 11:31:51

技術(shù)資訊

2021-04-28 11:35:06

Java框架日志

2016-01-25 09:35:23

測試程序

2023-06-09 07:21:23

React前端框架

2015-11-18 13:54:41

網(wǎng)易段子

2018-02-27 15:22:12

裝機內(nèi)存容量

2020-01-15 08:06:28

HTTP超文本傳輸協(xié)議網(wǎng)絡(luò)協(xié)議

2020-02-03 17:22:34

垃圾回收原理種類

2020-03-14 09:17:55

HTTPS網(wǎng)絡(luò)協(xié)議HTTP

2018-07-18 10:30:29

云存儲

2012-01-09 09:51:04

2011年度IT博客大IT博客大賽2011年度十大杰出I

2020-05-15 11:14:58

操作系統(tǒng)面試官運行
點贊
收藏

51CTO技術(shù)棧公眾號