react基础--原理揭秘:

目录

一、setState

1.1 setState() 是异步更新数据的

1.2 推荐语法(setState的第一个参数)

1.3 setState的第二个参数

1.4 综合示例

二、组件更新机制

三、组件性能优化

3.1 减轻state

3.2 避免不必要的重新渲染--shouldComponentUpdate(nextProps, nextState)

四、纯组件

4.1 纯组件的基本使用

4.2 纯组件的浅层对比说明


一、setState

1.1 setState() 是异步更新数据的

  • 注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()

1. 当你调用 setState 的时候,React.js 并不会马上修改 state (为什么)
2. 而是把这个对象放到一个更新队列里面
3. 稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
  • 可以多次调用 setState() ,只会触发一次重新渲染

index.js
/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';

 class App extends Component {
  state = {
    count: 20,
    money: 100
  }
     render() {
         return (
          <div>
            <h4>APP组件</h4>
            <div>点击次数:{this.state.count}--{this.state.money}</div>
            <button onClick={this.handleClick}>点击</button>
          </div>
         )
     }

     handleClick = () => {
      // 异步
      this.setState({
        count: this.state.count + 1
      })
      this.setState({
        count: this.state.count + 1
      })
      this.setState({
        money: 200
      })
      this.setState({
        count: this.state.count + 1
      })
      this.setState({
        count: this.state.count + 1
      })

      // 后面覆盖前面,上面的一堆就等价于
      // this.setState({
      //   count: this.state.count + 1,
      //   money: 200
      // })

      console.log(this.state.count);
     }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)

在使用 React.js 的时候,并不需要担心多次进行 setState 会带来性能问题。


1.2 推荐语法(setState的第一个参数)

使用 setState((preState) => {}) 语法

参数preState: React.js 会把上一个 setState 的结果传入这个函数

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';

 class App extends Component {
  state = {
    count: 20,
    money: 100
  }
     render() {
         return (
          <div>
            <h4>APP组件</h4>
            <div>点击次数:{this.state.count}--{this.state.money}</div>
            <button onClick={this.handleClick}>点击</button>
          </div>
         )
     }

     handleClick = () => {
      // state -- 能够保证是最新的 state
      this.setState((state) => {
        return {
          count: state.count + 1
        }
      })
      this.setState((state) => {
        return {
          count: state.count + 1
        }
      })
      // 或者下面这种写法
      // 箭头函数返回一个对象的时候要用小括号包起来
      this.setState((state) => ({ count: state.count + 1})
      )
     }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)

这种语法依旧是异步的,但是state可以获取到最新的状态,适用于需要调用多次setState  


1.3 setState的第二个参数

  • 场景:在状态更新(页面完成重新渲染)后立即执行某个操作

  • 语法:setState(updater[, callback])

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';

 class App extends Component {
  state = {
    count: 20,
    money: 100
  }
     render() {
         return (
          <div>
            <h4>APP组件</h4>
            <div>点击次数:{this.state.count}--{this.state.money}</div>
            <button onClick={this.handleClick}>点击</button>
          </div>
         )
     }

     handleClick = () => {
      // state -- 能够保证是最新的 state
      this.setState((state) => {
        return {
          count: state.count + 1
        }
      })
      this.setState((state) => {
        return {
          count: state.count + 1
        }
      })
      // 或者下面这种写法
      // 箭头函数返回一个对象的时候要用小括号包起来
      this.setState((state) => ({ count: state.count + 1})
      )
      // 拿到的还是修改之前的
      console.log(this.state.count);
      this.setState((state) => ({ count: state.count + 1}),
      () => {
        console.log(this.state.count);
      }
      )
     }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)


1.4 综合示例

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';

 class App extends Component {
  state = {
    isShow: false
  }
  inputRef = React.createRef()
  // 点击按钮,显示输入框,并且让输入框有焦点
  render() {
      return (
       <div>
         <h4>APP组件</h4>
         {
           this.state.isShow ? (<input type="text" ref={this.inputRef} placeholder='请输入你的评论' />) : (<button onClick={this.handleClick}>发表评论</button>)
         }
       </div>
      )
  }

  handleClick = () => {
    this.setState({
      isShow: true
    }, () => {
      this.inputRef.current.focus()
    })
  }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)


二、组件更新机制

setState() 的两个作用: 1. 修改 state 2. 更新组件(UI)

过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';
 import Parent from './Parent'

 class App extends Component {
  render() {
    console.log('App render');
      return (
       <div>
         <h4>APP组件</h4>
         <Parent></Parent>
         <button onClick={this.handleClick}>渲染</button>
       </div>
      )
  }

  handleClick = () => {
    this.setState({})
  }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)

 Parent.js

import React, { Component } from 'react'
import Son from './Son';
import Son2 from './Son2'

export default class Parent extends Component {
  render() {
    console.log('parent render');
    return (
      <div>
        <p>Parent</p>
        <Son></Son>
        <Son2></Son2>
      </div>
    )
  }
}

Son.js

import React, { Component } from 'react'

export default class Son extends Component {
  render() {
    console.log('son1 render');
    return (
      <div>
        <p>Son组件</p>
      </div>
    )
  }
}

Son2.js

import React, { Component } from 'react'

export default class Son2 extends Component {
  render() {
    console.log('son2 render');
    return (
      <div>
      <p>Son2组件</p>
      </div>
    )
  }
}


三、组件性能优化

遵循的原则:

  • 功能第一
  • 性能优化第二

