[譯] React Hooks: 沒有魔法,只是數組

[譯] React Hooks: 沒有魔法,只是數組 原文連接: medium.com/@ryardley/r…react

我是 React 新特性 Hooks 的粉絲。可是,在你使用 React Hooks的過程當中,有一些看上去 很奇怪的限制 。在本文裏,對於那些還在爲了理解這些限制而苦苦掙扎的同志,我嘗試經過一些列圖表的方式,來解釋爲何會存在這些限制。數組

理解hooks怎麼運行

我據說不少同窗都對hooks像魔法通常的效果感到困惑,所以我將嘗試經過淺顯的方式,來演示hooks是怎麼運行的。bash

hooks的原則

react團隊在怎麼使用hooks的 官方文檔 中,強調了兩點主要的使用原則:函數

  • 不要 在 循環、條件語句或者嵌套函數中調用hooks優化

  • 只能在 React 函數組件中調用hooksui

第二點我認爲是顯而易見的。爲了給 函數組件 增長一些能力(好比 state,類聲明週期方法),你固然須要經過一種方式,來把這種能力賦給函數組件,這種方式就是使用hooks。spa

然而,第一點規則,很容易讓人感到困惑。不就是使用一個 API 麼,爲何還有這麼多限制呢。這也正是我將要在下文裏解釋的。3d

hooks中的state管理,只是在操做數組指針

爲了更加清晰的理解hooks,讓咱們來看看怎麼簡單實現hooks API。code

請注意,下面代碼只是一個demo,是爲了讓咱們理解hooks大概是怎麼運做的。這不是 React 中的真正內部實現。

怎麼實現 useState 呢?

讓咱們經過一個例子來演示,useState內部大概是怎麼運做的。

組件代碼以下:

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

useState 實現的功能是,你能經過這個hook返回的 數組 中第二個元素,做爲修改這個state的一個setter方法。

那麼,React可能會怎麼來實現 useState 呢?

讓咱們來想一想react內部會怎麼來實現 useState 呢。在下面的實現裏,state 是存放在被render的組件外面,而且這個state不會和其餘組件共享,同時,在這個組件後續render中,可以經過特定的做用域方式,訪問到這個state。

1) state初始化

建立兩個空數組,分別用來存放 setters 和 state,將 指針 指到 0 的位置:

2) 組件首次render

當首次render這個函數組件的時候。

每個 useState 調用,當 首次 執行的時候,在 setter 數組裏加入一個 setter 函數(和對應的數組index關聯);而後,將 state 加入對應的 state 數組裏:

3) 組件後續(非首次)render

後續組件的每次render,指針都會重置爲 0 ,每調用一次 useState,都會返回指針對應的兩個數組裏的 state 和 setter,而後將指針位置 +1。

4)setter調用處理

每個 setter 函數,都關聯了對應的指針位置。當調用某個 setter 函數式,就能夠經過這個函數所關聯的指針,找到對應的 state,修改state數組裏對應位置的值:

最後來看看useState簡單的實現

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
​
function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
  };
}
​
// This is the pseudocode for the useState helper
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];
}
​
// Our component code that uses hooks
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>
  );
}
​
// This is sort of simulating Reacts rendering cycle
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 'Fred' button
​
console.log(state); // After-click: ['Fred', 'Yardley']
複製代碼

爲何hooks的調用順序不能變呢?

若是咱們根據某些外部變量,或者組件自身的state,改變hooks的調用順序,會有什麼後果呢?

咱們來演示下 錯誤的 作法:

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 是在一個 條件分支裏。咱們來看看這樣引入的bug。

1) 第一次render

第一個render以後,咱們的兩個state,firstName 和 lastName 都對應了正確的值。接下來看看組件第二次render的時候,會發生什麼狀況。

2) 第二次render

第二次render以後,咱們的兩個state, firstName和 lastName 都成了 Rudi。這顯然是錯誤的,必需要避免這樣使用hooks!可是這也給咱們演示了,hooks的調用順序,爲何不能改變。

react團隊明確強調了hooks的2個使用原則,若是不按照這些原則來使用hooks,將會致使咱們數據的不一致性!

將hooks的操做想象成數組的操做,你可能不太會違背這些原則

OK,如今你應該清楚,爲何咱們不能在條件塊或者循環語句裏調用hooks了。由於調用hooks的過程當中,咱們是在操做數組上的指針,若是你在屢次render中,改變了hooks的調用順序,將致使數組上的指針和組件裏的 useState 不匹配,從而返回錯誤的 state 以及 setter 。

結論

但願我基本講明白了,hooks調用順序的大概原理。hooks是對react生態的一個很好的優化。人們對hooks感到興奮,是有緣由的。若是你將hooks的操做,當作數組同樣來看待,那麼你通常不會違背hooks的使用原則。

相關文章
相關標籤/搜索