【前端框架】react框架 第五章 react原理揭秘

1. setState()的说明

使用setState的原因

在开发中我们并不能直接通过修改state的值来让界面发生更新:
因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是this.state这种方式的修改React并不知道数据发生了变化。

在组件中并没有实现setState的方法,为什么可以调用呢?
原因很简单,setState方法是从Component中继承过来的。 我有找到对应的源码, 可以看到源码中, setState是放在Component的原型上的

setState推荐语法

  1. 推荐:使用setState((state, props) => {})语法
  2. 参数state:表示最新的state
  3. 参数props:表示最新的props
//推荐语法
// 注意:这种语法是异步更新state的!
handleClick = ()=>{
    
    
    this.setState((state,props)=>{
    
    
        return{
    
    
            conut:state.conut +1
        }
    })
    console.log('count:', this.state.conut)///1
}

第二个参数

  1. 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
  2. 语法:setState(updater[,callback])
handleClick = ()=>{
    
    
    this.setState(
        (state,props)=>{
    
    },
        ()=>{
    
    console.log('这个回调函数会在状态更新后立即执行')}
    )    
    
}
handleClick = ()=>{
    
    
    this.setState(
        (state,props)=>{
    
    },
        ()=>{
    
    document.title='更新state后的标题:'+this.state.count}
    )    
    
}

setState的基本用法

setState常见的用法

用法一:

直接在setState函数中传入一个对象, 传入的该对象会和this.state的对象进行一个合并, 相同的属性会进行覆盖,
修改完成后等待任务队列批处理调用render函数实现页面更新

export class App extends Component {
    
    
  constructor() {
    
    
    super()

    this.state = {
    
    
      name:"小莉"
    }
  }
  
  changeText() {
    
    
    this.setState({
    
    
      name:"猪"
    })
  }

  render() {
    
    
    const {
    
     name } = this.state
    return (
      <div>
        <h2>{
    
    name}</h2>
        <button onClick={
    
    () => {
    
    this.changeText()}}>点我</button>
      </div>
    )
  }
}

用法二:

在setState函数中传入一个回调函数, 要求传入的回调函数返回一个对象,React内部会将返回的这个对象和state对象进行合并

用法二的好处:

  1. 可以在该回调函数中, 编写修改state数据的逻辑, 具有更强的内聚性
  2. 当前回调函数会默认将state和props传入进来, 我们可以在回调函数中直接获取, 不需要再通过this.state或this.props的方式获取
export class App extends Component {
    
    
  constructor() {
    
    
    super()

    this.state = {
    
    
      name:"小莉"
    }
  }
  
  changeText() {
    
    
    this.setState((state, props) => {
    
    
      // 可以直接在回调函数中获取state和props
      console.log(state, props);

      // 对数据进行操作的逻辑
      const name = state.name + "小莉"

      // 该回调函数返回一个对象
      return {
    
    
        name
      }
    })
  }

  render() {
    
    
    const {
    
     name } = this.state
    return (
      <div>
        <h2>{
    
    name}</h2>
        <button onClick={
    
    () => {
    
    this.changeText()}}>点我</button>
      </div>
    )
  }
}

setState是异步更新的

可以用以下代码检验,在执行完setState后, 我们立即打印一下name的结果

export class App extends Component {
    
    
  constructor() {
    
    
    super()

    this.state = {
    
    
      name:"小莉"
    }
  }
  
  changeText() {
    
    
    // 用法一
    this.setState({
    
    
      name:"猪"
    })
    console.log(this.state.name) // 小莉
  }

  render() {
    
    
    const {
    
     name } = this.state
    return (
      <div>
        <h2>{
    
    name}</h2>
        <button onClick={
    
    () => {
    
    this.changeText()}}>点我</button>
      </div>
    )
  }
}

打印结果为“小莉”, 并不是修改后的结果,
可见setState是异步的操作,我们并不能在执行完setState之后立马拿到最新的state的结果

那么为什么setState设计为异步呢?

设计为异步的两个主要原因

1. setState设计为异步,可以显著的提升性能:
我们知道调用setState会让render函数重新执行,如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的。最好的办法应该是获取到多个更新,之后进行批量更新(或者说统一的更新)。而React中的做法也是如此,在一个时间段中,会获取多个更新,再将多个更新放入一个任务队列中,再对任务队列进行批处理。如果还有其他更新不在当前时间段,则在下一个时间段(或者其他时间段)的任务队列中进行批处理

2. 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步:
state和props不能保持一致性,会在开发中产生很多的问题。

2. JSX语法的转化过程

  1. JSX仅仅是createElement()方法的语法糖(简化语法)
  2. JSX语法@babel/preset-react插件编译为createElent()方法
  3. React元素:是一个对象,用来描述你希望在屏幕上看到的内容

转化过程:

JSX语法 --> createElement() --> React元素

3. 组件更新机制

  1. setState()的两个作用:修改state和更新组件(UI)
  2. 过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件及其所有子组件)

4. 组件性能优化

减轻state

  1. 减轻state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading等)
  2. 注意:不用做渲染的数据不要放在state中,比如定时器id等
  3. 对于这种需要在多个方法中用到的数据,应该放在this中
class Hello extends React.Component{
    
    
    componentDidMount(){
    
    
        // timerId存储到this中,而不是state中
        this.timerId = setInterval(()=>{
    
    },200)
    }
    componentWillUnmount(){
    
    
        clearInterval(this.timerId)
    }
    render(){
    
    ...}
}