3.1 减轻state

减轻 state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)

注意:不用做渲染的数据不要放在 state 中,比如定时器 id等

对于这种需要在多个方法中用到的数据,应该直接放在 this 中

  • this.xxx = 'bbb'

  • this.xxx

class Hello extends Component {
    componentDidMount() {
        // timerId存储到this中,而不是state中
        this.timerId = setInterval(() => {}, 2000)
    }
    componentWillUnmount() {
    	clearInterval(this.timerId)
    }
    render() { … }
}

vue中同样不要把和渲染无关的数据放到data中


3.2 避免不必要的重新渲染--shouldComponentUpdate(nextProps, nextState)

不是很推荐这种方法,而是推荐使用纯组件 

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰

  • 问题:子组件没有任何变化时也会重新渲染 (接收到的props没有发生任何的改变)

  • 如何避免不必要的重新渲染呢?

  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)

  • 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染

  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)

第一个参数:

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';
 import Parent from './Parent'

 class App extends Component {
  render() {
    console.log('App render');
      return (
       <div>
         <h4>APP组件</h4>
         <Parent></Parent>
       </div>
      )
  }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)

Parent.js

import React, { Component } from 'react'
import Son from './Son';
import Son2 from './Son2'

export default class Parent extends Component {
  state = {
      money: 100,
      car: '小黄车'
  }
  render() {
    console.log('Parent render');
    const { money, car } = this.state
    return (
      <div>
        <p>Parent</p>
        <Son money={money}></Son>
        <Son2 car={car}></Son2>
        <button onClick={() => this.setState({money: 200})}>加钱</button>
        <button onClick={() => this.setState({car: '镚子'})}>改车</button>
      </div>
    )
  }
}

Son.js

import React, { Component } from 'react'

export default class Son extends Component {
  render() {
    console.log('Son render');
    return (
      <div>
        <p>Son组件--{this.props.money}</p>
      </div>
    )
  }

  shouldComponentUpdate(nextProps){
    // console.log(this.props);
    // console.log(nextProps);
    if(this.props.money === nextProps.money){
        // 返回 true(默认) 或 false
        return false; // 让组件不更新
    }else {
        return true;
    }
  }
}

Son2.js

import React, { Component } from 'react'

export default class Son2 extends Component {
  render() {
    console.log('Son2 render');
    return (
      <div>
      <p>Son2组件--{this.props.car}</p>
      </div>
    )
  }
}

第二个参数:

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { Component } from 'react';

 class App extends Component {
  state = {
    list: ['1', '2', '3'],
    current: 0
  }
  render() {
    console.log('APP render');
      return (
       <div>
         <h4>APP组件</h4>
         <div>渲染:{this.state.list[this.state.current]}</div>
         <button onClick={this.random}>随机</button>
       </div>
      )
  }
  random = () => {
    this.setState({
      current: parseInt(Math.random() * this.state.list.length)
    })
  }

  shouldComponentUpdate(nextProps, nextState){
    if(this.state.current === nextState.current){
      // 没有变化
      return false
    } else {
      return true
    }
  }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)


四、纯组件

做组件性能优化推荐用纯组件去做 --做性能优化的时候

纯组件内部用的是浅层对比,也就是不能改原来的对象或数组,而是给一个新的对象或数组

React.PureComponentReact.Component功能相似

区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较

原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件

4.1 纯组件的基本使用

index.js

/**
 * 1. 导入react和react-dom
 * 2. 创建 react 元素
 * 3. 把 react 元素渲染到页面
 */
 import React from 'react';
 import ReactDom from 'react-dom/client';
 import { PureComponent } from 'react';

 class App extends PureComponent {
  state = {
    list: ['1', '2', '3'],
    current: 0
  }
  render() {
    console.log('APP render');
      return (
       <div>
         <h4>APP组件</h4>
         <div>渲染:{this.state.list[this.state.current]}</div>
         <button onClick={this.random}>随机</button>
       </div>
      )
  }
  random = () => {
    this.setState({
      current: parseInt(Math.random() * this.state.list.length)
    })
  }
 }

ReactDom.createRoot(document.getElementById('root')).render(<App />)

 

只有在性能优化的时候可能会用到纯组件,不要所有的组件都使用纯组件,因为纯组件需要消耗性能进行对比


4.2 纯组件的浅层对比说明

值类型: 

  • 说明:纯组件内部的对比是 shallow compare(浅层对比)

  • 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)

let number = 0
let newNumber = number
newNumber = 2
console.log(number === newNumber) // false
state = { number: 0 }
setState({
  number: Math.floor(Math.random() * 3)
})
// PureComponent内部对比:
最新的state.number === 上一次的state.number // false,重新渲染组件

引用类型: 

  • 说明:纯组件内部的对比是 shallow compare(浅层对比)

  • 对于引用类型来说:只比较对象的引用(地址)是否相同

const obj = { number: 0 }
const newObj = obj
newObj.number = 2
console.log(newObj === obj) // true
state = { obj: { number: 0 } }
// 错误做法
state.obj.number = 2
setState({ obj: state.obj })
// PureComponent内部比较:
最新的state.obj === 上一次的state.obj // true,不重新渲染组件

纯组件的最佳实践:

注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!

// 正确!创建新数据
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
// 正确!创建新数据
// 不要用数组的push / unshift 等直接修改当前数组的的方法
// 而应该用 concat 或 slice 等这些返回新数组的方法
this.setState({
	list: [...this.state.list, {新数据}]
})