[譯] React Hooks 揭祕:數組解構融成魔法

用模型解析此提案的執行規則

我超喜歡 React 新出的這個 Hooks API。而在使用它時卻有一些奇怪的規則。爲了那些糾結於爲何要有這些規則的人,在這裏我會以模型圖的方式來向大家展現這個新的 API。html

警告: Hooks 這項提案仍在測試階段

這篇文章主要講述的是關於 React hooks 這項新 API,此時這個提案仍處於 alpha 測試版本。你能夠在 React 官方文檔中找到穩定的 React API。前端


圖片來自網站 Pexels(rawpixel.comreact

拆解 Hooks 的工做方式

「就像魔法同樣沒法理解」,這是我從一些人口中聽到的對於這項新提案的評價,因此我願意嘗試經過拆解這項提案的代碼語法去了解它是如何在代碼中工做的,至少也要了解它最表層的執行邏輯。android

Hooks 的規則

React 的核心團隊規定咱們在使用 hooks 時必須聽從 hooks 提案文檔中列出的兩條規則。ios

  • 不要在循環、條件語句或者嵌套函數中調用 Hooks
  • 只能在 React 函數中調用 Hooks

對於後者我以爲是不言而喻的。要將本身的業務代碼嵌入到功能組件當中,你天然須要以某種方式將你的代碼和組件聯繫起來。git

至於前者我認爲也是使人感到困惑的一點,由於這與正常編程時調用 API 的方式相比起來並不尋常,這也是我今天正想探索的一點。github

在 hooks 中,狀態管理都與數組有關

爲了讓咱們更清晰地理解這個模型,讓咱們直接實現一個簡單的 hooks API 實例看看它可能長什麼樣子。編程

請注意這只是推測,而且只是可能的 API 實現方法,主要是爲了展現應該經過怎樣的思惟去理解它。固然這並不必定就是 API 實際的內部工做方式。並且目前這也只是一個提案,在將來一切均可能發生改變後端

咱們能夠怎樣實現 useState()

讓咱們經過剖析一個例子來演示一下狀態鉤子的實現可能會怎麼運行吧。數組

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

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

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}
複製代碼

實現 hooks API 的基本思想是把一個 setter 方法做爲鉤子函數返回的第二個數組項,以後經過這個 setter 方法來控制鉤子管理的狀態值。(在這個例子中,setter 方法指 setFirstName,鉤子就是 useState 而它管理的值就是"Rudi")

因此讓咱們來看看 React 將利用它來作些什麼?

讓我先解釋一下在 React 的內部中它可能會怎麼工做。下面圖表將爲咱們展現一個特定組件在渲染時其內部執行上下文的執行過程。這也意味着存儲在這裏的數據是從外面傳入的。雖然這裏的狀態沒有與其餘組件共享,可是它會繼續保留在必定範圍以內以供後續渲染對應的特殊組件時使用。

1) 初始化

聲明兩個空數組:settersstate

設置一個 cursor 參數爲 0

初始化:兩個空數組,cursor 參數爲 0


2) 初次渲染

第一次執行組件方法

當調用 useState() 時,第一次會將一個 setter 方法(就是以 cursor 爲下標的參數)添加到 setters 數組當中而後再把一些狀態添加到 state 數組當中。

初次渲染:cursor ++,變量分別被寫入數組當中


3) 後續渲染

後續的每一次渲染過程中 cursor 都將被重置,而渲染的值都只是從每一次的數組當中取出來的。

後續渲染:從數組(以 cursor 爲下標)中讀取了每一項變量的值


4) 事件代理

由於每個 setter 方法都和其 cursor 綁定以致於經過觸發來調用任何 setter 時它都將改變對應索引位置的狀態數組的狀態值。

Setters 「記住」了他們的索引並根據它來寫入內存。


以及底層的實現

這裏用一段代碼示例來展現:

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

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

// 這是我仿寫的 useState 輔助類
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>
  );
}

// 這裏模擬了 React 的渲染週期
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 命令放在渲染週期裏執行會怎樣呢?

讓咱們嘗試寫一些 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。一塊兒來看這樣會對整個系統形成多大的影響。

被「破壞」的組件第一次渲染

渲染一個外部的「壞」鉤,以後它將會在下次渲染時消失不見。

在此時咱們在實例中聲明的 firstNamelastName 暫時還帶着正確的數據,但接下來讓咱們看看在第二次渲染時會發生什麼:

被「破壞」的組件第二次渲染

在渲染時移除了鉤子以後,咱們發現了一個錯誤。

因爲在此時咱們的狀態存儲了錯位的數據使 firstNamelastName 被同時賦值爲 「Rudi」。這很明顯是不正確的並且它也沒有任何做用,可是這也給咱們說明了爲何 hooks 具備這樣的規則限制。

React 團隊正在制定使用規範由於不遵照它們將會使數據錯位。

思考一下如何在不違反規則的狀況下使用 hooks 操做一組數組

因此如今咱們應該很是清楚爲何咱們不能在循環或者條件語句中調用 use 鉤子。由於事實上咱們的代碼處理是基於數組解構賦值的,若是你更改了渲染時的調用順序,那麼就會使咱們的數據或者事件處理器在解構後沒有匹配正確。

因此技巧是考慮讓 hooks 用一致的 cursor 來管理數組業務,若是你這麼作就可讓它正常的工做。

結論

但願我是創建了一個比較清晰的模型向大家展現了這個新 hooks API 在底層是如何進行工做的。記住這裏真正的價值是如何把咱們所好奇的點一步步解析出來,因此你只要多注意 hooks API 的規則,這樣咱們就能更好的利用它。

在 React 組件中 Hooks 是一個高效率的 API。這也是人們對它趨之若鶩的緣由,若是你還沒想到答案,那麼只要把數組存儲爲狀態的形式,就會發現你並無破壞他們的規則。

我但願未來能夠持續再瞭解一下 useEffects 方法而且嘗試對比一下它和 React 的那些生命週期方法有什麼區別。


這篇文章尚有不足之處,若是你有更好的建議或者發現了文章中的錯誤紕漏請及時跟我聯繫。

你能夠在 Twitter 上關注 Rudi Yardley @rudiyardley 或者關注 github _@_ryardley

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索