組件的生命周期
掛載
當組件實例被創建并插入 DOM 中時,其生命周期調用順序如下:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
componentWillMount() 之后將廢棄
更新
當組件的 props 或 state 發生變化時會觸發更新。組件更新的生命周期調用順序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
componentWillUpdate() 、 componentWillReceiveProps() 之后將被廢棄
卸載
當組件從 DOM 中移除時會調用如下方法:
- componentWillUnmount()
錯誤處理
當渲染過程,生命周期,或子組件的構造函數中拋出錯誤時,會調用如下方法:
- static getDerivedStateFromError()
- componentDidCatch()
組件還提供了一些額外的 API:
- setState()
- forceUpdate()
單獨介紹
getDerivedStateFromProps
這個生命周期函數是為了替代 componentWillReceiveProps 存在的,所以在你需要使用componentWillReceiveProps的時候,就可以考慮使用getDerivedStateFromProps來進行替代了。
兩者的參數是不相同的,而getDerivedStateFromProps是一個靜態函數
,也就是這個函數不能通過this訪問到class的屬性
,也并不推薦直接訪問屬性。
而是應該通過參數提供的nextProps以及prevState來進行判斷,根據新傳入的props來映射到state。
需要注意的是,如果props傳入的內容不需要影響到你的state,那么就需要返回一個null,這個返回值是必須的,所以盡量將其寫到函數的末尾。
static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;// 當傳入的type發生變化的時候,更新stateif (type !== prevState.type) {return {type,};}// 否則,對于state不進行任何操作return null;
}
如果你的組件內部既需要修改自己的type,又需要接收從外部修改的type。
static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;// type可能由props驅動,也可能由state驅動,這樣判斷會導致state驅動的type被回滾if (type !== prevState.type) {return {type,};}// 否則,對于state不進行任何操作return null;
}
在非必須的時候,摒棄這種寫法。type要么由props驅動,要么完全由state驅動。
如果實在沒有辦法解耦,那么就需要一個hack來輔助:綁定props到state上。
constructor(props) {super(props);this.state = {type: 0,props,}
}
static getDerivedStateFromProps(nextProps, prevState) {const {type, props} = nextProps;// 這段代碼可能看起來非常混亂,這個props可以被當做緩存,僅用作判斷if (type !== props.type) {return {type,props: {type,},};}// 否則,對于state不進行任何操作return null;
}
上面的代碼可以保證在進行多數據源驅動的時候,狀態能夠正確改變。當然,這樣的代碼很多情況下是會影響到別人閱讀你的代碼的,對于維護造成了非常大的困難。
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)之前調用。它使得組件能在發生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)。此生命周期的任何返回值將作為參數傳遞給 componentDidUpdate()。
此用法并不常見,但它可能出現在 UI 處理中,如需要以特殊方式處理滾動位置的聊天線程等。
應返回 snapshot 的值(或 null)。
class ScrollingList extends React.Component {constructor(props) {super(props);this.listRef = React.createRef();}getSnapshotBeforeUpdate(prevProps, prevState) {// 我們是否在 list 中添加新的 items ?// 捕獲滾動位置以便我們稍后調整滾動位置。if (prevProps.list.length < this.props.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 (<div ref={this.listRef}>{/* ...contents... */}</div>);}
}
在上述示例中,重點是從 getSnapshotBeforeUpdate 讀取 scrollHeight 屬性,因為 “render” 階段生命周期(如 render)和 “commit” 階段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之間可能存在延遲。
Error boundaries
Error boundaries 是 React 組件,它會在其子組件樹中的任何位置捕獲 JavaScript 錯誤,并記錄這些錯誤,展示降級 UI 而不是崩潰的組件樹。Error boundaries 組件會捕獲在渲染期間,在生命周期方法以及其整個樹的構造函數中發生的錯誤。
如果 class 組件定義了生命周期方法 static getDerivedStateFromError() 或 componentDidCatch() 中的任何一個(或兩者),它就成為了 Error boundaries。通過生命周期更新 state 可讓組件捕獲樹中未處理的 JavaScript 錯誤并展示降級 UI。
僅使用 Error boundaries 組件來從意外異常中恢復的情況;不要將它們用于流程控制。
static getDerivedStateFromError(error)
此生命周期會在后代組件拋出錯誤后被調用。 它將拋出的錯誤作為參數,并返回一個值以更新 state
class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false };}static getDerivedStateFromError(error) {// 更新 state 使下一次渲染可以顯降級 UIreturn { hasError: true };}render() {if (this.state.hasError) {// 你可以渲染任何自定義的降級 UIreturn <h1>Something went wrong.</h1>;}return this.props.children; }
}
注意:getDerivedStateFromError() 會在渲染階段調用,因此不允許出現副作用。 如遇此類情況,請改用 componentDidCatch()。
componentDidCatch(error, info)
componentDidCatch() 會在“提交”階段被調用,因此允許執行副作用。 它應該用于記錄錯誤之類的情況:
class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false };}static getDerivedStateFromError(error) {// 更新 state 使下一次渲染可以顯示降級 UIreturn { hasError: true };}componentDidCatch(error, info) {// "組件堆棧" 例子:// in ComponentThatThrows (created by App)// in ErrorBoundary (created by App)// in div (created by App)// in ApplogComponentStackToMyService(info.componentStack);}render() {if (this.state.hasError) {// 你可以渲染任何自定義的降級 UIreturn <h1>Something went wrong.</h1>;}return this.props.children; }
}
setState的異步操作
因為setState是異步的,所有有時setState后獲取this.state數據并不準確,可以如下操作:
state = {name:'initName'
}this.setState((state,props)=>{console.log(state,props);return {name:'name-change'};
})const {name}=this.state;
this.setState({name:'name-change'},()=>{console.log(this.state.name,name) // name-change initName
})
官方文檔:https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromprops