jsonp react 获取返回值_谈谈对 React 新旧生命周期的理解
前言
在寫這篇文章的時候,React 已經出了 17.0.1 版本了,雖說還來討論目前 React 新舊生命周期有點晚了,React 兩個新生命周期雖然出了很久,但實際開發我卻沒有用過,因為 React 16 版本后我們直接 React Hook 起飛開發項目。
但對新舊生命周期的探索,還是有助于我們更好理解 React 團隊一些思想和做法,于是今天就要回顧下這個問題和理解總結,雖然還是 React Hook 寫法香,但是依然要深究學習類組件的東西,了解 React 團隊的一些思想與做法。
本文只討論 React17 版本前的。
React 16 版本后做了什么
首先是給三個生命周期函數加上了 UNSAFE:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate
這里并不是表示不安全的意思,它只是不建議繼續使用,并表示使用這些生命周期的代碼可能在未來的 React 版本(目前 React17 還沒有完全廢除)存在缺陷,如 React Fiber 異步渲染的出現。
同時新增了兩個生命周期函數:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillReceiveProps(nextProps)先來說說這個函數,componentWillReceiveProps
該子組件方法并不是父組件 props 改變才觸發,官方回答是:
?如果父組件導致組件重新渲染,即使 props 沒有更改,也會調用此方法。如果只想處理更改,請確保進行當前值與變更值的比較。
?先來說說 React 為什么廢除該函數,廢除肯定有它不好的地方。
componentWillReceiveProps函數的一般使用場景是:
- 如果組件自身的某個 state 跟父組件傳入的 props 密切相關的話,那么可以在該方法中判斷前后兩個 props 是否相同,如果不同就根據 props 來更新組件自身的 state。類似的業務需求比如:一個可以橫向滑動的列表,當前高亮的 Tab 顯然隸屬于列表自身的狀態,但很多情況下,業務需求會要求從外部跳轉至列表時,根據傳入的某個值,直接定位到某個 Tab。
但該方法缺點是會破壞 state 數據的單一數據源,導致組件狀態變得不可預測,另一方面也會增加組件的重繪次數。
而在新版本中,官方將更新 state 與觸發回調重新分配到了 getDerivedStateFromProps 與 componentDidUpdate 中,使得組件整體的更新邏輯更為清晰。
新生命周期方法static getDerivedStateFromProps(props, state)怎么用呢?
?getDerivedStateFromProps 會在調用 render 方法之前調用,并且在初始掛載及后續更新時都會被調用。它應返回一個對象來更新 state,如果返回 null 則不更新任何內容。
?從函數名字就可以看出大概意思:使用 props 來派生/更新 state。這就是重點了,但凡你想使用該函數,都必須出于該目的,使用它才是正確且符合規范的。
跟getDerivedStateFromProps不同的是,它在掛載和更新階段都會執行(componentWillReceiveProps掛載階段不會執行),因為更新 state 這種需求不僅在 props 更新時存在,在 props 初始化時也是存在的。
而且getDerivedStateFromProps在組件自身 state 更新也會執行而componentWillReceiveProps方法執行則取決于父組件的是否觸發重新渲染,也可以看出getDerivedStateFromProps并不是 componentWillReceiveProps方法的替代品.
引起我們注意的是,這個生命周期方法是一個靜態方法,靜態方法不依賴組件實例而存在,故在該方法內部是無法訪問 this 的。新版本生命周期方法能做的事情反而更少了,限制我們只能根據 props 來派生 state,官方是基于什么考量呢?
因為無法拿到組件實例的 this,這也導致我們無法在函數內部做 this.fetch()請求,或者不合理的 this.setState()操作導致可能的死循環或其他副作用。有沒有發現,這都是不合理不規范的操作,但開發者們都有機會這樣用。可如果加了個靜態 static,間接強制我們都無法做了,也從而避免對生命周期的濫用。
React 官方也是通過該限制,盡量保持生命周期行為的可控可預測,根源上幫助了我們避免不合理的編程方式,即一個 API 要保持單一性,做一件事的理念。
如下例子:
//?beforecomponentWillReceiveProps(nextProps)?{
??if?(nextProps.isLogin?!==?this.props.isLogin)?{
????this.setState({
??????isLogin:?nextProps.isLogin,
????});
??}
??if?(nextProps.isLogin)?{
????this.handleClose();
??}
}
//?after
static?getDerivedStateFromProps(nextProps,?prevState)?{
??if?(nextProps.isLogin?!==?prevState.isLogin)?{?//?被對比的props會被保存一份在state里
????return?{
??????isLogin:?nextProps.isLogin,?//?getDerivedStateFromProps?的返回值會自動?setState
????};
??}
??return?null;
}
componentDidUpdate(prevProps,?prevState)?{
??if?(!prevState.isLogin?&&?this.props.isLogin)?{
????this.handleClose();
??}
}
UNSAVE_componentWillMount
?UNSAFE_componentWillMount() 在掛載之前被調用。它在 render() 之前調用,因此在此方法中同步調用 setState() 不會觸發額外渲染。
?我們應該避免在此方法中引入任何副作用或事件訂閱,而是選用componentDidMount()。
在 React 初學者剛接觸的時候,可能有這樣一個疑問:一般都是數據請求放在componentDidMount里面,但放在componentWillMount不是會更快獲取數據嗎?
因為理解是componentWillMount在 render 之前執行,早一點執行就早拿到請求結果;但是其實不管你請求多快,都趕不上首次 render,頁面首次渲染依舊處于沒有獲取異步數據的狀態。
還有一個原因,componentWillMount是服務端渲染唯一會調用的生命周期函數,如果你在此方法中請求數據,那么服務端渲染的時候,在服務端和客戶端都會分別請求兩次相同的數據,這顯然也我們想看到的結果。
特別是有了 React Fiber,更有機會被調用多次,故請求不應該放在componentWillMount中。
還有一個錯誤的使用是在componentWillMount中訂閱事件,并在componentWillUnmount中取消掉相應的事件訂閱。事實上只有調用componentDidMount后,React 才能保證稍后調用componentWillUnmount進行清理。而且服務端渲染時不會調用componentWillUnmount,可能導致內存泄露。
還有人會將事件監聽器(或訂閱)添加到 componentWillMount 中,但這可能導致服務器渲染(永遠不會調用 componentWillUnmount)和異步渲染(在渲染完成之前可能被中斷,導致不調用 componentWillUnmount)的內存泄漏。
對于該函數,一般情況,如果項目有使用,則是通常把現有 componentWillMount 中的代碼遷移至 componentDidMount 即可。
UNSAFE_componentWillUpdate
?當組件收到新的 props 或 state 時,會在渲染之前調用 UNSAFE_componentWillUpdate()。使用此作為在更新發生之前執行準備更新的機會。初始渲染不會調用此方法。
?注意,不能在該方法中調用 this.setState();在 componentWillUpdate 返回之前,你也不應該執行任何其他操作(例如,dispatch Redux 的 action)觸發對 React 組件的更新。
首先跟上面兩個函數一樣,該函數也發生在 render 之前,也存在一次更新被調用多次的可能,從這一點上看就依然不可取了。
其次,該方法常見的用法是在組件更新前,讀取當前某個 DOM 元素的狀態,并在 componentDidUpdate 中進行相應的處理。但 React 16 版本后有 suspense、異步渲染機制等等,render 過程可以被分割成多次完成,還可以被暫停甚至回溯,這導致 componentWillUpdate 和 componentDidUpdate 執行前后可能會間隔很長時間,這導致 DOM 元素狀態是不安全的,因為這時的值很有可能已經失效了。而且足夠使用戶進行交互操作更改當前組件的狀態,這樣可能會導致難以追蹤的 BUG。
為了解決這個問題,于是就有了新的生命周期函數:
getSnapshotBeforeUpdate(prevProps,?prevState)?
getSnapshotBeforeUpdate 在最近一次渲染輸出(提交到 DOM 節點)之前調用。它使得組件能在發生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)。此生命周期的任何返回值將作為第三個參數傳入componentDidUpdate(prevProps, prevState, snapshot)
?與 componentWillUpdate 不同,getSnapshotBeforeUpdate 會在最終的 render 之前被調用,也就是說在 getSnapshotBeforeUpdate 中讀取到的 DOM 元素狀態是可以保證與 componentDidUpdate 中一致的。
雖然 getSnapshotBeforeUpdate 不是一個靜態方法,但我們也應該盡量使用它去返回一個值。這個值會隨后被傳入到 componentDidUpdate 中,然后我們就可以在 componentDidUpdate 中去更新組件的狀態,而不是在 getSnapshotBeforeUpdate 中直接更新組件狀態。避免了 componentWillUpdate 和 componentDidUpdate 配合使用時將組件臨時的狀態數據存在組件實例上浪費內存,getSnapshotBeforeUpdate 返回的數據在 componentDidUpdate 中用完即被銷毀,效率更高。
來看官方的一個例子:
class?ScrollingList?extends?React.Component?{??constructor(props)?{
????super(props);
????this.listRef?=?React.createRef();
??}
??getSnapshotBeforeUpdate(prevProps,?prevState)?{
????//?我們是否在 list 中添加新的 items?
????//?捕獲滾動位置以便我們稍后調整滾動位置。
????if?(prevProps.list.length???????const?list?=?this.listRef.current;
??????return?list.scrollHeight?-?list.scrollTop;
????}
????return?null;
??}
??componentDidUpdate(prevProps,?prevState,?snapshot)?{
????//?如果我們?snapshot?有值,說明我們剛剛添加了新的?items,
????//?調整滾動位置使得這些新 items 不會將舊的 items 推出視圖。
????//(這里的?snapshot?是?getSnapshotBeforeUpdate?的返回值)
????if?(snapshot?!==?null)?{
??????const?list?=?this.listRef.current;
??????list.scrollTop?=?list.scrollHeight?-?snapshot;
????}
??}
??render()?{
????return?(
??????{/*?...contents...?*/}
????);
??}
}
如果項目中有用到componentWillUpdate的話,升級方案就是將現有的 componentWillUpdate 中的回調函數遷移至 componentDidUpdate。如果觸發某些回調函數時需要用到 DOM 元素的狀態,則將對比或計算的過程遷移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中統一觸發回調或更新狀態。
除了這些,React 16 版本的依然還有大改動,其中引人注目的就是 Fiber,之后我還會抽空寫一篇關于 React Fiber 的文章,可以關注我的個人技術博文 Github 倉庫,覺得不錯的話歡迎 star,給我一點鼓勵繼續寫作吧~
參考:
- 為什么廢棄 react 生命周期函數
- React v16.3 版本新生命周期函數淺析及升級方案
歡迎關注我掘金賬號和Github技術博客:
- 掘金:https://juejin.im/user/1257497033714477
- Github:https://github.com/Jacky-Summer
- 覺得對你有幫助或有啟發的話歡迎 star,你的鼓勵是我持續創作的動力~
- 如需在微信公眾號平臺轉載請聯系作者授權同意,其它途徑轉載請在文章開頭注明作者和文章出處。
總結
以上是生活随笔為你收集整理的jsonp react 获取返回值_谈谈对 React 新旧生命周期的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美食,有名的+一道+水东鸭粥+是+家乡+
- 下一篇: 玉米烙的家常做法?