譯文:染陌 (Github)
轉載請著名出處
我是一名hooks API的忠實粉絲,然而它對你的使用會有一些奇怪的約束,所以我在本文中使用一個模型來把原理展示給那些想去使用新的API卻難以理解它的規則的人。
警告:Hooks 還處於實驗階段
本文提到的 Hooks API 還處於實驗階段,如果你需要的是穩定的 React API 文件,可以從這裡找到。
解密 Hooks 的工作方式
我發現一些同學苦苦思索新的 Hooks API 中的“魔法”,所以我打算嘗試著去解釋一下,至少從表層出發,它是如何工作的。
Hooks 的規則
React 核心團隊在Hooks的提案中提出了兩個在你使用Hooks的過程中必須去遵守的主要規則。
- 請不要在迴圈、條件或者巢狀函式中呼叫 Hooks
- 都有在 React 函式中才去呼叫 Hooks
後者我覺得是顯而易見的,你需要用函式的方式把行為與元件關聯起來才能把行為新增到元件。
然而對於前者,我認為它會讓人產生困惑,因為這樣使用 API 程式設計似乎顯得不那麼自然,但這就是我今天要套索的內容。
Hooks 的狀態管理都是依賴陣列的
為了讓大家產生一個更清晰的模型,讓我們來看一下 Hooks 的簡單實現可能是什麼樣子。
需要注意的是,這部分內容只是 API 的一種可能實現方法,以便讀者更好地趣理解它。它並不是 API 實際在內部的工作方式,而且它只是一個提案,在未來都會有可能發生變化。
我們應該如何實現“useState()”呢?
讓我們通過一個例子來理解狀態可能是如何工作的。
首先讓我們從一個元件開始:
/* 譯:https://github.com/answershuto */
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
複製程式碼
Hooks API 背後的思想是你可以將一個 setter 函式通過 Hook 函式的第二個引數返回,用該函式來控制 Hook 管理的壯體。
所以 React 能用這個做什麼呢?
首先讓我們解釋一下它在 React 內部是如何工作的。在執行上下文去渲染一個特殊元件的時候,下面這些步驟會被執行。這意味著,資料的儲存是獨立於元件之外的。該狀態不能與其他元件共享,但是它擁有一個獨立的作用域,在該作用域需要被渲染時讀取資料。
(1)初始化
建立兩個空陣列“setters”與“state”
設定指標“cursor”為 0
(2)首次渲染
首次執行元件函式
每當 useState() 被呼叫時,如果它是首次渲染,它會通過 push 將一個 setter 方法(繫結了指標“cursor”位置)放進 setters 陣列中,同時,也會將另一個對應的狀態放進 state 陣列中去。
(3)後續渲染
每次的後續渲染都會重置指標“cursor”的位置,並會從每個陣列中讀取對應的值。
(4)處理事件
每個 setter 都會有一個對應的指標位置的引用,因此當觸發任何 setter 呼叫的時候都會觸發去改變狀態陣列中的對應的值。
以及底層的實現
這是一段示例程式碼:
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
/* 譯:https://github.com/answershuto */
// 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];
}
/* 譯:https://github.com/answershuto */
// 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 在渲染週期的順序,那會發生什麼呢?
讓我們做一些 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 函式,讓我們看看它對整個系統造成的破壞。
糟糕元件的首次渲染
到此為止,我們的變數 firstName 與 lastName 依舊包含了正確的資料,讓我們繼續去看一下第二次渲染會發生什麼事情。
糟糕的第二次渲染
現在 firstName 與 lastName 這兩個變數全部被設定為“Rudi”,與我們實際的儲存狀態不符。
這個例子的用法顯然是不正確的,但是它讓我們知道了為什麼我們必須使用 React 團隊規定的規則去使用 Hooks。
React 團隊制定了這個規則,是因為如果不遵循這套規則去使用 Hooks API會導致資料有問題。
複製程式碼
思考 Hooks 維護了一些列的陣列,所以你不應該去違反這些規則
所以你現在應該清除為什麼你不應該在條件語句或者迴圈語句中使用 Hooks 了。因為我們維護了一個指標“cursor”指向一個陣列,如果你改變了 render 函式內部的呼叫順序,那麼這個指標“cursor”將不會匹配到正確的資料,你的呼叫也將不會指向正確的資料或控制程式碼。
因此,有一個訣竅就是你需要思考 Hooks 作為一組需要一個匹配一致的指標“cursor”去管理的陣列(染陌譯)。如果做到了這一點,那麼採用任何的寫法它都可以正常工作。
總結
希望通過上述的講解,我已經給大家建立了一個關於 Hooks 的更加清晰的思維模型,以此可以去思考新的 Hooks API 底層到底做了什麼事情。請記住,它真正的價值在於能夠關注點聚集在一起,同時小心它的順序,那使用 Hooks API 會很高的回報。
Hooks 是 React 元件的一個很有用的外掛,這也佐證了為何大家為何對此感到如此興奮。如果你腦海中形成了我上述的這種思維模型,把這種狀態作為一組陣列的存在,那麼你就會發現在使用中不會打破它的規則。
我希望將來再去研究一下 useEffects useEffects 方法,並嘗試將其與 React 的生命週期進行比較。
這篇文章是一篇線上文件,如果你想要參與貢獻或者有任何有誤的地方,歡迎聯絡我。
你可以在 Twitter 上面 fllow 我(Rudi Yardley)或者在Github找到我。