詳解React Hooks是如何工作的
1. React Hooks VS 純函數(shù)
React Hook 說白了就是 React V18.6 新增的一些 API,API的本質就是提供某種功能的函數(shù)接口。因此,React Hooks 就是一些函數(shù),但是 React Hooks 不是純函數(shù)。
什么是純函數(shù)呢?就是此函數(shù)在相同的輸入值時,需產生相同的輸出,并且此函數(shù)不能影響到外面的數(shù)據。
簡單理解就是函數(shù)里面不能用到在外面定義的變量,因為如果用到了外面定義的變量,當外面的變量改變時會影響函數(shù)內部的計算,函數(shù)也會影響到外面的變量。
對于 React Hooks 提供的函數(shù) API,恰恰就不是純函數(shù)。
來看一個 useState 的使用語句 const [count, setCount] = useState(0),使用 useState 函數(shù)得到的結果并不是全都一樣的,因為如果 useState(0) 每次得到的結果都是一樣的,那 count 值就永遠不會改變了,那 count 所在的頁面就永遠不會改變,和我們看到的結果就不一樣了。由此可知,React Hooks 都不是純函數(shù),也就是說 Hooks 用到了函數(shù)外的變量。
那么是什么特性讓 React Hooks 一定不能是純函數(shù)呢?實際上是 React 框架和函數(shù)組件本身決定的。我們知道,React 頁面渲染的原理就是通過每次 render 得到新的虛擬 DOM ,然后進行 DOM Diff 來渲染頁面。而 React 的函數(shù)組件是通過執(zhí)行整個函數(shù)得到一個虛擬 DOM。因此在每次頁面渲染 render 時,在函數(shù)組件內部的所有語句都會重新執(zhí)行一次。如果在函數(shù)組件內部使用的 React Hooks 是純函數(shù)的話,就不會在每次渲染后得到不同的虛擬 DOM 了。
React 規(guī)定: 所有 React 組件都必須是純函數(shù),并禁止修改其自身 props 。
因此在 React V16.8 之前 React Hooks 還沒出來的時候,函數(shù)組件因為是純函數(shù),只能返回一個固定的虛擬 DOM,不能包含狀態(tài),也不支持生命周期方法。因此,當時僅僅是支持函數(shù)組件,但函數(shù)組件相比于類組件限制太多,函數(shù)組件無法取代類組件,也沒類組件好用。
React 希望組件是簡單的而不是復雜的,React 認為組件的最佳寫法應該是函數(shù),而不是類。因此 React 就新增了 React Hooks,Hook 就是鉤子的意思,是 React 提供給函數(shù)組件在需要外部功能和數(shù)據狀態(tài)時將其 “鉤” 進去,從而完善函數(shù)組件,使其能完全代替類組件。
React 的函數(shù)組件只能是純函數(shù),那么每次事件發(fā)生時重新 render 函數(shù)組件時得到不同的虛擬 DOM 的事就完全交給了 React Hooks,那么 React Hooks 是如何做到的呢?下面就手動實現(xiàn)一個 useState,useState 的具體細節(jié)肯定不是這樣的,但原理和思路是一樣的。
2. 簡單 myUseState
React.useState 的第一次執(zhí)行是將初始值賦予給一個 _state,之后的每次重新 render 時就是讀取 _state 的值。[state, setState] 中的 setState 做的事就是改變 _state 的值,然后重新渲染頁面。
根據這個原理實現(xiàn) myUseState 函數(shù)如下:
import React from 'react'; import ReactDOM from 'react-dom'; let _state function myUseState(initialValue){ if(_state === undefined){ _state = initialValue } const setState = (newValue)=>{ _state = newValue render() } return [_state, setState] } function render(){ ReactDOM.render(<App/>,document.getElementById('root')); } function App(){ const [n, setN] = myUseState(0) return ( <div> n: {n} <button onClick={() => setN(n+1)}>+1</button> </div> ) } ReactDOM.render(<App/>,document.getElementById('root'));
3. 改進 myUseState
上述實現(xiàn)的 myUseState 存在 bug,當在函數(shù)組件內用到兩次 myUseState 時就會出現(xiàn)問題了,二者共用一個 _state 會出現(xiàn)混亂。
因此需要將上述實現(xiàn)進行改進,改進的思路就是將 _state 定義為一個數(shù)據或者是對象,由于我們在函數(shù)使用時只傳了一個數(shù)值,無法確定鍵值,因此只能使用數(shù)據。改進如下:
import React from 'react'; import ReactDOM from 'react-dom'; let _state = [] let index = 0 function myUseState(initialValue){ const currentIndex = index if(_state[currentIndex] === undefined){ _state[currentIndex] = initialValue } const setState = (newValue)=>{ _state[currentIndex] = newValue render() } index++ return [_state[currentIndex], setState] } function render(){ index = 0 ReactDOM.render(<App/>,document.getElementById('root')); } function App(){ const [n, setN] = myUseState(0) const [m, setM] = myUseState(0) return ( <div> n: {n} <button onClick={() => setN(n+1)}>+1</button> <br/> m: {m} <button onClick={() => setM(m+1)}>+1</button> </div> ) } ReactDOM.render(<App/>,document.getElementById('root'));
4. 實現(xiàn)原理引發(fā)的 Hooks 規(guī)則
上述實現(xiàn)的 myUseState 肯定不是 React.useState 的具體實現(xiàn)代碼,但實現(xiàn)原理是一致的。myUseState 函數(shù)封裝了函數(shù)組件內的數(shù)據狀態(tài),并對該狀態(tài)進行管理,以暴露出相關的操作接口的方式提供給函數(shù)組件使用。
這樣一來,函數(shù)組件就和其數(shù)據狀態(tài)分離了,函數(shù)組件只負責返回虛擬 DOM 本身就可以了,對于數(shù)據狀態(tài)的管理完全交給其 “鉤” 住的 React.useState Hook 就可以了。
從上述的實現(xiàn)思路可以發(fā)現(xiàn),React Hooks 的實現(xiàn)其實是基于 全局變量 和 閉包 原理實現(xiàn)的特殊函數(shù)。
但是,正是因為這樣的實現(xiàn)方式,限制了 React Hooks 的使用必須是 只在頂層調用Hook,意思就是說 不要在循環(huán),條件或嵌套函數(shù)中調用 Hook,如果在 if 條件句中使用了 Hook, 導致組件每次渲染生成時 React.useState 語句的執(zhí)行次數(shù)不對,就會打亂 index 的計數(shù),從而導致數(shù)據維護的錯誤。
上述的實現(xiàn)原理依賴于 index 的正確計數(shù),因此 React 依賴于調用 Hooks 的順序,
以上就是詳解React Hooks是如何工作的的詳細內容,更多關于詳解React Hooks的資料請關注腳本之家其它相關文章!
相關文章
React?中使用?RxJS?優(yōu)化數(shù)據流的處理方案
這篇文章主要為大家介紹了React?中使用?RxJS?優(yōu)化數(shù)據流的處理方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02