【譯】React hooks: 不是魔法,只是數組

圖解hooks的規則react

我是新的hooks API的超級粉絲。然而,在使用hooks的時候有一些奇怪的限制。在這裏,我將爲那些對於理解這些規則有困難的人,呈現一個模型來解釋如何去思考新的API。數組

Photo by rawpixel.com from Pexels

揭祕hooks是如何工做的

我聽聞有些人對於新的hooks API的「魔力」感到很是困惑,所以我想我會至少在使用級別揭祕這個語法是怎麼工做的。bash

hooks的原則

對於hooks的使用,React核心團隊規定了兩條使用規則,這兩條規則列在了文檔Hooks規則中。函數

  • 不要在循環,條件或者是嵌套函數中使用hooks
  • 只在React函數中調用hooks

後一條規則,我想是顯而易見的。爲了將這些功能添加到(React)函數組件中,就必需要將這些功能與(React)組件聯繫起來。 然而,前一條規則,我想多是比較使人疑惑的。由於對於使用一個像這樣的API來講,這條規則彷佛不太常見。這也正是,我今天想要探索內容。ui

在hooks中的狀態的管理全是關於數組的

爲了獲取到一個更加形象的模型,讓咱們來看下hooks API的一個簡單的使用是什麼樣的。spa

請注意這只是猜想,也只是使用API的一種方式,用來展示關於hooks,你可能想知道的。這並不必定是API內部如何真正工做的。插件

咱們該如何使用useState()?

讓咱們在這裏演示一個例子,來論證下state hook的可能的工做方式。code

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

function RenderFunctionComponent() {
    const [firstName, setFirstName] = useState('Rudi');
    const [lastName, setLastName] = useState('Yardley');
    
    return (
        <Button onClick={() => setFirstName('Fred')}>Fred</Button>
    );
}
複製代碼

在hooks API背後的思想是:你可使用一個setter函數做爲hook函數返回值的數組的第二個元素。這個setter函數會控制被hook管理的state狀態值。

因此React將會對這些作什麼呢?

讓咱們來解釋下這些在React的內部多是怎麼工做的。接下來的執行過程是在渲染一個特定組件的執行上下文中運行。這意味着這裏存儲的數據在渲染組件的外級存放。這個state不與其餘組件共享,只維護在一個做用域中,在這個特定組件二次渲染的時候能夠獲取獲得。

1)初始化

建立兩個空的數組:settersstate

設置光標爲0:

初始化:兩個空的數組,光標是0

首次渲染

第一次運行這個函數組件。 每一個useState調用,首次運行的時候,都會將一個setter函數(綁定着一個光標位置)推動setters數組中,而後將一些state狀態值推動state數組。

首次渲染:隨着光標增長,每個元素被寫入數組

二次渲染

每一個二次渲染,光標都會被重製,那些值正是從每個數組中去讀取。

二次渲染:隨着光標增長,值從數組中讀取

事件處理

每個setter跟它的光標位置之間都有一個對應關係。所以任一一個setter被觸發調用,在state數組中,相應位置的state的值就會被改變。

Setters會「記住」他們的位置索引值,並據此設置內存值

簡單的模擬

這裏是一段代碼示例來論證執行過程。

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

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

export function useState(initVal) {
    if (firstRun) {
        state.push(initVal);
        setters.push(createSetter(cursor));
        firstRun = false;
    }
    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={() => setFirstName('Fred')}>Fred</Button>
        </div>
    )
}

function MyComponent() {
    cursor = 0; // 重置光標
    return <RenderFunctionComponent />; // 渲染
}

console.log(state); // 渲染以前:[]
MyComponent();
console.log(state);  // 首次渲染: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // 再次渲染: ['Rudi', 'Yardley']

// 點擊'Fred'按鈕
console.log(state); // 在點擊之後:['Fred', 'Yardley']
複製代碼

爲何順序是重要的

若是咱們基於一些外在因素或者甚至是組件的狀態,來改變hooks的順序,會發生什麼呢?

讓咱們來作一些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在條件語句中被調用。讓咱們來看下這將會對這個系統形成哪些破壞。

有問題組件的首次渲染

渲染一個二次render時不執行的額外的有問題的hook

此時咱們的例子中的變量firstNamelastName包含着正確的數據。可是讓咱們來看下第二次渲染的時候發生了什麼:

有問題組件的第二次渲染

在渲染之間移除了一個hook,咱們獲得了一個錯誤

當下firstNamelastName都被設置爲了Rudi,由於咱們的state的存儲變得不一致了。這顯然是錯誤的、而且不能正常工做的。可是這也給了咱們一個解釋,爲何hooks的原則要被設置成那樣。

React團隊規定這些規則,由於不遵循這些規則將會致使數據的不一致性

設想hooks是正在操做一系列的數組,你將不會違反這些規則

因此如今應該很明確了,爲何你不能在條件或者是循環中調用hooks。由於咱們正在處理一個指向一系列數組的光標。若是你在render之間改變調用的順序,光標將沒法與數據相匹配,同時你的use調用將不會指向正確的數據或者是處理器。

因此這裏的技巧是設想hooks是把它的功能邏輯看成一系列數組來處理,同時還須要一個可以保持一致性的光標。若是你能作到這一點,全部的hooks都將能正常工做。

總結

但願我已經呈現了一個比較清晰的思惟模型,可以幫助你們思考在新的Hooks API下,一切都是怎麼運行的。記得這裏(hooks)真正的價值是可以將一組相關的邏輯組合在一塊兒,所以對於執行順序要當心。使用hooks API將會有較高的回報。

Hooks對於React組件是一個有用的API插件。這也是人們對於hooks感到比較激動的一個緣由。若是你將這種模型想象成一系列的存放state狀態值的數組,那麼你應該不會在使用他們的時候違反這些規則。

相關文章
相關標籤/搜索