若是說你是一位經驗豐富的 React 工程師,看到這邊文章講的是 React 的生命週期,已經看過無數篇關於 React 生命週期的文章的你,可能有關閉頁面的想法。html
請不要急着退出,我想講一些不同的、來自將來的。react
2018.5.24 更新:安全
今天 React 16.4 發佈了,
getDerivedStateFromProps
的觸發時機有所改動——本來在父組件從新渲染它時纔會觸發getDerivedStateFromProps
,現改成只要渲染就會觸發,包括自身setState
。這一變化進一步地區分了該方法與componentWillReceiveProps
,更適合異步渲染。異步
你們都知道,如今關於 React 生命週期的解析、教程、深刻解讀等文章早已數不勝數。那麼爲什麼我又要來重描一遍?由於 React 的生命週期即將改變 !React 團隊目前致力於打造異步渲染的機制,以進一步提升 React 的渲染效能,在這一過程當中,他們大刀闊斧地對生命週期作出了若干改革,以更好地適應異步渲染的須要。async
簡單說來,刪除了 3 個生命週期方法,增長了另外 2 個:性能
+: 新增 -: 刪除 ?: 有變化
- componentWillMount
render
componentDidMount
- componentWillReceiveProps
+ static getDerivedStateFromProps
shouldComponentUpdate
- componentWillUpdate
+ getSnapshotBeforeUpdate
? componentDidUpdate
componentWillUnmount
複製代碼
class Example extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
// 這一輩子命週期方法是靜態的,它在組件實例化或接收到新的 props 時被觸發
// 若它的返回值是對象,則將被用於更新 state ;如果 null ,則不觸發 state 的更新
// 配合 `componentDidUpdate` 使用,這一方法能夠取代 `componentWillReceiveProps`
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 該方法在實際改動(好比 DOM 更新)發生前的「瞬間」被調用,返回值將做爲 `componentDidUpdate` 的第三個參數
// 配合 `componentDidUpdate` 使用,這一方法能夠取代 `componentWillUpdate`
}
componentDidUpdate(props, state, snaptshot) {
// 新增的參數 snapshot 便是以前調用 getSnapshotBeforeUpdate 的返回值
}
}
複製代碼
移除這一輩子命週期方法緣由以下:this
constructor
和 componentDidMount
舉幾個例子:spa
部分經驗不足的開發者可能誤把 事件綁定 和 異步獲取數據 的代碼置於 componentWillMount
中,致使code
a. 服務端渲染會觸發這一輩子命週期方法,但因每每忽略異步獲取的數據而白白請求 或 因服務端不觸發 componentWillUnmount
而沒法取消事件監聽,致使內存泄漏
b. 結合 1.
,屢次調用會致使發出重複的請求 或 進行重複監聽,後者亦會致使內存泄漏component
最佳實踐:
class ExampleComponent extends React.Component {
state = {
subscribedValue: this.props.dataSource.value,
externalData: null,
};
componentDidMount() {
// 這裏只觸發一次,能夠安全地進行 異步請求、事件綁定
this.asyncRequest = asyncLoadData().then(
externalData => {
this.asyncRequest = null;
this.setState({externalData});
}
);
this.props.dataSource.subscribe(this.handleSubscriptionChange);
}
componentWillUnmount() {
if (this.asyncRequest) this.asyncRequest.cancel();
// 事件綁定在客戶端才進行,且只進行一次,在這裏能夠安全地解綁
this.props.dataSource.unsubscribe(this.handleSubscriptionChange);
}
handleSubscriptionChange = dataSource => {
this.setState({ subscribedValue: dataSource.value });
};
}
複製代碼
來看一段常見的 componentWillReceiveProps
的用法:
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
};
componentWillReceiveProps(nextProps) {
if (this.props.currentRow !== nextProps.currentRow) {
// 檢測到變化後更新狀態、並請求數據
this.setState({
isScrollingDown: nextProps.currentRow > this.props.currentRow,
});
this.loadAsyncData()
}
}
loadAsyncData() {/* ... */}
}
複製代碼
這段代碼其實沒有什麼大問題,但存在更好的寫法。當使用 getDerivedStateFromProps
,能夠這樣寫:
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
lastRow: null,
};
static getDerivedStateFromProps(nextProps, prevState) {
// 再也不提供 prevProps 的獲取方式
if (nextProps.currentRow !== prevState.lastRow) {
return {
isScrollingDown: nextProps.currentRow > prevState.lastRow,
lastRow: nextProps.currentRow,
};
}
// 默認不改動 state
return null;
}
componentDidUpdate() {
// 僅在更新觸發後請求數據
this.loadAsyncData()
}
loadAsyncData() {/* ... */}
}
複製代碼
能夠看到在這種狀況下,開發者們將更「自發地」採起在 componentDidUpdate
編寫觸發異步請求的代碼(由於別無選擇:P),避免了一個問題——外部組件屢次頻繁更新傳入屢次不一樣的 props,而該組件將這些更新 batch 後僅僅觸發單次本身的更新,如此一來前者的寫法會致使沒必要要的異步請求,後者更節省資源。
componentWillUpdate
的常見用法是,在更新前記錄 DOM 狀態,結合更新後 componentDidUpdate
再次獲取的 DOM 狀態進行必要的處理。異步渲染的到來,使得 componentWillUpdate
的觸發時機(它在異步渲染被取締,但此處咱們假想它仍然存在)與 componentDidUpdate
的觸發時機間隔較大,由於異步渲染隨時可能暫緩這一組件的更新。這樣一來,以前的作法將變得不夠穩定,由於這間隔久到 DOM 可能由於用戶行爲發生了變化。
爲此,React 提供了 getSnapshotBeforeUpdate
。它的觸發時機是 React 進行修改前(一般是更新 DOM)的「瞬間」 ,這樣一來在此獲取到的 DOM 信息甚至比 componentWillUpdate
更加可靠。此外,它的返回值會做爲第三個參數傳入 componentDidUpdate
,這樣作的好處很明顯——開發者能夠沒必要將爲了協調渲染先後狀態之用而產生的數據保存在組件實例上,用完便可銷燬。
React 自從邁上 16 的版本號,就像坐上了火箭,性能與 API 的進化使人矚目。在不久的未來,異步渲染 將正式登場,帶來渲染性能又一輪突破。本文所討論的生命週期變化中,能夠看到 React 團隊爲這一變化所作鋪墊的良苦用心——舊 API 的移除,警醒或潛移默化地使開發者們遵循更加優秀的開發方式。從 v16.3 開始, 舊的 API 將逐漸被替代,而等到了 v17 將完全廢棄,看到這裏的各位能夠開始考慮給本身的項目升升級啦!
參考連接: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html