作者:Dave Ceddia
譯者:前端小智
來源:daveceddia
為了保證的可讀性,本文采用意譯而非直譯。
我們大部分 React 類元件可以儲存狀態,而函式元件不能? 並且類元件具有生命週期,而函式元件卻不能?
React 早期版本,類元件可以通過繼承PureComponent
來優化一些不必要的渲染,相對於函式元件,React 官網沒有提供對應的方法來快取函式元件以減少一些不必要的渲染,直接 16.6 出來的 React.memo
函式。
React 16.8 新出來的Hook
可以讓React 函式元件具有狀態,並提供類似 componentDidMount
和componentDidUpdate
等生命週期方法。
類被會替代嗎?
Hooks
不會替換類,它們只是一個你可以使用的新工具。React 團隊表示他們沒有計劃在React中棄用類,所以如果你想繼續使用它們,可以繼續用。
我能體會那種總有新東西要學的感覺有多痛苦,不會就感覺我們們總是落後一樣。Hooks
可以當作一個很好的新特性來使用。當然沒有必要用 Hook 來重構原來的程式碼, React團隊也建議不要這樣做。
Go Go
來看看Hooks的例子,我們們先從最熟悉的開始:函式元件。
以下 OneTimeButton 是函式元件,所做的事情就是當我們點選的時候呼叫 sayHi
方法。
import React from 'react';
import { render } from 'react-dom';
function OneTimeButton(props) {
return (
<button onClick={props.onClick}>
點我點我
</button>
)
}
function sayHi() {
console.log('yo')
}
render(
<OneTimeButton onClick={sayHi}/>,
document.querySelector('#root')
)
複製程式碼
我們想讓這個元件做的是,跟蹤它是否被點選,如果被點選了,禁用按鈕,就像一次性開關一樣。
但它需要一個state,因為是一個函式,它不可能有狀態(React 16.8之前),所以需要重構成類。
函式元件轉換為類元件的過程中大概有5個階段:
-
否認:也許它不需要是一個類,我們可以把 state 放到其它地方。
-
實現: 廢話,必須把它變成一個
class
,不是嗎? -
接受:好吧,我會改的。
-
努力加班重寫:首先 寫
class Thing extends React.Component
,然後 實現render
等等 。 -
最後:新增state。
class OneTimeButton extends React.Component {
state = {
clicked: false
}
handleClick = () => {
this.props.onClick();
// Ok, no more clicking.
this.setState({ clicked: true });
}
render() {
return (
<button
onClick={this.handleClick}
disabled={this.state.clicked}
>
You Can Only Click Me Once
</button>
);
}
}
複製程式碼
這是相當多的程式碼,元件的結構也發生了很大的變化, 我們需要多個小的功能,就需要改寫很多。
使用 Hook 輕鬆新增 State
接下來,使用新的 useState
hook向普通函式元件新增狀態:
import React, { useState } from 'react'
function OneTimeButton(props) {
const [clicked, setClicked] = useState(false)
function doClick() {
props.onClick();
setClicked(true)
}
return (
<button
onClick={clicked ? undefined : doClick}
disabled={clicked}
>
點我點我
</button>
)
}
複製程式碼
這段程式碼是如何工作的
這段程式碼的大部分看起來像我們一分鐘前寫的普通函式元件,除了useState
。
useState
是一個hook。 它的名字以**“use”**開頭(這是Hooks的規則之一 - 它們的名字必須以“use”開頭)。
useState
hook 的引數是 state 的初始值,返回一個包含兩個元素的陣列:當前state和一個用於更改state 的函式。
類元件有一個大的state物件,一個函式this.setState
一次改變整個state物件。
函式元件根本沒有狀態,但useState
hook允許我們在需要時新增很小的狀態塊。 因此,如果只需要一個布林值,我們就可以建立一些狀態來儲存它。
由於Hook
以某種特殊方式建立這些狀態,並且在函式元件內也沒有像setState
函式來更改狀態,因此 Hook 需要一個函式來更新每個狀態。 所以 useState
返回是一對對應關係:一個值,一個更新該值函式。 當然,值可以是任何東西 - 任何JS型別 - 數字,布林值,物件,陣列等。
現在,你應該有很多疑問,如:
-
當元件重新渲染時,每次都不會重新建立新的狀態嗎? React如何知道舊狀態是什麼?
-
為什麼hook 名稱必須以**“use”**開頭? 這看起來很可疑。
-
如果這是一個命名規則,那是否意味著我可以自定義 Hook。
-
如何儲存更復雜的狀態,很多場景不單單隻有一個狀態值這麼簡單。
Hooks 的魔力
將有狀態資訊儲存在看似無狀態的函式元件中,這是一個奇怪的悖論。這是第一個關於鉤子的問題,我們們必須弄清楚它們是如何工作的。
原作者得的第一個猜測是某種編譯器的在背後操眾。搜尋程式碼useWhatever
並以某種方式用有狀態邏輯替換它。
然後再聽說了呼叫順序規則(它們每次必須以相同的順序呼叫),這讓我更加困惑。這就是它的工作原理。
React第一次渲染函式元件時,它同時會建立一個物件與之共存,該物件是該元件例項的定製物件,而不是全域性物件。只要元件存在於DOM中,這個元件的物件就會一直存在。
使用該物件,React
可以跟蹤屬於元件的各種後設資料位。
請記住,React元件甚至函式元件都從未進行過自渲染。它們不直接返回HTML
。元件依賴於React
在適當的時候呼叫它們,它們返回的物件結構React
可以轉換為DOM
節點。
React有能力在呼叫每個元件之前做一些設定,這就是它設定這個狀態的時候。
其中做的一件事設定 Hooks 陣列。 它開始是空的, 每次呼叫一個hook
時,React 都會向該陣列新增該 hook
。
為什麼順序很重要
假設我們們有以下這個元件:
function AudioPlayer() {
const [volume, setVolume] = useState(80);
const [position, setPosition] = useState(0);
const [isPlaying, setPlaying] = useState(false);
.....
}
複製程式碼
因為它呼叫useState
3次,React 會在第一次渲染時將這三個 hook 放入 Hooks 陣列中。
下次渲染時,同樣的3
個hooks
以相同的順序被呼叫,所以React
可以檢視它的陣列,並發現已經在位置0
有一個useState
hook ,所以React
不會建立一個新狀態,而是返回現有狀態。
這就是React能夠在多個函式呼叫中建立和維護狀態的方式,即使變數本身每次都超出作用域。
多個useState 呼叫示例
讓我們們更詳細地看看這是如何實現的,第一次渲染:
-
React 建立元件時,它還沒有呼叫函式。React 建立後設資料物件和Hooks的空陣列。假設這個物件有一個名為
nextHook
的屬性,它被放到索引為0
的位置上,執行的第一個hook將佔用位置0
。 -
React 呼叫你的元件(這意味著它知道儲存
hooks
的後設資料物件)。 -
呼叫
useState
,React建立一個新的狀態,將它放在hooks
陣列的第0
位,並返回[volume,setVolume]
對,並將volume
設定為其初始值80
,它還將nextHook
索引遞增1。 -
再次呼叫
useState
,React檢視陣列的第1
位,看到它是空的,並建立一個新的狀態。 然後它將nextHook
索引遞增為2
,並返回[position,setPosition]
。 -
第三次呼叫
useState
。 React看到位置2
為空,同樣建立新狀態,將nextHook
遞增到3
,並返回[isPlaying,setPlaying]
。
現在,hooks
陣列中有3
個hook,渲染完成。 下一次渲染會發生什麼?
-
React
需要重新渲染元件, 由於 React 之前已經看過這個元件,它已經有了後設資料關聯。 -
React
將nextHook
索引重置為0
,並呼叫元件。 -
呼叫
useState
,React檢視索引0
處的hooks陣列,並發現它已經在該槽中有一個hook。,所以無需重新建立一個,它將nextHook
推進到索引1
並返回[volume,setVolume]
,其中volume
仍設定為80
。 -
再次呼叫
useState
。 這次,nextHook
為1
,所以React
檢查陣列的索引1
。同樣,hook 已經存在,所以它遞增nextHook
並返回[position,setPosition]
。 -
第三次呼叫
useState
,我想你知道現在發生了什麼。
就是這樣了,知道了原理,看起來也就不那麼神奇了, 但它確實依賴於一些規則,所以才有使用 Hooks 規則。
Hooks 的規則
自定義 hooks 函式只需要遵守規則 3 :它們的名稱必須以**“use”**為字首。
例如,我們可以從AudioPlayer
元件中將3個狀態提取到自己的自定義鉤子中:
function AudioPlayer() {
// Extract these 3 pieces of state:
const [volume, setVolume] = useState(80);
const [position, setPosition] = useState(0);
const [isPlaying, setPlaying] = useState(false);
// < beautiful audio player goes here >
}
複製程式碼
因此,我們們可以建立一個專門處理這些狀態的新函式,並使用一些額外的方法返回一個物件,以便更容易啟動和停止播放,例如:
function usePlayerState(lengthOfClip) {
const [volume, setVolume] = useState(80);
const [position, setPosition] = useState(0);
const [isPlaying, setPlaying] = useState(false);
const stop = () => {
setPlaying(false);
setPosition(0);
}
const start = () => {
setPlaying(true);
}
return {
volume,
position,
isPlaying,
setVolume,
setPosition,
start,
stop
};
}
複製程式碼
像這樣提取狀態的一個好處是可以將相關的邏輯和行為組合在一起。可以提取一組狀態和相關事件處理程式以及其他更新邏輯,這不僅可以清理元件程式碼,還可以使這些邏輯和行為可重用。
另外,通過在自定義hooks
中呼叫自定義hooks
,可以將hooks
組合在一起。hooks只是函式,當然,函式可以呼叫其他函式。
總結
Hooks 提供了一種新的方式來處理React中的問題,其中的思想是很有意思且新奇的。
React團隊整合了一組很棒的文件和一個常見問題解答,從是否需要重寫所有的類元件到鉤Hooks是否因為在渲染中建立函式而變慢? 以及兩者之間的所有東西,所以一定要看看。
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
原文:daveceddia.com/intro-to-ho…
交流(歡迎加入群,群工作日都會發紅包,互動討論技術)
乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。
我是小智,公眾號「大遷世界」作者,對前端技術保持學習愛好者。我會經常分享自己所學所看的乾貨,在進階的路上,共勉!
關注公眾號,後臺回覆福利,即可看到福利,你懂的。