- Published on
Class-based Component 生命週期方法(續)
- Authors
- Name
- Penghua Chen(PH)
Class-based Component 生命週期方法(續)
今天的篇幅要延續昨天的部分繼續,要來看看在官方文件中如何定義了這些方法,以及哪些是常用的、應用在什麼地方。
學習順序會依照元件建立、更新及銷毀的生命週期順序來學習。
並在最後透過發一個非同步請求的範例來作為 ending,事不宜遲,趕緊來學習吧!
元件(Component)建立時(mounting)的生命週期方法
所有方法:
constructor
static getDerivedStateFromProps
render
componentDidMount
常用方法:
constructor
render
componentDidMount
contructor
首先在 mounting 階段時,最先被初始化的是 constructor
的部分,而這部分同時也是 JS class 類別,文件中提到:
通常在 React 中 constructor 只會有兩種用途:
- 透過指定一個 this.state 物件來初始化內部 state。
- 為 event handler 方法綁定 instance。
於是我們常常會有如下的設計:
import React, { Component } from 'react';
class Test extends Component{
constructor() {
super();
this.state = {
persons: [
{ name: 'Bill' }
]
};
this.changeNameHandler = () => {
// do something...
}
}
}
而由於 ES7 中將 Class 的寫法簡化,所以我們可以將上方改寫成如下的方式:
import React, { Component } from 'react';
class Test extends Component{
state = {
persons: [
{ name: 'Bill' }
]
}
changeNameHandler = () => {
// do something...
}
}
此外文件中還有提到:
Constructor 應該要是開發者唯一指定 state 的地方,其餘地方則是透過 this.setState 的方式修改 state 的狀態。
==直接修改 state 的資料是雖然會改變 state 的狀態,但是並不會觸發畫面的更新。==
static getDerivedStateFromProps(props, state)
當子元件中具有自己的 state,並且這個 state
的改變是依賴父層 props
進來的值,那就可能需要用到這個方法。
此時會需要回傳一個物件來更新 state
,但是如果並不需要更新的話,那其實也可以直接回傳 null 即可。
這邊我們設計一種情境:
父元件、子元件中皆有一個 radio box,切換任意一個的狀態時,都需要可以同步更新另一個 radio box 的狀態。
相關測試程式碼,點擊前往
// App.js
import React, { Component } from 'react';
import './App.css';
import SubComponent from './SubComponent';
class App extends Component {
constructor() {
super();
this.state = {
isTrue: true
};
}
changeRadioHandler = e => {
const value = e.target.value;
const checkTrue = value === 'true'
? true
: false;
this.setState(state => {
return {
isTrue: checkTrue
}
})
}
render() {
return (
<div className="App">
{ this.state.persons.map((person, idx) => (
<Card
key={ idx }
changeRadioHandler={ this.changeRadioHandler }
isTrue={ this.state.isTrue }
/>
))}
<br />
True: <input onChange={ e => this.changeRadioHandler(e) } checked={ this.state.isTrue } type="radio" name="main" value="true"/>
False: <input onChange={ e => this.changeRadioHandler(e) } checked={ !this.state.isTrue } type="radio" name="main" value="false"/>
</div>
);
}
}
export default App;
// SubComponent.js
import React, { Component } from 'react';
import './index.css';
class SubComponent extends Component {
constructor() {
super();
this.state = {
isTrue: true
}
}
static getDerivedStateFromProps(props, state) {
console.log(props);
console.log(state);
if(props.isTrue != state.isTrue) {
return {
isTrue: props.isTrue
};
} else {
return null;
}
}
render() {
return (
<div className="sub">
<h1>Sub Component</h1>
<br />
True: <input onChange={ e => this.props.changeRadioHandler(e) } checked={ this.state.isTrue } type="radio" name="sub" value="true"/>
False: <input onChange={ e => this.props.changeRadioHandler(e) } checked={ !this.state.isTrue } type="radio" name="sub" value="false"/>
</div>
);
}
}
export default SubComponent;
在 SubComponent
元件中使用了 getDerivedStateFromProps
方法,可以透過 props 取得外層傳入的最新值,透過比對元件自身的 state 決定是否要更新子元件中的 state 狀態,而這個就是一個簡單的使用情境。
render
render()
是 class component 中唯一必要的方法。- 透過
render
方法可以在瀏覽器上渲染出我們需要的 DOM。 - 此外,不應該在
render
方法中改變 component 的state
。
而 render 方法中接受以下的值(關於 portal 與 fragement 的細節留待後續學習)
- React element
// ... 略
render() {
<div>
<MyComponent />
</div>
}
// ... 略
- Array 和 fragment
透過 array 產生數個元件
// ... 略
render() {
<div>
{
[1,2,3].map(num => (
<MyComponent />
))
}
</div>
}
// ... 略
透過 Fragment 減少額外新增一個節點
// ... 略
render() {
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
}
// ... 略
等同於:
// ... 略
render() {
<>
<ChildA />
<ChildB />
<ChildC />
</>
}
// ... 略
- Portal
它們讓你將 children render 到不同的 DOM subtree 中
// 參考官方範例
return ReactDOM.createPortal(
this.props.children,
domNode
);
- String 和 number
// ... 略
render() {
<div>
<h1>Hello world!</h1>
</div>
}
// ... 略
- Boolean 或 null
test
作為 flag,當 test
為 true 時才渲染 <Child />
// ... 略
render() {
<div>
test && <Child />
</div>
}
// ... 略
componentDidMount
這個方法是一個蠻重要的方法,依據官方描述,==我們可以在這個方法中做網路請求==
意思是如果需要透過 call api 取得一些資料的話,在這個方法中非常適合。
而文件中有描述到:
這個方法適合設立任何 subscription。設立完 subscription 後,別忘了在 componentWillUnmount() 內取消 subscription。
這是什麼意思呢? 意思是我們可能有時候會需要透過額外建立像是監聽事件:
const div = document.querySelector('div');
div.addEventListener('click', fn);
我們可以在 componentDidMount
中建立類似這種監聽事件,並於元件銷毀時取消:
// 在 componentWillUnmount life cycle method 中取消
div.removeEventListener('click', fn);
另外就是可以在componentDidMount() 內呼叫 setState(),而這會觸發額外的一次 render ,但是會在瀏覽器更新螢幕之前發生。代表使用者並不會察覺到變化。
但使用上需要注意,可能會導致效能問題。
相關測試範例,點擊前往。
元件(Component)更新時(updating)的生命週期方法
所有方法:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
常用方法:
render()
componentDidUpdate()
shouldComponentUpdate(nextProps, nextState)
透過 shouldComponentUpdate 可以用來決定 component 會不會跟著被更新的 state 或者 props 而有所變動。
當我們回傳 false
時,可以阻止 component 重新 render。
但 React 的預設行為是每次只要有更新就會觸發重新 render,所以在使用這個方法時要特別注意。雖然這個方法的存在是為了效能最佳化,使我們可以透過比對前後的 state, prop 來決定是否做此次的更新,但透過回傳 false
並不會避免子元件在自身 state 改變時的重新 render
// ...
shouldComponentUpdate(nextProps, nextState) {
// 可以在這裡比對 state, props 決定是否要做此次更新
return true;
}
// ...
getSnapshotBeforeUpdate(prevProps, prevState)
在最近一次 render 前可以讓我們做些處理的方法,通常可以用來在 DOM 改變之前先從其中抓取一些資訊,此外當使用了這個方法時,會回傳一個值作為參數傳遞給 componentDidUpdate()
最常使用的情況是用來處理 取得滾動軸位置,以官方的例子為例:
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
透過在 getSnapshotBeforeUpdate
做些判斷並回傳值,作為 componentDidUpdate
中可以用來判斷並額外處理的值。
componentDidUpdate(prevProps, prevState, snapshot)
這是更新時最重要的方法,在更新後會馬上被呼叫。
而依據官方解釋,在這邊也非常適合做網路請求,這代表我們也可以在這個生命週期 call api。
componentDidUpdate
與 componentDidMount
用法類似,也可以在此呼叫 setState
來更新 state
的值,但需要特別注意的是,在 componentDidUpdate
中記得比較前後的值是否有差異,如果有差異的話才做新的一次網路請求,否則可能會影響 component 的效能。
此官方例子為例:
componentDidUpdate(prevProps) {
// 常見用法(別忘了比較 prop):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
元件(Component)卸下時(Unmount)的生命週期方法
最後是元件生命週期的最後一哩路,就是銷毀或卸下這個元件。
這個部分的生命週期方法只有 componentWillUnmount
, 會在一個 component 被 unmount 和 destroy 後馬上被呼叫,我們可以在這裡做一些清除的行為。
鐵人賽文章與程式碼同步發佈於: