React学习笔记(六) --- 部分原理机制

一、setState()详解

1、异步更新

​ 我们都知道 setState() 使用来更新数据的,但你知道吗?setState() 方法更新数据的操作,是异步更新操作。 也就是调用setState()之后状态值并不会立即改变,而是等同步操作执行结束后才会进行改变。所以在连续使用该方法修改数据时,后面的setState()不能依赖于前面的setState(),因为后面的setState()在被调用时,前面的setState()还并未将状态修改,所以后面的setState()获取的值,还是状态的初始值。

// 初始状态值 
state = {
    count: 1
  }
// 修改状态的方法
  handleClick = () => {
    // 此处,第一次更新state
    // 注意:异步更新数据的!!!
    this.setState({
      count: this.state.count + 1
    })
    console.log('count:', this.state.count) // 1
    // 第二次更新state 
    this.setState({
        // 此时第一次setState() 还没有将数据修改完成  所以获取的还是初始值
      count: this.state.count + 1 // 1 + 1
    })
    console.log('count:', this.state.count) // 1
  }
  // 最终页面中 展示 count 值 还是2 因为两次 setState() 的修改结果都是 2
  render() {
    console.log('render')
    return (
      

计数器:{this.state.count}

) }

​ *注意:而且在React方法中多次调用 setState()方法,只会触发一次页面的重新渲染。

2、推荐语法

​ 通过上面我们可以知道,如果想要在方法中调用两次setState()方法来修改同一个状态是无法实现我们想要的效果,所以React为我们提供一个新语法来解决上面的问题:setState((state,props) => { 修改操作 }) ,该语法的参数是一个箭头函数,箭头函数的参数获取的都是页面中最新的 state 和 props,内部则是对状态进行操作。也就是说,在连续使用该语法修改数据时,后面的setState()可以依赖于前面的setState()。虽然获取的是最新的状态,但该语法更新状态 state 的操作也是异步操作:

  // 初始状态值
  state = {
    count: 1
  }
  // 更新状态的方法
  handleClick = () => {
    // 推荐语法
    // 注意:这种语法也是异步更新state的!
    this.setState((state, props) => {
      return {
        count: state.count + 1 // 1 + 1
      }
    })
    console.log('count:', this.state.count) // 1
    this.setState((state, props) => {
      console.log('第二次调用:', state)
      return {
        count: state.count + 1 // 2 + 1
      }
    })
    console.log('count:', this.state.count) // 1
  }
// 最终页面中 展示 count 值 是 3 
  render() {
    return (
      

计数器:{this.state.count}

) }

​ 该语法还可以设置第二个参数,也是一个函数,表示修改状态操作的回调函数,在页面完成重新渲染后,才会立即执行的某个操作:

  state = {
    count: 1
  }

  handleClick = () => {
    this.setState(
      // 第一个参数  修改状态操作 
      (state, props) => {
        return {
          count: state.count + 1
        }
      },
       // 第二个参数 回调函数
      // 状态更新后并且重新渲染后,立即执行:
      () => {
        console.log('状态更新完成:', this.state.count) // 2
        console.log(document.getElementById('title').innerText)
        document.title = '更新后的count为:' + this.state.count
      }
    )
    console.log(this.state.count) // 1
  }

二、JSX语法转化机制

​ JSX 是React提供的对 createElement() 方法的简化语法(语法糖),在代码编译时,JSX语法会被 @babel/preset-react 插件编译为原本的 createElement() 方法,然后再被解析为 React 元素,最后被渲染到页面中:

React学习笔记(六) --- 部分原理机制_第1张图片

三、组件更新机制

​ setState() 方法在修改状态之后,还会去重新渲染组件的内容。但这个重新渲染的范围是有规定的:组件在进行重新渲染时,也会去重新渲染它所有的子组件,其父组件以及兄弟组件则不会被影响。

​ 当Parent2被触发重新渲染时:

React学习笔记(六) --- 部分原理机制_第2张图片

四、组件性能优化

1、减轻 state

​ 减轻 state 是指:在 state 中只存储跟组件渲染相关的数据,例如:数据变量、列表数据、loading等。其他数据则不要放在state中,根据场景存储到不同位置,比如定时器这类可能在多个方法中被用到的数据,应该放在 this 中,方便调用:

