理解 React Hooks 心智模型:必須按順序、不能在條件語句中調用的規則

前言

自從 React 推出 hooks 的 API 後,相信你們對新 API 都很喜歡,可是它對你如何使用它會有一些奇怪的限制。好比,React 官網介紹了 Hooks 的這樣一個限制:javascript

不要在循環,條件或嵌套函數中調用 Hook, 確保老是在你的 React 函數的最頂層以及任何 return 以前調用他們。遵照這條規則,你就能確保 Hook 在每一次渲染中都按照一樣的順序被調用。這讓 React 可以在屢次的 useState 和 useEffect 調用之間保持 hook 狀態的正確。java

useState Hook 的工做原理

這個限制並非 React 團隊憑空造出來的,的確是因爲 React Hooks 的實現設計而不得已爲之。react

爲了讓你們有一個更清晰的思惟模型,我將用數組來模擬useState的簡單實現。數組

首先讓咱們經過一個例子來看看 hook 是如何工做的。函數

咱們首先從一個組件開始:spa

function RenderFunctionComponent() {
    const [firstName, setFirstName] = useState("Rudi");
    const [lastName, setLastName] = useState("Yardley");

    return (
        <Button onClick={() => setFirstName("Fred")}>Fred</Button>
    );
}

  

useState hook 背後的思想是,你可使用 hook 函數返回的數組的第二個數組項做爲 setter 函數,而且該 setter 將控制由 hook 管理的狀態。設計

具體實現

1)初始化

建立兩個空數組:setters 和 statecode

將 cursor 設置爲 0blog

2)第一次渲染

首次運行組件函數。事件

每次useState()調用,在第一次運行時,都會將一個 setter 函數推送到 setters 數組上,而後將一些狀態推送到 state 數組上。

3)後續渲染

每次後續渲染都會重置 cursor,而且僅從每一個數組中讀取這些值。

4)事件處理

每一個 setter 都有對其 cursor 的引用,所以經過觸發對 setter 的調用,setter 它將更改狀態數組中該位置的狀態值。

簡單代碼實現

下面經過一段簡單的代碼示例來演示該實現。

注意:這並非 React 的底層實現,但對於咱們理解 react hook 的心智模型很是有幫助。

const state = [];
const setters = [];
let cursor = 0;

function createSetter(cursor) {
    return function setterWithCursor(newVal) {
        state[cursor] = newVal;
    };
}

export function useState(initVal) {
    if (state[cursor] === undefined && setters[cursor] === undefined) {
        state.push(initVal);
        setters.push(createSetter(cursor));
    }

    const setter = setters[cursor];
    const value = state[cursor];

    cursor++;
    return [value, setter];
}

function RenderFunctionComponent() {
    const [firstName, setFirstName] = useState('Rudi'); // cursor: 0
    const [lastName, setLastName] = useState('Yardley'); // cursor: 1

    return (
        <div>
            <button onClick={() => setFirstName('Richard')}>Richard</button>
            <button onClick={() => setLastName('Fred')}>Fred</button>
        </div>
    );
}

function MyComponent() {
    cursor = 0; // resetting the cursor
    return <RenderFunctionComponent />; // render
}

console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']

// click the 'Richard' button

console.log(state); // After-click: ['Richard', 'Yardley']

爲何順序很重要

如今,若是咱們根據某些外部因素甚至組件狀態更改渲染週期的鉤子順序會發生什麼?

讓咱們作 React 團隊說你不該該作的事情:

let firstRender = true;

function RenderFunctionComponent() {
  let initName;

  if(firstRender){
    [initName] = useState("Rudi");
    firstRender = false;
  }
  const [firstName, setFirstName] = useState(initName);
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}  

這裏咱們有useState的一個條件調用。讓咱們看看這對系統形成的破壞。

Bad Component 第一次渲染

咱們的實例變量firstNamelastName包含正確的數據,但讓咱們看看第二次渲染會發生什麼:

Bad Component 第二次渲染

如今,firstNamelastName發生了錯位,咱們的狀態存儲變得不一致了。這就是爲何保持正確順序的重要性。

總結:

經過對 useState 的簡單實現來理解 react hooks 的幕後實現邏輯。考慮將狀態做爲一組數組存在的模型,那麼咱們不應違反其對應的使用規則。

相關文章
相關標籤/搜索