避免不必要的重新渲染

  1. 组件更新机制:父组件的更新会引起它的子组件也被更新,这种思路很清晰
  2. 问题:子组件没有任何变化时也会被更新
  3. 如何避免不必要的重新渲染呢?
    解决方法:使用钩子函数shouldComponentUpdate(nextProps,nextState)
    作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
    触发时机:更新阶段的钩子函数、组件重新渲染前执行( shouldComponentUpdate --> render )
class Hello extends React.Component{
    
    
    shouldComponentUpdate(){
    
    
        // 根据条件是否重新渲染组件
        return false
    }
    render(){
    
    ...}

}

随机数案例

class App extends React.Component{
    
    
    // 因为两次生成的随机数可能相同,如果相同中,此时,不需要重新渲染
    shouldComponentUpdate(nextProps,nextState){
    
    
    	//return nextState.number!==this.state.number 和以下代码等同
        if(nextState.number===this.state.number){
    
    
            return false
        }
        return true
    }
    render(){
    
    ...}
}

class NumberBox extends React.Component{
    
    
    shouldComponentUpdate(nextProps){
    
    
        // 如果前后两次的number值相同,就返回false,不更新组件
        //return nextProps.number!==this.props.number 和以下代码等同
        if(nextProps.number===this.props.number){
    
    
            return false
        }
        return true

    }
    render(){
    
    ...}
}

代码实现:

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

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

    handleClick = ()=> {
    
    
        this.setState(()=>{
    
    
            return {
    
    
                number:Math.floor(Math.random() *3)
            }
        })
    }

    // 因为两次生成的随机数可能相同,如果相同中,此时,不需要重新渲染
    shouldComponentUpdate(nextProps,nextState){
    
    
        console.log('最新状态:',nextState,",当前状态:",this.state)
        if(nextState.number===this.state.number){
    
    
            return false
        }
        return true
    }

    render(){
    
    
        return(
            <div>
                <h1>随机数:{
    
    this.state.number}</h1>
                <button onClick={
    
    this.handleClick}>重新生成</button>
            </div>
        )
    }
}

class NumberBox extends React.Component{
    
    
    shouldComponentUpdate(nextProps){
    
    
        console.log('最新props:',nextProps,",当前props:",this.props)
        // 如果前后两次的number值相同,就返回false,不更新组件
        if(nextProps.number===this.props.number){
    
    
            return false
        }
        return true

    }
    render(){
    
    
        console.log('子组件中的render')
        return <h1>随机数:{
    
    this.props.number}</h1>
    }
}


ReactDOM.render(<App />,document.getElementById('root'))

纯组件

  1. 纯组件:React.PureComponent 与React.Component 功能相似
  2. 区别:PureComponent内部自动实现了shouldComponentUpdate钩子函数,不需要手动比较
  3. 原理:纯组件内部通过分别对比前后的props和state的值,来决定是否重新渲染组件
class Hello extends React.PureComponent{
    
    
    render(){
    
    
        return <h1>纯组件</h1>
    }
}

代码实现:

import React from 'react'
import ReactDOM from 'react-dom'


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

    handleClick = ()=> {
    
    
        this.setState(()=>{
    
    
            return {
    
    
                number:Math.floor(Math.random() *3)
            }
        })
    }

    render(){
    
    
        // console.log('父组件中的render')
        return(
            <div>
                <NumberBox number={
    
    this.state.number}/>
                <h1>随机数:{
    
    this.state.number}</h1>
                <button onClick={
    
    this.handleClick}>重新生成</button>
            </div>
        )
    }
}

class NumberBox extends React.PureComponent{
    
    
    
    render(){
    
    
        console.log('子组件中的render')
        return <h1>随机数:{
    
    this.props.number}</h1>
    }
}


ReactDOM.render(<App />,document.getElementById('root'))


  1. 说明:纯组件内部的对比是shallow compare (浅层对比)
  2. 对于值类型来说,比较这两个的值是否相同(直接赋值即可)
let number =0
let newNumber = number
newNumber = 2
console.log(number===newNumber) //false

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

  1. 对于引用类型来说:只比较对象的引用(地址)是否相同
  2. 注意:state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据
const obj={
    
    number:0}
const newObj = obj
newObj.number = 2
console.log(newObj===obj)  //true
// 正确!创建新数据
    const newObj = {
    
    ...state.obj,number:2}
    setState({
    
    obj:newObj})

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

虚拟DOM和Diff算法

  1. React更新视图的思想是:只要state变化就重新渲染视图
  2. 特点:思路非常清晰
  3. 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
  4. 理想状态:部分更新,只更新变化的地方
  5. 问题:React是如何做到部分更新的?虚拟DOM配合Diff算法
  6. 虚拟 DOM(Virtual DOM),就是一个 JS 对象,用来描述我们希望在页面中看到的 HTML 结构内容.
  7. 虚拟 DOM : 本质是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI)
    虚拟DOM 对象 和 HTML 结构之间是一一对应的关系

执行过程

  1. 第一次页面渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)
    过程:1 JSX + state => 2 虚拟 DOM 树(JS 对象) => 3 浏览器中看到的 HTML 结构内容
  2. 根据虚拟DOM生成真正的DOM,渲染到页面中
  3. 当数据变化时后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
  4. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容
  5. 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面

在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qiqizgl777/article/details/129390628