class Hello extends Component {
    componentDidMount() {
        this.timerId = setInterval(() => {},1000)
    }
    componentWillUnmount() {
        clearInterval(this.timerId)
    }
    render() {...}
}

2、避免不必要的重新渲染

​ 从上面我们可以得知,React 的组件更新机制是:当父组件更新时,其所有子组件无论是否有变化,都会跟着更新。这样就出现了不必要的渲染,浪费性能。我们想要实现只有发生变化的组件才进行重新渲染的效果,可以借助于更新阶段的生命周期钩子函数:shouldComponentUpdate(nextProps,nextState) ,nextProps 和 nextState 表示最新的props和最新的state。通过nextProps或nextState判断是否数据发生变化,并以该钩子函数的返回值是true/false,来决定当前组件是否重新渲染。只有当返回值为 true 时,当前组件才会重新渲染。

class App extends React.Component {
  state = {
    number: 0
  }

  shouldComponentUpdate(nextProps, nextState) {
    // 比较最新的state状态和原来state状态  是否发生变化 决定是否重新渲染
    return nextState.number !== this.state.number
	// 或者比较最新的props和原来props 是否发生变化 决定是否重新渲染
    return nextProps.number !== this.props.number
  }

  render() {...}
}

3、纯组件

​ 之前我们创建的类组件都是继承的 React.Component ,而纯组件是继承的 React.PureComponent。纯组件与普通组件的区别在于:纯组件内部自动实现了对比前后两次 props 和 state 的数据,并根据是否有变化,来决定 是否重新渲染组件:

// 纯组件 继承 React.PureComponent
class App extends React.PureComponent {
  render() {...}
}

​ 但要注意的是:纯组件内部的自动对比 props 和 state 是浅层对比,对简单数据类型(值类型)来说没啥不同,但对复杂数据类型(引用类型)来说,浅层对比仅仅是比较引用的地址空间是否相同。如果想要修改 state或props 中复杂数据类型的状态,我们不能直接修改原数据,因为地址空间未变,无法被检测到,也就不会重新渲染。正确的做法是:我们应该去创建一个新数据,引发重新渲染:

// 生成随机数
class App extends React.PureComponent {
  state = {
    obj: {
      number: 0
    }
  }

  handleClick = () => {
    // 正确做法:创建新对象,引用地址与旧对象不同
    // 利用...扩展运算符
    const newObj = { ...this.state.obj, number: Math.floor(Math.random() * 3) }
    this.setState(() => {
      return {
          // 将新对象赋值给旧对象  引发重新渲染
        obj: newObj
      }
    })
    // 错误演示:直接修改原始对象中属性的值
    /* const newObj = this.state.obj
    newObj.number = Math.floor(Math.random() * 3)

    this.setState(() => {
      return {
        obj: newObj
      }
    }) */
  }

  render() {
    console.log('父组件重新render')
    return (
      

随机数:{this.state.obj.number}

) } }

​ 如果是数组类型的数据,除了创建新数组之外,还可以通过 concat()/slice() 等返回一个新数组的方法来修改数组数据。但不要用 push()/unshift() 等直接修改当前数组的方法,因为引用地址未改变,无法被检测到变化,也就无法触发重新渲染。

五、虚拟DOM和Diff算法(简介)

​ React更新视图的思想是:当组件的状态发生变化时,就去更新重新渲染视图,但并不是全部更新,而是依靠虚拟DOM配合Diff算法来实现部分更新,只重新渲染变化的部分视图。

​ 虚拟DOM是什么?虚拟DOM是组件在创建并初次渲染(组件的render()调用)的时候,根据 初始 state 和 JSX结构 所构建的一个JS对象。然后根据这个虚拟DOM生成真正的DOM树,渲染到页面中。当组件的数据发生变化后(setState()),框架会根据最新的数据,创建一个新的虚拟DOM对象,并在组件的render()调用的时候将新的虚拟DOM与原来的虚拟DOM使用Diff算法进行对比,找出不同的DOM节点。最后根据最新的数据去重新渲染这部分发生变化的视图。

React学习笔记(六) --- 部分原理机制_第3张图片

(认知尚浅,后续更新…)

你可能感兴趣的:(React,react.js,javascript,前端)