變更的部分
react v16.3終於出來了,最大的變動莫過於生命週期去掉了以下三個
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
同時為了彌補失去上面三個週期的不足又加了兩個
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
當然,這個更替是緩慢的,在整個16版本里都能無障礙的使用舊的三生命週期,但值得注意的是,舊的生命週期(unsafe)不能和新的生命週期同時出現在一個元件,否則會報錯“你使用了一個不安全的生命週期”。
為什麼要改
舊的生命週期十分完整,基本可以捕捉到元件更新的每一個state/props/ref,沒有什從邏輯上的毛病。
但是架不住官方自己搞事情,react打算在17版本推出新的Async Rendering,提出一種可被打斷的生命週期,而可以被打斷的階段正是實際dom掛載之前的虛擬dom構建階段,也就是要被去掉的三個生命週期。
生命週期一旦被打斷,下次恢復的時候又會再跑一次之前的生命週期,因此componentWillMount,componentWillReceiveProps, componentWillUpdate都不能保證只在掛載/拿到props/狀態變化的時候重新整理一次了,所以這三個方法被標記為不安全。
兩個新生命週期
static getDerivedStateFromProps
- 觸發時間(v16.4修正):元件每次被rerender的時候,包括在元件構建之後(render之前最後執行),每次獲取新的props或state之後。在v16.3版本時,元件state的更新不會觸發該生命週期。
- 每次接收新的props之後都會返回一個物件作為新的state,返回null則說明不需要更新state.
- 配合componentDidUpdate,可以覆蓋componentWillReceiveProps的所有用法
class Example extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
// 沒錯,這是一個static
}
}
複製程式碼
getSnapshotBeforeUpdate
- 觸發時間: update發生的時候,在render之後,在元件dom渲染之前。
- 返回一個值,作為componentDidUpdate的第三個引數。
- 配合componentDidUpdate, 可以覆蓋componentWillUpdate的所有用法。
class Example extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}
複製程式碼
建議用法總結
-
初始化state — Initializing state
- 在constructor初始化state就可以了
-
請求資料 — Fetching external data
- 在componentDidMount請求非同步載入的資料
- 有一種錯覺,在componentWillMount請求的資料在render就能拿到,但其實render在willMount之後幾乎是馬上就被呼叫,根本等不到資料回來,同樣需要render一次“載入中”的空資料狀態,所以在didMount去取資料幾乎不會產生影響。
-
新增事件監聽 — Adding event listeners (or subscriptions)
- 在componentDidMount中新增加事件監聽
- react只能保證componentDidMount-componentWillUnmount成對出現,componentWillMount可以被打斷或呼叫多次,因此無法保證事件監聽能在unmount的時候被成功解除安裝,可能會引起記憶體洩露
-
根據props更新state — Updating state based on props
- 用getDerivedStateFromProps(nextProps, prevState), 將傳入的props更新到state上
- 用來代替componentWillReceiveProps(nextProps, nextState),willReceiveProps經常被誤用,導致了一些問題,因此該方法將不被推薦使用。
- getDerivedStateFromProps是一個static方法,意味著拿不到例項的this,所以想要在setState之前比對一下props有沒有更新,下面方法是不能用了
if (this.props.currentRow !== nextProps.currentRow) { ... } 複製程式碼
取而代之的是,額外寫一個state來記錄上一個props (` ^ ‘)
if (nextProps.currentRow !== prevState.lastRow) { return { ... lastRow: nextProps.currentRow, }; // 不更新state return null } 複製程式碼
- 為什麼我們不給一個prevProps引數呢,官方解釋是,一來prevProps第一次被呼叫的時候是null,每次更新都要判斷耗效能,二來如果大家都習慣了,以後react不記錄prevProps的話(啥),可以省下不少記憶體
-
觸發請求 — Invoking external callbacks
- 在生命週期中由於state的變化觸發請求,在componentDidUpdate中進行
- 為什麼不在componentWillUpdate中的理由同上2
-
props更新引起的副作用 — Side effects on props change
- props更改引發的可視變化(副作用,比如log,ga),在componentDidUpdate中處理
// 在didUpdate中根據props更新的確很不適應 // props變了也是可以觸發update的 componentDidUpdate(prevProps, prevState) { if (this.props.isVisible !== prevProps.isVisible) { logVisibleChange(this.props.isVisible); } } 複製程式碼
- componentWillUpdate, componentWillReceiveProps在一次更新中可能會被觸發多次,因此這種只希望觸發一次的副作用應該放在保證只觸發一次的componentDidUpdate中。
-
props更新時重新請求 — Fetching external data when props change
- 傳入新的props時重新非同步取資料,getDerivedStateFromProps+ componentDidUpdate 替代 componentWillReceiveProps
// old componentWillReceiveProps(nextProps) { if (nextProps.id !== this.props.id) { this.setState({externalData: null}); this._loadAsyncData(nextProps.id); } } 複製程式碼
// new static getDerivedStateFromProps(nextProps, prevState) { // Store prevId in state so we can compare when props change. if (nextProps.id !== prevState.prevId) { return { externalData: null, prevId: nextProps.id, }; } // No state update necessary return null; } componentDidUpdate(prevProps, prevState) { if (this.state.externalData === null) { this._loadAsyncData(this.props.id); } } 複製程式碼
-
在更新前記錄原來的dom節點屬性 — Reading DOM properties before an update
- 在upate之前獲取dom節點,getSnapshotBeforeUpdate(prevProps, prevState)代替componentWillUpdate(nextProps, nextState)
- getSnapshotBeforeUpdate在render之後,但在節點掛載前
- componentDidUpdate(prevProps, prevState, snapshot)直接獲得getSnapshotBeforeUpdate返回的dom屬性值
生命週期功能替換一覽
static getDerivedStateFromProps(nextProps, prevState) {
4. Updating state based on props
7. Fetching external data when props change // Clear out previously-loaded data so we dont render stale stuff
}
constructor() {
1. Initializing state
}
componentWillMount() {
// 1. Initializing state
// 2. Fetching external data
// 3. Adding event listeners (or subscriptions)
}
componentDidMount() {
2. Fetching external data
3. Adding event listeners (or subscriptions)
}
componentWillReceiveProps() {
// 4. Updating state based on props
// 6. Side effects on props change
// 7. Fetching external data when props change
}
shouldComponentUpdate() {
}
componentWillUpdate(nextProps, nextState) {
// 5. Invoking external callbacks
// 8. Reading DOM properties before an update
}
render() {
}
getSnapshotBeforeUpdate(prevProps, prevState) {
8. Reading DOM properties before an update
}
componentDidUpdate(prevProps, prevState, snapshot) {
5. Invoking external callbacks
6. Side effects on props change
}
componentWillUnmount() {
}
複製程式碼
最後
上面的內容基本就是結合我的一些經驗半翻譯半總結,有不準確的地方歡迎指正。
對更多細節感興趣的話可以去看官方文件,https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
對async-rendering或者對dan小哥哥感興趣的話可以去看看他在前端大會上的一段小演示:https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html