Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。javascript
Hook 是一些可讓你在函數組件裏「鉤入
」 React state
及生命週期等特性的函數。Hook 不能在 class 組件中使用 —— 這使得你不使用 class 也能使用 React。(不推薦把你已有的組件所有重寫,可是你能夠在新組件裏開始使用 Hook。)前端
若是你在編寫函數組件並意識到須要向其添加一些 state,之前的作法是必須將其它轉化爲 class。如今你能夠在現有的函數組件中使用 Hook。java
React 內置了一些像 useState
這樣的 Hook。你也能夠建立你本身的 Hook 來複用不一樣組件之間的狀態邏輯。先來看看有哪些內置的 Hook。react
個人網站:www.dengzhanyong.com
我的公衆號: 【前端筱園】
State Hook
這個例子用來顯示一個計數器。當你點擊按鈕,計數器的值就會增長:數組
import React, { useState } from 'react';
function Example() {
// 聲明一個叫 「count」 的 state 變量。
const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
複製代碼
這裏的useState
就是一個hook
,在函數組件內容經過調用useState 來添加一些所需的state,它只須要傳入一個參數,這個參數就是state的默認初始值,返回一對值:當前狀態以及更改變量的方法,這裏採用的是數組結構的寫法。瀏覽器
const [count, setCount] = useState(0);
複製代碼
它等同於下面的Class組件:性能優化
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div>
);
}
}
複製代碼
這二者所實現的功能是同樣的,能夠很明顯的看到,使用hook實現的代碼量少了不少,不管是聲明、使用仍是修改都變得更加的簡單。網絡
聲明 State 變量
在 class 中,咱們經過在構造函數中設置 this.state 爲 { count: 0 } 來初始化 count state 爲 0:閉包
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
複製代碼
在函數組件中,咱們沒有 this,因此咱們不能分配或讀取 this.state。咱們直接在組件中調用 useState Hook:ide
import React, { useState } from 'react';
function Example() {
// 聲明一個叫 「count」 的 state 變量
const [count, setCount] = useState(0);
}
複製代碼
讀取 State 變量
當咱們想在 class 中顯示當前的 count,咱們讀取 this.state.count:
<p>You clicked {this.state.count} times</p>
複製代碼
在函數中,咱們能夠直接用 count:
<p>You clicked {count} times</p>
複製代碼
更新 State 變量
在 class 中,咱們須要調用 this.setState() 來更新 count 值:
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
複製代碼
在函數中,咱們已經有了 setCount 和 count 變量,因此咱們不須要 this:
<button onClick={() => setCount(count + 1)}>
Click me
</button>
複製代碼
使用多個 state 變量
將 state 變量聲明爲一對 [something, setSomething] 也很方便,由於若是咱們想使用多個 state 變量,它容許咱們給不一樣的 state 變量取不一樣的名稱:
function ExampleWithManyStates() {
// 聲明多個 state 變量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '學習 Hook' }]);
複製代碼
Effect Hook 可讓你在函數組件中執行反作用操做,仍是上面的例子,如今增長一個功能,當計數器數值改變時,使 document 的 title 也隨之改變,你能夠像下面這樣作:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
複製代碼
若是你熟悉 React class 的生命週期函數,你能夠把 useEffect Hook 看作 componentDidMount
,componentDidUpdate
和 componentWillUnmount
這三個函數的組合。
這時就有人會問了,在Class明明是3個生命週期,在函數中怎麼能夠只須要一個useEffect
函數就能夠徹底代替他們?
無需清除的 effect
有時候,咱們只想在 React 更新 DOM 以後運行一些額外的代碼。好比發送網絡請求,手動變動 DOM
,記錄日誌,這些都是常見的無需清除的操做。由於咱們在執行完這些操做以後,就能夠忽略他們了。
在 React 的 class 組件中,render
函數是不該該有任何反作用的。通常來講,在這裏執行操做太早了,咱們基本上都但願在 React 更新 DOM 以後才執行咱們的操做。
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div>
);
}
}
複製代碼
上面是使用class實現的方法,咱們須要在componentDidMount
與componentDidUpdate
中都加上更新的操做,這時由於咱們但願在組件加載和更新的狀況下都要進行一樣的操做。
回到hook中,只須要在useEffect
中寫一遍操做便可:
useEffect(() => {
document.title = `You clicked ${count} times`;
});
複製代碼
useEffect 作了什麼? 經過使用這個 Hook,你能夠告訴 React 組件須要在渲染後執行哪些操做。React 會保存你傳遞的函數(咱們將它稱之爲 「effect
」),而且在執行 DOM 更新以後調用它。在這個 effect 中,咱們設置了 document 的 title 屬性,不過咱們也能夠執行數據獲取或調用其餘命令式的 API。
爲何在組件內部調用 useEffect? 將 useEffect 放在組件內部讓咱們能夠在 effect 中直接訪問 count state 變量(或其餘 props)。咱們不須要特殊的 API 來讀取它 —— 它已經保存在函數做用域中。Hook 使用了 JavaScript
的閉包機制,而不用在 JavaScript 已經提供瞭解決方案的狀況下,還引入特定的 React API
。
useEffect 會在每次渲染後都執行嗎? 是的,默認狀況下,它在第一次渲染以後和每次更新以後都會執行。你可能會更容易接受 effect 發生在「渲染以後」這種概念,不用再去考慮「掛載」仍是「更新」。React 保證了每次運行 effect 的同時,DOM 都已經更新完畢。
須要清除的 effect
以前,咱們研究瞭如何使用不須要清除的反作用,還有一些反作用是須要清除的。例如訂閱外部數據源、settimeout、setinterval等。這種狀況下,清除工做是很是重要的,能夠防止引發內存泄露!如今讓咱們來比較一下如何用 Class 和 Hook 來實現。
在 React class 中,你一般會在 componentDidMount
中設置訂閱,並在 componentWillUnmount
中清除它。例如,假設咱們有一個 ChatAPI 模塊,它容許咱們訂閱好友的在線狀態。如下是咱們如何使用 class 訂閱和顯示該狀態:
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
componentDidMount()與componentWillUnmount()中的內容是相對應的,在class中不得部迫使咱們拆分這些邏輯代碼。 複製代碼
因爲添加和刪除訂閱的代碼的緊密性,因此 useEffect
的設計是在同一個地方執行。若是你的 effect 返回一個函數,React 將會在執行清除操做時調用它:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
複製代碼
若是對useEffect設置了返回值,那個這個返回值中的內容就至關因而class組件內的componentWillUnmount
操做,React會在組件卸載時執行清除操做。
effect
像你可使用多個state
的 hook
同樣,你也可使用多個effect,將不相關邏輯分離到不一樣的 effect 中:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
}
複製代碼
Hook 容許咱們按照代碼的用途分離他們, 而不是像生命週期函數那樣。React 將按照 effect 聲明的順序依次調用組件中的每個 effect
。
Effect 進行性能優化
在某些狀況下,每次渲染後都執行清理或者執行 effect 可能會致使性能問題。在 class 組件中,咱們能夠經過在 componentDidUpdate
中添加對 prevProps
或 prevState
的比較邏輯解決:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
複製代碼
這是很常見的需求,因此它被內置到了 useEffect
的 Hook API 中。若是某些特定值在兩次重渲染之間沒有發生變化,你能夠通知 React 跳過對 effect
的調用,只要傳遞數組做爲 useEffect
的第二個可選參數便可:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新
複製代碼
若是想執行只運行一次的 effect
(僅在組件掛載和卸載時執行),能夠傳遞一個空數組([]
)做爲第二個參數。這就告訴 React 你的 effect 不依賴於 props
或 state
中的任何值,因此它永遠都不須要重複執行。這並不屬於特殊狀況 —— 它依然遵循依賴數組的工做方式。
不要在循環,條件或嵌套函數中調用 Hook
, 確保老是在你的 React 函數的最頂層調用他們。遵照這條規則,你就能確保 Hook 在每一次渲染中都按照一樣的順序被調用。這讓 React 可以在屢次的 useState
和 useEffect
調用之間保持 hook 狀態的正確
不要在普通的 JavaScript
函數中調用 Hook
。你能夠在 React 的函數組件中調用 Hook,在自定義 Hook 中調用其餘 Hook。
經過自定義 Hook,能夠將組件邏輯提取到可重用的函數中。
自定義 Hook 是一個函數,其名稱以 「use」
開頭,函數內部能夠調用其餘的 Hook。 例如,下面的 useFriendStatus 是就是一個自定義的 Hook:
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
複製代碼
與 React 組件不一樣的是,自定義 Hook 不須要具備特殊的標識。咱們能夠自由的決定它的參數是什麼,以及它應該返回什麼(若是須要的話)。換句話說,它就像一個正常的函數。可是它的名字應該始終以 use 開頭,這樣能夠一眼看出其符合 Hook
的規則。
此處 useFriendStatus
的 Hook 目的是訂閱某個好友的在線狀態。這就是咱們須要將 friendID 做爲參數,並返回這位好友的在線狀態的緣由。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
return isOnline;
}
複製代碼
如今咱們已經把這個邏輯提取到 useFriendStatus
的自定義 Hook
中,而後就可使用它了:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
複製代碼
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li>
);
}
複製代碼
自定義 Hook 必須以 「use」 開頭嗎? 必須如此。這個約定很是重要。不遵循的話,因爲沒法判斷某個函數是否包含對其內部 Hook 的調用,React 將沒法自動檢查你的 Hook 是否違反了 Hook
的規則。
在兩個組件中使用相同的 Hook 會共享 state 嗎? 不會。自定義 Hook 是一種重用狀態邏輯的機制(例如設置爲訂閱並存儲當前值),因此每次使用自定義 Hook
時,其中的全部 state 和反作用都是徹底隔離的。
自定義 Hook 如何獲取獨立的 state? 每次調用 Hook
,它都會獲取獨立的 state
。因爲咱們直接調用了 useFriendStatus,從 React 的角度來看,咱們的組件只是調用了 useState
和 useEffect
。咱們能夠在一個組件中屢次調用 useState 和 useEffect,它們是徹底獨立的。
useContext
const value = useContext(MyContext);
複製代碼
接收一個 context 對象(React.createContext
的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
複製代碼
useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer
,並返回當前的 state 以及與其配套的 dispatch 方法。
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
複製代碼
把內聯回調函數及依賴項數組做爲參數傳入useCallback
,它將返回該回調函數的 memoized
版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子組件時,它將很是有用。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
複製代碼
把「建立」函數和依賴項數組做爲參數傳入 useMemo
,它僅會在某個依賴項改變時才從新計算 memoized
值。這種優化有助於避免在每次渲染時都進行高開銷的計算。
useRef
const refContainer = useRef(initialValue);
複製代碼
useRef
返回一個可變的 ref 對象,其 .current
屬性被初始化爲傳入的參數(initialValue
)。返回的 ref 對象在組件的整個生命週期內保持不變。
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
複製代碼
useImperativeHandle
可讓你在使用 ref 時自定義暴露給父組件的實例值。在大多數狀況下,應當避免使用 ref 這樣的命令式代碼。useImperativeHandle
應當與 forwardRef
一塊兒使用。
useLayoutEffect
其函數簽名與 useEffect
相同,但它會在全部的 DOM 變動以後同步調用 effect
。可使用它來讀取 DOM 佈局並同步觸發重渲染。在瀏覽器執行繪製以前,useLayoutEffect
內部的更新計劃將被同步刷新。
useDebugValue
useDebugValue
可用於在 React 開發者工具中顯示自定義 hook
的標籤。
個人網站:www.dengzhanyong.com
![]()