react 原理的簡單分析


react 組件生命周期

組件生命周期:組件從創建到掛載到頁面運行、完成復雜的組件功能、分析組件錯誤原因等。

鈎子函數的作用:為開發人員在不同的階段操作組件提供了時機。

鈎子函數:

階段 順序 鈎子函數 說明
創建階段 1 constructor 初始化 props and state
創建階段 2 componentWillMount(不安全) 組件將要掛載
掛載 3 render 渲染UI(不能使用setState方法)
掛載 4 componentDidMount 組件掛載完成
更新 5 shouldComponentUpdate 詢問組件是否需要更新
更新 6 componentWillUpdate(不安全) 組件將要更新
更新 7 componentDidUpdate 組件更新完畢
挾雜 8 componentWillUnmount 組件從頁面消失,執行清理工作(比如清理定時器等)
- getSnapshotBeforeUpdate(新版) 在子組件更新 DOMrefs 之前,從 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 之后、更新 DOMrefs 之前

版本遷移:

componentWillMountcomponentWillReceivePropscomponentWillUpdate 這三個生命周期因為經常會被誤解和濫用,所以被稱為 不安全(不是指安全性,而是表示使用這些生命周期的代碼,有可能在未來的 React 版本中存在缺陷,可能會影響未來的異步渲染) 的生命周期。

React 16.3 版本:為不安全的生命周期引入別名 UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate。(舊的生命周期名稱和新的別名都可以在此版本中使用

React 16.3 之后的版本:為 componentWillMountcomponentWillReceivePropscomponentWillUpdate 啟用棄用警告。(舊的生命周期名稱和新的別名都可以在此版本中使用,但舊名稱會記錄DEV模式警告

React 17.0 版本: 推出新的渲染方式——異步渲染( Async Rendering),提出一種可被打斷的生命周期,而可以被打斷的階段正是實際 dom 掛載之前的虛擬 dom 構建階段,也就是要被去掉的三個生命周期 componentWillMountcomponentWillReceivePropscomponentWillUpdate。(從這個版本開始,只有新的“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執行過程:

  1. 初次渲染時,React會根據初始state(Model)創建一個虛擬DOM對象(樹)。
  2. 根據虛擬DOM生成真正DOM渲染到頁面中。
  3. 當數據發生變化后(setState()),重新根據新的數據,創建新的虛擬DOM對象(樹)
  4. 與上一次得到的虛擬DOM對象,使得Diff算法對比(找不同),得到需要更新的內容。
  5. 最終,React只將變化的內容更新(patch)到DOM中,重新渲染到頁面。

總結:虛擬DOM和Diff算法的確帶來了性能提升,但它的真正價值從來都不是性能。最大的價值是讓React脫離了瀏覽器環境的束縛,也為跨平台提供了支持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM