很多語言或是套件都會有生命週期的概念,就像是之前介紹的git hook一樣,React當然也不例外,供我們在特定階段能做一些操作。
先看常見的生命週期(如下圖)。
這些都是class component的生命週期,會從建立元件開始到移除依序執行這些函數,如下
class App extends Component {
constructor(props) {
super(props);
}
componentDidMount() {}
componentDidUpdate(prevProps, prevState, snapshot){}
render() {
return <>
<h1>生命週期</h1>;
<>
}
}
圖垂直來看可以分為兩種階段,Render和Commit這在之前也有介紹到,忘記的可以再回顧一下。這邊Render階段指的副作用會像是呼叫API等的操作,不可以在這個階段使用。
水平維度可以根據不同時期分為三個階段
Mounting:component被建立且被放到畫面上的階段
constructor
當我們建立類別物件實體的時候一開始執行的函式,在這個地方會宣告元件有哪些狀態等。
render
看上面的例子是不是覺得很眼熟,就是之前function component裡面return的JSX,用來呈現畫面的函式。
componentDidMount
當HTML第一次被畫到頁面上後會呼叫的函式,可以用來呼叫API資料或抓取HTML Tag等。
Updating:畫面更新的階段
Render
根據更新的狀態資料在執行一次Render
componentDidUpdate
更新完畫面後就會呼叫
Unmounting:component被從畫面移除時會呼叫
componentWillUnmount
component要被移除的時候,可以用來移除綁定監聽的事件,避免組件消失後監聽還在。
我們也可以用hook來讓function component有生命週期的效果,主要就是使用useEffect這個hook。
useEffect(setup, dependencies?)
setup
是要執行的內容
dependencies
是相依的變數,若是放在這的變數有改變就會執行setup
如果dependencies
是空陣列,只會執行第一次,若整個沒有放就會每次渲染都執行。
function App() {
const [val, setVal] = useState(0);
useEffect(() => {
// **componentDidMount或componentDidUpdate
return () => {
// componentDidUpdate
//** componentWillUnmount
**}**
}, []);
}
export default App;
這邊有個可能常被忽略的是,如果是更新狀態而執行useEffect的話,會先執行下圖的1再執行2喔!
再來要來講不常用的幾個生命週期
getDerivedStateFromProps(props, state)
回傳要更新的state,不更新回傳null
shouldComponentUpdate(nextProps, nextState)
用於控制component是否要更新,預設每次都更新(回傳true),可以判斷props不同時才更新,來減少不必要的更新達到優化效能,但是,通常也可以用內建的PureComponent(有實作shouldComponentUpdate
)來達成效果。
看個官方PureComponent的例子,在這個例子Greeting component的props只有name,所以只有在name有變更時這個component再更新這樣會比較節省資源,
import { PureComponent, useState } from 'react';
class Greeting extends PureComponent {
render() {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return <h3>Hello{this.props.name && ', '}{this.props.name}!</h3>;
}
}
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
<label>
Name{': '}
<input value={name} onChange={e => setName(e.target.value)} />
</label>
<label>
Address{': '}
<input value={address} onChange={e => setAddress(e.target.value)} />
</label>
<Greeting name={name} />
</>
);
}
如果改為我們自己實作shouldComponentUpdate
可以改為如下,判斷新的name和原本的name如果有不同才更新
import { Component, useState } from 'react';
class Greeting extends Component {
shouldComponentUpdate(nextProps) {
return this.props.name !== nextProps.name
}
render() {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return <h3>Hello{this.props.name && ', '}{this.props.name}!</h3>;
}
}
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
<label>
Name{': '}
<input value={name} onChange={e => setName(e.target.value)} />
</label>
<label>
Address{': '}
<input value={address} onChange={e => setAddress(e.target.value)} />
</label>
<Greeting name={name} />
</>
);
}
但是官方文件也是有提到,如果你不是維護舊專案建議還是使用function component來寫React會比較好。所以同樣的功能當然也會有function component版本的,就是memo。只要把memo帶入一個function component就可以了,然後這個memo函式會回傳一個component。
import { memo} from 'react';
const Greeting = memo(function Greeting({ name }) {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return <h3>Hello{name && ', '}{name}!</h3>;
});
getSnapshotBeforeUpdate(prevProps, prevState)
可以用來取得畫面更新前的DOM的資訊(滾軸位置)。回傳值會傳給componentDidUpdate的第3個參數。
https://zh-hant.legacy.reactjs.org/docs/react-component.html#shouldcomponentupdate
https://react.dev/reference/react/PureComponent