亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React Hooks的原理分析

發布時間:2022-04-19 17:39:51 來源:億速云 閱讀:117 作者:zzz 欄目:大數據

今天小編給大家分享一下React Hooks的原理分析的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

0x00  React中的useEffect

在React中有非常多的Hooks,其中useEffect使用非常頻繁,針對一些具有副作用的函數進行包裹處理,使用Hook的收益有:增強可復用性、使函數組件有狀態

數據獲取、訂閱或手動修改DOM都屬于副作用(side effects)。

effect會在React的每次render之后執行,如果是有一些需要同步的副作用代碼,則可以借助useLayoutEffect來包裹,它的用法和useEffect類似

useEffect有兩個參數,第一個傳遞一個函數,第二個參數是作為effect是否執行第一個參數中的函數是否執行的標準,換句話說,第二個參數數組中的變量是否變化來決定函數是否執行,函數是否執行依賴于第二個參數的值是否變化。在React中的比較是一個shallow  equal(淺比較),對于深層次的對象嵌套,無法準確判斷是否發生變化。

useEffect借助了JS的閉包機制,可以說第一個參數就是一個閉包函數,它處在函數組件的作用域中,同時可以訪問其中的局部變量和函數。

多個useEffect串聯,根據是否執行函數(依賴項值是否變化),依次掛載到執行鏈上

在類組件中,有生命周期的概念,在一些講react hooks的文章中常常會看到如何借助useEffect來模擬 componentDidmount和  componentUnmount的例子,其第二個參數是一個空數組[],這樣effect在組件掛載時候執行一次,卸載的時候執行一下return的函數。也同樣是閉包的關系,通過return一個函數,來實現閉包以及在React下次運行effect之前執行該return的函數,用于清除副作用。

0x01 構建React Hooks的心智模型

個人在一開始接觸react  hooks的時候,覺得代碼的執行有點違背常識,在對react構建合理的心智模型花了不少時間。函數組件(Functional  Component)沒有生命周期的概念,React控制更新,頻繁的更新但是值有的會變,有的不變,反而使得程序的可理解性變差了。

不過在后來不斷地學習以及運用之后,我個人覺得hooks其實是一種非常輕量的方式,在項目構建中,開發自定義的hooks,然后在應用程序中任意地方調用hook,類似于插件化(可插拔)開發,降低了代碼的耦合度。但隨之也帶來了一些麻煩的事情,有的同學在一個hook里寫了大量的代碼,分離的effect也冗雜在一起,再加上多維度的變量控制,使得其他同學難以理解這個hook到底在干嘛。

針對hook的內部代碼冗雜的問題,首先得明確當前hook的工作,是否可拆分工作,在hook里可以調用其他的hook,所以是否可以進行多個hook拆分?或者組織(梳理)好代碼的運行邏輯?

  • React中每次渲染都有自己的effect

React中的hooks更新,筆者認為可以把其看作是一個“快照”,每一次更新都是一次“快照”,這個快照里的變量值是不變的,每個快照會因為react的更新而產生串行(可推導的)差異,而effect中的函數每一次都是一個新的函數。

我對于hooks的心智模型,簡單來講,就是一種插件式、有狀態、有序的工具函數。

0x02  useEffect

針對useEffect,React每一次更新都會根據useEffect的第二個參數中依賴項去判斷是否決定執行包裹的函數。

React會記住我們編寫的effect function,effect  function每次更新都會在作用于DOM,并且讓瀏覽器在繪制屏幕,之后還會調用effect function。

整個執行過程可以簡單總結如下:

1.組件被點擊,觸發更新count為1,通知React,“count值更新為1了”

2.React響應,向組件索要count為1的UI

3.組件:

a.給count為1時候的虛擬DOM

b.告知react完成渲染時,記得調用一下effect中的函數() => {document.title = 'you click' + 1 +  'times!'}

4.React通知瀏覽器繪制DOM,更新UI

5.瀏覽器告知ReactUI已經更新到屏幕

6.React收到屏幕繪制完成的消息后,執行effect中的函數,使得網頁標題變成了“you click 1 times!”。

0x03 useRef

假如已經對上面的思想和流程已經爛熟于心,對于“快照”的概念也十分認同。

有時候,我們想在effect中拿到最新的值,而不是通過事件捕獲,官方提供了useRef的hook,useRef在“生命周期”階段是一個“同步”的變量,我們可以將值存放到其current里,以保證其值是最新的。

對于上面描述,為什么說其值是捕獲而不是最新的,可以通過 setState(x => x +  1),來理解。傳入的x是前一個值,x+1是新的值,在一些setTimeout異步代碼里,我們想獲取到最新的值,以便于同步最新的狀態,所以用ref來幫助存儲最新更新的值。

這種打破范式的做法,讓程序有一絲絲的dirty,但確實解決了很多問題,這樣做的好處,也可以表明哪些代碼是脆弱的,是需要依賴時間次序的。

而在類組件中,通過 this.setState() 的做法每次拿到的也是最新的值

0x04 effect的清理

在前面的描述中或多或少涉及到對于effect的清理,只是為了便于一個理解,但描述并不完全準確。

例如下面的例子:

useEffect(() => {   ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);   return () => {     ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);   }; });

假設第一次渲染的時候props是{id: 10},第二次渲染的時候是{id: 20}。你可能會認為發生了下面的這些事:

  • React 清除了 {id: 10}的effect。

  • React 渲染{id: 20}的UI。

  • React 運行{id: 20}的effect。

但是實際情況并非如此,如果按照這種心智模型來理解,那么在清除時候,獲取的值是之前的舊值,因為清除是在渲染新UI之前完成的。這和之前說到的React只會在瀏覽器繪制之后執行effects矛盾。

React這樣做的好處是不會阻塞瀏覽器的一個渲染(屏幕更新)。當然,按照這個規則,effect的清除也被延遲到了瀏覽器繪制UI之后。那么正確的執行順序應該是:

  • React渲染了id 20 的UI

  • React清除了id 10的effect

  • React運行id 20的effect

那么為啥effect里清除的是舊的吶?

  • 組件內的每一個函數(包括事件處理函數,effects,定時器或者API調用等等)會捕獲定義它們的那次渲染中的props和state。

那么,effect的清除并不會讀取到“最新”的props,它只能讀取到定義它那次渲染中props的值

人類發展的進程中淘汰的永遠都是不思進取的守舊派。React中亦是如此思想,或許激進,但大多數人們總期待“新桃換舊符”。

0x05  effect的更新依賴

useEffect中的第二個參數,可以是一個參數數組(依賴數組)。React更新DOM的思想,不管過程怎樣,只將結果展示給世人。

React在更新組件的時候,會對比props,通過AST等方式比較,然后僅需更新變化了的DOM。

第二個參數相當于告訴了useEffect,只要我給你的這些參數任中之一發生了改變,你就執行effect就好了。如此,便可以減少每次render之后調用effect的情況,減少了無意義的性能浪費。

那么在開發過程中,我們會嘗試在組件載入時候,通過api獲取遠程數據,并運用于組件的數據渲染,所以我們使用了如下的一個簡單例子:

useEffect(() => {   featchData(); }, []);

由于是空數組,所以只有在組件掛載(mount)時獲取一遍遠程數據,之后將不再執行。如果effect中有涉及到局部變量,那么都會根據當前的狀態發生改變,函數是每次都會創建(每次都是創建的新的匿名函數)。

function Counter() {   const [count, setCount] = useState(0);    useEffect(() => {     const id = setInterval(() => {       setCount(count + 1);     }, 1000);     return () => clearInterval(id);   }, []);    return <h2>{count}</h2>; }

你可能會認為上面的例子,會在組件加載后,每秒UI上count+1,但實際情況是只會執行一次。為什么吶?是不是覺得有些違反直覺了?

因為,并沒有給effect的依賴項加入count,effect只會在第一次渲染時候,創建了一個匿名函數,盡管通過了setInterval包裹,每秒去執行count  + 1,但是count的值始終是為0,所以在UI表現上永遠渲染的是1。

當然,通過一些規則,我們可以通過加上count來改變其值,或者通過useRef,或者通過setState(x =>  x+1),模式來實現獲取最新的值。例如下面的黑科技操作:

// useRef function Example() {   const [count, setCount] = useState(0);   const countRef = useRef(count);   countRef.current = count; // 假如這一行代碼放到effect函數中會怎么樣吶?可以思考下!   // answer: 在effect中count是effect匿名函數聲明時就有了,值就是0,那么拿到的count值自然也是渲染前(本次props中的值)的count(值為0,再次復盤理解下快照的概念),但由于依賴數組中并不存在任何依賴,所以該匿名函數不會二次執行。   // 但,由于setInterval的原因,函數會不停地setCount,關鍵是其中的參數了,countRef.current = count;取到的值是第一次快照時候的值0,所以其更新的值永遠為0+1 = 1。這樣的結果是符合預期規則的。   // 那為什么放在外面就好了吶?因為countRef.current同步了count的最新值,每次render前就拿到了新的count值,并且賦值給countRef.current,由于ref的同步特性(及時性、統一性),所以循環中獲取的countRef.current也是最新的值,故而能實現計數效果    useEffect(() => {     const id = setInterval(() => {       setCount(countRef.current + 1);     }, 1000);     return () => clearInterval(id);   }, []);    return <h2>{count}</h2>; }  // setState傳入函數 function Example() {   const [count, setCount] = useState(0);    useEffect(() => {     const id = setInterval(() => {       setCount(x => x + 1);  // 傳遞參數為一個函數時候,默認傳遞的第一個參數是之前的值,這是useState的hook在處理     }, 1000);     return () => clearInterval(id);   }, []);    return <h2>{count}</h2>; }  // 使用useReducer function Counter({ step }) {   const [count, dispatch] = useReducer(reducer, 0);    function reducer(state, action) {     if (action.type === 'tick') {       return state + step;     } else {       throw new Error();     }   }    useEffect(() => {     const id = setInterval(() => {       dispatch({ type: 'tick' });     }, 1000);     return () => clearInterval(id);   }, [dispatch]);    return <h2>{count}</h2>; }

上面的做法其實有些自欺欺人了,可以看到如下圖中的log,在setInterval匿名函數中count變量的值并沒有發生改變,這可能會給我們的業務帶來一些風險。

React Hooks的原理分析

demo示例

不過一般情況下,如果不是對業務或程序有充分的了解,我并不建議大家這樣做。

對于依賴,首先得誠實地寫入相關聯的參數,其次,可以優化effect,考慮是否真的需要某參數,是否可以替換?

依賴項中dispatch、setState、useRef包裹的值都是不變的,這些參數都可以在依賴項中去除。

依賴項是函數

可以把函數定義到useEffect中,這樣添加的依賴變成了函數的參數,這樣子,useEffect就無需添加xxx函數名作為依賴項了。

另外如果單純把函數名放到依賴項中,如果該函數在多個effects中復用,那么在每一次render時,函數都是重新聲明(新的函數),那么effects就會因新的函數而頻繁執行,這與不添加依賴數組一樣,并沒有起到任何的優化效果,那么該如何改善吶?

方法一:

如果該函數沒有使用組件內的任何值,那么就把該函數放到組件外去定義,該函數就不在渲染范圍內,不受數據流影響,所以其永遠不變

方法二:

用useCallback hook來包裝函數,與useEffect類似,其第二個參數也是作為函數是否更新的依賴項

0x06 競態

常見于異步請求數據,先發后到,后發先到的問題,這就叫做競態,如果該異步函數支持取消,則直接取消即可

那么更簡單的做法,給異步加上一個boolean類型的標記值,就可以實現取消異步請求

function Article({ id }) {   const [article, setArticle] = useState(null);    useEffect(() => {     let didCancel = false;      async function fetchData() {       const article = await API.fetchArticle(id);       if (!didCancel) {         setArticle(article);       }     }      fetchData();      return () => {       didCancel = true;     };   }, [id]);    // ... }

按照之前的規則,例如id=19,并且獲取數據的時間為30s,變成了id=20,其獲取數據的時間僅需5s,那么執行順序應該如下:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. id=19組件卸載,didCancle=true,當id=19異步請求收到數據時30s后,由于!didCancle ===  false,則不執行數據更新

  3. id=20,因id改變,首先設置了didCancle=false,請求獲取數據,5s后拿到了數據,然后更新數據,最后將更新后數據渲染到屏幕

以上就是“React Hooks的原理分析”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

九江县| 资源县| 若尔盖县| 扎兰屯市| 偃师市| 甘德县| 乐山市| 望城县| 泰顺县| 得荣县| 察隅县| 北宁市| 聂荣县| 靖宇县| 绥德县| 桂阳县| 梁平县| 沙雅县| 斗六市| 开封市| 永寿县| 山阳县| 印江| 大石桥市| 溧阳市| 淮安市| 宝应县| 尚义县| 肃宁县| 成安县| 平远县| 辉县市| 武乡县| 临高县| 托克逊县| 广河县| 丹江口市| 潍坊市| 绥阳县| 宁国市| 汉源县|