react 組件生命周期
組件生命周期:組件從創建到掛載到頁面運行、完成復雜的組件功能、分析組件錯誤原因等。
鈎子函數的作用:為開發人員在不同的階段操作組件提供了時機。
鈎子函數:
| 階段 | 順序 | 鈎子函數 | 說明 |
|---|---|---|---|
| 創建階段 | 1 | constructor | 初始化 props and state |
| 創建階段 | 2 | componentWillMount(不安全) | 組件將要掛載 |
| 掛載 | 3 | render | 渲染UI(不能使用setState方法) |
| 掛載 | 4 | componentDidMount | 組件掛載完成 |
| 更新 | 5 | shouldComponentUpdate | 詢問組件是否需要更新 |
| 更新 | 6 | componentWillUpdate(不安全) | 組件將要更新 |
| 更新 | 7 | componentDidUpdate | 組件更新完畢 |
| 挾雜 | 8 | componentWillUnmount | 組件從頁面消失,執行清理工作(比如清理定時器等) |
| - | getSnapshotBeforeUpdate(新版) | 在子組件更新 DOM 和 refs 之前,從 DOM 中捕獲一些信息(例如滾動位置) |
|
| - | getDerivedStateFromProps(新版) | ||
| - | componentWillReceiveProps(不安全) | 可以在子組件的render函數執行前獲取新的props |
import React, { Component } from 'react'
export default class LifeCycle extends Component {
//// props = {age:10,name:'計數器'}
static defaultProps = {
name: '計數器'
}
constructor(props) {
//Must call super constructor in derived class before accessing 'this' or returning from derived constructor
super();//this.props = props;
this.state = { number: 0, users: [] };//初始化默認的狀態對象
console.log('1. constructor 初始化 props and state');
}
//componentWillMount在渲染過程中可能會執行多次
componentWillMount() {
console.log('2. componentWillMount 組件將要掛載');
//localStorage.get('userss');
}
//componentDidMount在渲染過程中永遠只有執行一次
//一般是在componentDidMount執行副作用,進行異步操作
componentDidMount() {
console.log('4. componentDidMount 組件掛載完成');
fetch('https://api.github.com/users').then(res => res.json()).then(users => {
console.log(users);
this.setState({ users });
});
}
shouldComponentUpdate(nextProps, nextState) {
console.log('Counter', nextProps, nextState);
console.log('5. shouldComponentUpdate 詢問組件是否需要更新');
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log('6. componentWillUpdate 組件將要更新');
}
componentDidUpdate(prevProps, prevState) {
console.log('7. componentDidUpdate 組件更新完畢');
}
add = () => {
this.setState({ number: this.state.number });
};
render() {
console.log('3.render渲染,也就是掛載')
return (
<div style={{ border: '5px solid red', padding: '5px' }}>
<p>{this.props.name}:{this.state.number}</p>
<button onClick={this.add}>+</button>
<ul>
{
this.state.users.map((user,index) => (<li key={index}>{user.login}</li>))
}
</ul>
{this.state.number % 2 === 0 && <SubCounter number={this.state.number} />}
</div>
)
}
}
class SubCounter extends Component {
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentWillUnmount() {
console.log('SubCounter componentWillUnmount');
}
//調用此方法的時候會把新的屬性對象和新的狀態對象傳過來
shouldComponentUpdate(nextProps, nextState) {
console.log('SubCounter', nextProps, nextState);
if (nextProps.number % 3 === 0) {
return true;
} else {
return false;
}
}
//componentWillReceiveProp 組件收到新的屬性對象
componentWillReceiveProps() {
console.log('SubCounter 1.componentWillReceiveProps')
}
render() {
console.log('SubCounter 2.render')
return (
<div style={{ border: '5px solid green' }}>
<p>{this.props.number}</p>
</div>
)
}
}
getSnapshotBeforeUpdate: ** 接收父組件傳遞過來的 props 和組件之前的狀態,此生命周期鈎子必須有返回值,返回值將作為第三個參數傳遞給 componentDidUpdate。必須和 componentDidUpdate 一起使用,否則會報錯
該生命周期鈎子觸發的時機 :被調用於 render 之后、更新 DOM 和 refs 之前
版本遷移:
componentWillMount,componentWillReceiveProps,componentWillUpdate 這三個生命周期因為經常會被誤解和濫用,所以被稱為 不安全(不是指安全性,而是表示使用這些生命周期的代碼,有可能在未來的 React 版本中存在缺陷,可能會影響未來的異步渲染) 的生命周期。
React 16.3 版本:為不安全的生命周期引入別名 UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(舊的生命周期名稱和新的別名都可以在此版本中使用)
React 16.3 之后的版本:為 componentWillMount,componentWillReceiveProps 和 componentWillUpdate 啟用棄用警告。(舊的生命周期名稱和新的別名都可以在此版本中使用,但舊名稱會記錄DEV模式警告)
React 17.0 版本: 推出新的渲染方式——異步渲染( Async Rendering),提出一種可被打斷的生命周期,而可以被打斷的階段正是實際 dom 掛載之前的虛擬 dom 構建階段,也就是要被去掉的三個生命周期 componentWillMount,componentWillReceiveProps 和 componentWillUpdate。(從這個版本開始,只有新的“UNSAFE_”生命周期名稱將起作用)
jsx 語法轉換過程
-
jsx 只是 createElement 方法的語法糖。
-
jsx 語法被 @bable/prset-react 插件編譯為 createElement 語法。
-
createElement 會被轉化成 react 元素
// jsx 語法 const el = <div className="red" >hello</div> // createElement() 語法 const el = React.createElement( 'div', { className: 'red'}, 'hello' ) // React 元素 const el = { type: 'div', key: null, ref: null, props: { className: 'red', children: 'hello' } }
組件性能優化
減輕state:
- 只存放與組件渲染相關的數據。
- 不用做渲染的數據不要存放在state中。
- 對於多個方法中需要用到的數據,應該放到this中。
避免不必要的重新渲染:
-
父組件更新,子組件沒有任何變化也會被重新渲染
-
解決方法:避免子組件重新更新的方法是使用鈎子函數 shouldComponentUpdate(nextProps, nextState)
-
作用:通過 鈎子函數的返回值決定是否重新渲染,返回true重新渲染,返回false不重新渲染。
-
觸發時機:更新階段的鈎子函數,組件重新渲染前(shouldComponentUpdate => render)
-
shouldComponentUpdate(nextProps, nextState) { // nextProps 最新的Props值 // nextState 最新的State值 // this.props 上一次的props值 // this.state 上一次的state值 // 通過 新值和舊值做對比 返回true或false,手動的決定是否更新組件 return this.state.xxx !== nextState.xxx) // 如果值沒有發生變化則不重新渲染組件 } -
使用PureComponent(純組件) 可以自動實現 shouldComponentUpdate的更新判斷, 不用手動去做對比決定是否重新渲染。
-
class App extends React.PureComponent{ redner(){ return <div>{this.state.num}</div> } }
純組件:
純組件內部對比是:shadllow compare(淺層對比)
對於值類型:比較兩個值是否相同(直接賦值即可 沒有坑)
對於引用類型:只比較對象的引用(地址)是否相同。
組件更新機制與setState 方法
setState 的作用:1. 修改 state 2.更新組件UI
父組件重新渲染時,也會重新渲染子組件,但只會渲染當前組件子樹(當前組件及其所有子組件會被更新)
setState 的執行是異步的,如果在調用了setState 方法后立即 打印 state ,state 還是原來未更新時的狀態。
state = {
num: 1
}
this.setState({ num: this.state.num + 1 })
console.log('打印', this.state.num) // 此時打印的結果並沒有 + 1 ,還是原來的值
如果在一個函數里面重復調用多次 setState,setState等同於執行了一次。
state = {
num: 1
}
this.setState({ num: this.state.num + 1 })
console.log('打印', this.state.num)
this.setState({ num: this.state.num + 4 })
// 最終 num 並沒有並更新2次變為3, 而是被更新為2
// 這是由於 num 在第一次setState的異步更新之后下面的代碼拿到值是原來的1,因此再次執行 setState等同於在1的基礎上+4,最終 num = 5 。
如果想要在函數中多次調用setState,需要用回調函數的方法獲取上一次state的最新值再更新。
在setState中獲取上一次state 的最新值可以用回調函數 this.setStte( (state, props) => {} )
代碼如下:
state = {
num: 1
}
this.setState({num: this.state.num + 1 })
this.setState((state, props) => {
// 此時state就是被更新后的最新值 num = 2
// 在獲取到了最新值后再執行更新操作就可以實現多次調用 setState 方法了
return {
num: state + 4
}
})
// 此時的 num = 6
在setState中有兩個回調函數 this.setState({num: 1}, callback ),第二個回調函數會在第一個回調函數執行完更新后執行,因此在第二個回調函數中也可以拿到state被更新后的最新值。
state = {
num: 1
}
this.setState({ num: this.state.num + 1 }, () => {
console.log('打印', this.state.num)
// num = 2
})
虛擬DOM與Diff算法
react更新視圖的思想是:state發生變化會重新渲染視圖。
組件中只有一個DOM元素需要更新時,並不會重新渲染整個組件的內容,只會更新部分變化的地方。 而部分更新是靠虛擬DOM和Diff算法來實現的。
虛擬DOM本質上是一個JS對象,用來描述你希望在屏幕上看到的內容(UI)。
虛擬DOM對象:
const element = {
type: 'h1',
props: {
className: 'qreeting',
children: 'hello jsx!'
}
}
虛擬DOM執行過程:
- 初次渲染時,React會根據初始state(Model)創建一個虛擬DOM對象(樹)。
- 根據虛擬DOM生成真正DOM渲染到頁面中。
- 當數據發生變化后(setState()),重新根據新的數據,創建新的虛擬DOM對象(樹)
- 與上一次得到的虛擬DOM對象,使得Diff算法對比(找不同),得到需要更新的內容。
- 最終,React只將變化的內容更新(patch)到DOM中,重新渲染到頁面。
總結:虛擬DOM和Diff算法的確帶來了性能提升,但它的真正價值從來都不是性能。最大的價值是讓React脫離了瀏覽器環境的束縛,也為跨平台提供了支持。
