轉載於免費視頻網站:www.rails365.net/articles/re…html
Effect Hook
可使得你在函數組件中執行一些帶有反作用的方法。react
import { 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>
);
}
複製代碼
上面這段代碼是基於上個state hook計數器的例子,可是如今添加了新的功能: 咱們將文檔標題設置爲自定義消息,包括點擊次數。git
數據獲取,設置訂閱以及手動更改React
組件中的DOM
都是反作用的示例。不管你是否習慣於將這些操做稱爲「反作用」(或僅僅是「效果」),但你以前可能已經在組件中執行了這些操做。github
提示: 若是你熟悉
React
類生命週期方法,則能夠將useEffect Hook
視爲componentDidMount
,componentDidUpdate
和componentWillUnmount
的組合。數組
React組件中有兩種常見的反作用:那些不須要清理的反作用,以及那些須要清理的反作用。讓咱們更詳細地看一下這種區別。瀏覽器
有時,咱們但願在 React
更新DOM
以後運行一些額外的代碼。 網絡請求,手動改變DOM
和日誌記錄是不須要清理的效果(反作用,簡稱'效果')的常見示例。咱們這樣說是由於咱們能夠運行它們並當即忘記它們。讓咱們比較一下class
和hooks
如何讓咱們表達這樣的反作用。網絡
在React
類組件中,render
方法自己不該該致使反作用。這太早了 - 咱們一般但願在React
更新DOM
以後執行咱們的效果。閉包
這就是爲何在React
類中,咱們將反作用放入componentDidMount
和componentDidUpdate
中。回到咱們的示例,這裏是一個React
計數器類組件,它在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>
);
}
}
複製代碼
請注意咱們如何在類中複製這兩個生命週期方法之間的代碼。佈局
這是由於在許多狀況下,咱們但願執行相同的反作用,不管組件是剛安裝仍是已更新。從概念上講,咱們但願它在每次渲染以後發生 - 可是React類組件沒有這樣的方法(render方法應該避免反作用)。咱們能夠提取一個單獨的方法,但咱們仍然須要在兩個地方調用它。
如今讓咱們看看咱們如何使用useEffect Hook
作一樣的事情。
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() : {
document.title = `You clicked ${count} times`;
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() : setCount(count + 1)}> Click me </button> </div>
);
}
複製代碼
useEffect
有什麼做用? 經過使用這個Hook
,你告訴React
你的組件須要在渲染後執行某些操做。React
將記住你傳遞的函數(咱們將其稱爲「效果」),並在執行DOM
更新後稍後調用它。在這個效果中,咱們設置文檔標題,但咱們也能夠執行數據提取或調用其餘命令式API
。
爲何在組件內調用useEffect
? 在組件中使用useEffect
讓咱們能夠直接從效果中訪問狀態變量(如count
或任何道具)。咱們不須要特殊的API
來讀取它 - 它已經在函數範圍內了。Hooks
擁抱JavaScript
閉包,並避免在JavaScript
已經提供解決方案的狀況下引入特定於React
的API
。
每次渲染後useEffect都會運行嗎? 是的。默認狀況下,它在第一次渲染以後和每次更新以後運行。 (咱們稍後會討論如何自定義它。)你可能會發現更容易認爲效果發生在「渲染以後」,而不是考慮「掛載」和「更新」。React
保證DOM
在運行‘效果’時已更新。
如今咱們對這個hook
更加的瞭解了,那讓咱們再看看下面的例子:
function Example() {
const [count, setCount] = useState(0);
useEffect(() : {
document.title = `You clicked ${count} times`;
});
}
複製代碼
咱們聲明瞭count
狀態變量,而後告訴React
咱們須要使用效果。咱們將一個函數傳遞給useEffect Hook
,這個函數就是效果(反作用)。在咱們的效果中,咱們使用document.title
瀏覽器API
設置文檔標題。咱們能夠讀取效果中的最新count
,由於它在咱們的函數範圍內。當React
渲染咱們的組件時,它會記住咱們使用的效果,而後在更新DOM
後運行咱們的效果。每次渲染都會發生這種狀況,包括第一次渲染。
有經驗的JavaScript
開發人員可能會注意到,傳遞給useEffect
的函數在每次渲染時都會有所不一樣。這是有意的。事實上,這就是讓咱們從效果中讀取計數值而不用擔憂它沒有改變的緣由。每次咱們從新渲染時,咱們都會安排一個不一樣的效果,取代以前的效果。在某種程度上,這使得效果更像是渲染結果的一部分 - 每一個效果「屬於」特定渲染。咱們將在本頁後面更清楚地看到爲何這有用。
注意: 與
componentDidMount
或componentDidUpdate
不一樣,使用useEffect
的效果不會阻止瀏覽器更新屏幕。這使應用感受更具響應性。大多數效果不須要同步發生。在他們這樣作的不常見狀況下(例如測量佈局),有一個單獨的useLayoutEffect Hook,其API
與useEffect
相同。
以前,咱們研究瞭如何表達不須要任何清理的反作用。可是,有些效果須要清理。例如,咱們可能但願設置對某些外部數據源的訂閱。在這種狀況下,清理是很是重要的,這樣咱們就不會引入內存泄漏!讓咱們比較一下咱們如何使用類和Hooks
來實現它。
class
的例子在React
類中,一般會在componentDidMount
中設置訂閱,並在componentWillUnmount
中清除它。例如,假設咱們有一個ChatAPI
模塊,可讓咱們訂閱朋友的在線狀態。如下是咱們如何使用類訂閱和顯示該狀態:
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
如何相互做用。生命週期方法迫使咱們拆分這個邏輯,即便它們中的概念代碼都與相同的效果有關。
注意: 眼尖的你可能會注意到這個例子還須要一個
componentDidUpdate
方法才能徹底正確。咱們暫時忽略這一點,但會在本頁的後面部分再回過頭來討論它。
hooks
的例子你可能認爲咱們須要單獨的效果來執行清理。可是添加和刪除訂閱的代碼是如此緊密相關,以致於useEffect
旨在將它保持在一塊兒。若是你的效果返回一個函數,React將在清理時運行它:
import { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() : {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
複製代碼
爲何咱們從效果中返回一個函數? 這是效果的可選清理機制。每一個效果均可能返回一個在它以後清理的函數。這使咱們能夠保持添加和刪除彼此接近的訂閱的邏輯。
React何時清理效果? 當組件卸載時,React
執行清理。可是,正如咱們以前所瞭解的那樣,效果會針對每一個渲染運行而不只僅是一次。這就是React
在下次運行效果以前還清除前一渲染效果的緣由。咱們將討論爲何這有助於避免錯誤以及如何在之後發生性能問題時選擇退出此行爲。
注意 咱們沒必要從效果中返回命名函數。咱們在這裏只是爲了說明才加的命名,但你能夠返回箭頭函數。
咱們已經瞭解到useEffect
讓咱們在組件渲染後表達不一樣類型的反作用。某些效果可能須要清理,所以它們返回一個函數:
useEffect(() : {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () : {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
複製代碼
其餘效果可能沒有清理階段,也不會返回任何內容。好比:
useEffect(() : {
document.title = `You clicked ${count} times`;
});
複製代碼
若是你以爲你對Effect Hook
的工做方式有了很好的把握,或者你感到不知所措,那麼如今就能夠跳轉到關於Hooks
規則。
咱們將繼續深刻了解使用React
用戶可能會產生好奇心的useEffect
的某些方面。
這是一個組合了前面示例中的計數器和朋友狀態指示器邏輯的組件:
class FriendStatusWithCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0, isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
// ...
複製代碼
請注意設置document.title
的邏輯如何在componentDidMount
和componentDidUpdate
之間拆分。訂閱邏輯也在componentDidMount
和componentWillUnmount
之間傳播。componentDidMount
包含兩個任務的代碼。
那麼,Hooks
如何解決這個問題呢?就像你能夠屢次使用狀態掛鉤同樣,你也可使用多種效果。這讓咱們將不相關的邏輯分紅不一樣的效果:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() : {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() : {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () : {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
}
複製代碼
Hooks
容許咱們根據它正在作的事情而不是生命週期方法名稱來拆分代碼。 React
將按照指定的順序應用組件使用的每一個效果。
若是你習慣了類,你可能想知道爲何每次從新渲染後效果的清理階段都會發生,而不是在卸載過程當中只發生一次。讓咱們看一個實際的例子,看看爲何這個設計能夠幫助咱們建立更少bug的組件。
在上面介紹了一個示例FriendStatus
組件,該組件顯示朋友是否在線。咱們的類從this.props
讀取friend.id
,在組件掛載後訂閱朋友狀態,並在卸載期間取消訂閱:
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
複製代碼
可是若是friend prop
在組件出如今屏幕上時發生了變化,會發生什麼? 咱們的組件將繼續顯示不一樣朋友的在線狀態。這是一個錯誤。卸載時咱們還會致使內存泄漏或崩潰,由於取消訂閱會使用錯誤的朋友ID。
在類組件中,咱們須要添加componentDidUpdate
來處理這種狀況:
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate(prevProps) {
// Unsubscribe from the previous friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// Subscribe to the next friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
複製代碼
忘記正確處理componentDidUpdate
是React
應用程序中常見的bug
漏洞。
如今考慮使用Hooks的這個組件的版本:
function FriendStatus(props) {
// ...
useEffect(() : {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () : {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
複製代碼
它不會受到這個bug
的影響。 (但咱們也沒有對它作任何改動。)
沒有用於處理更新的特殊代碼,由於默認狀況下useEffect會處理它們。它會在應用下一個效果以前清除以前的效果。爲了說明這一點,這裏是一個訂閱和取消訂閱調用的序列,該組件能夠隨着時間的推移產生:
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect
// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect
// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect
複製代碼
此行爲默認確保一致性,並防止因爲缺乏更新邏輯而致使類組件中常見的錯誤。
在某些狀況下,在每次渲染後清理或應用效果可能會產生性能問題。在類組件中,咱們能夠經過在componentDidUpdate
中編寫與prevProps
或prevState
的額外比較來解決這個問題:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
複製代碼
這個要求很常見,它被內置到useEffect Hook API
中。若是在從新渲染之間沒有更改某些值,則能夠告訴React
跳過應用效果。爲此,將數組做爲可選的第二個參數傳遞給useEffect
:
useEffect(() : {
document.title = `You clicked ${count} times`;
}, [count]); // 當count改變的時候回再次運行這個效果
複製代碼
在上面的例子中,咱們傳遞[count]
做爲第二個參數。這是什麼意思?若是count
爲5,而後咱們的組件從新渲染,count
仍然等於5,則React
將比較前一個渲染的[5]和下一個渲染的[5]。由於數組中的全部項都是相同的(5 === 5
),因此React
會跳過這個效果。這是咱們的優化。
當咱們使用count
更新爲6渲染時,React
會將前一渲染中[5]數組中的項目與下一渲染中[6]數組中的項目進行比較。此次,React
將從新運行效果,由於5!== 6
。若是數組中有多個項目,React
將從新運行效果,即便其中只有一個不一樣。
這也適用於具備清理階段的效果:
useEffect(() : {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () : {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes
複製代碼
未來, 第二個參數可能會經過構建時轉換自動添加。
注意 若是使用此優化,請確保該數組包含外部做用域中隨時間變化且效果使用的任何值,換句話說就是要在這個效果函數裏有意義。 不然,代碼將引用先前渲染中的舊值。咱們還將討論
Hooks API
參考中的其餘優化選項。若是要運行效果並僅將其清理一次(在裝載和卸載時),則能夠將空數組([])做爲第二個參數傳遞。 這告訴
React
你的效果不依賴於來自props
或state
的任何值,因此它永遠不須要從新運行。這不做爲特殊狀況處理 - 它直接遵循輸入數組的工做方式。雖然傳遞[]更接近熟悉的componentDidMount
和componentWillUnmount
生命週期,但咱們建議不要將它做爲一種習慣,由於它常常會致使錯誤,除非你明確你本身在作什麼, 如上所述。 不要忘記React
推遲運行useEffect
直到瀏覽器繪製完成後,因此作額外的工做不是問題。