zoukankan      html  css  js  c++  java
  • 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脱离了浏览器环境的束缚,也为跨平台提供了支持。

  • 相关阅读:
    codeforces 455B A Lot of Games(博弈,字典树)
    HDU 4825 Xor Sum(二进制的字典树,数组模拟)
    hdu 1800 Flying to the Mars(简单模拟,string,字符串)
    codeforces 425A Sereja and Swaps(模拟,vector,枚举区间)
    codeforces 425B Sereja and Table(状态压缩,也可以数组模拟)
    HDU 4148 Length of S(n)(字符串)
    codeforces 439D Devu and Partitioning of the Array(有深度的模拟)
    浅谈sass
    京东楼层案例思维逻辑分析
    浅谈localStorage和sessionStorage
  • 原文地址:https://www.cnblogs.com/liea/p/12507909.html
Copyright © 2011-2022 走看看