React(四):事件总线、setState的细节、PureComponent、ref

news2024/11/15 9:07:53

React(四)

  • 一、事件总线
  • 二、关于setState的原理
    • 1.setState的三种使用方式
      • (1)基本使用
      • (2)传入一个回调
      • (3)第一个参数是对象,第二个参数是回调
    • 2.为什么setState要设置成异步
      • (1)提升性能,减少render次数
        • 2.1.1多个state合并的小问题
      • (2)避免state和props数据不同步
    • 3.获取异步修改完数据的结果
  • 三、PureComponent监测数据的原理
    • 1.先来看一个问题
    • 2.sholdComponentUpdate()
    • 3.引出PureComponent
      • (1)类组件
      • (2)函数组件
    • 4.PureComponent只监测第一层
    • 5.PureComponent如何监测深层数据的改变
  • 四、ref获取元素或组件实例
    • 1.ref的三种用法
    • 2.ref获取类组件实例
    • 3.ref获取函数组件内的某个元素

一、事件总线

这里的事件总线和vue中基本一个思路。

在React中可以通过第三方库来进行任意组件通信,安装:

npm install hy-event-store

使用:

在这里插入图片描述
1、在某个地方新建jsx文件对外暴露事件总线

// 创建事件总线
import { HYEventBus } from 'hy-event-store';
const eventBus = new HYEventBus();

export default eventBus;

2、在需要接收值的组件中,在挂在完毕的生命周期函数中绑定事件和被触发时的回调,最好写上销毁的代码:

//事件的回调
 getData(name,age) {
     console.log(name,age,this);
     this.setState({
         name: name, age: age
     })
 }
 //1.挂载完毕后绑定事件接收别的地方传过来的值
 componentDidMount() {
     eventBus.on('getData', this.getData.bind(this))
 }
 //3.销毁的时候解绑
 componentWillUnmount() {
     eventBus.off('getData', this.getData)
 }

3、另一个组件触发,并传值

sendData() {
        //2.某个组件中触发事件并传值
        eventBus.emit('getData', 'zzy', 18)
    }
    render() {
        return (
            <div>
                <h1>GrandSon组件</h1>
                <button onClick={() => this.sendData()}>点击传值给App</button>
            </div>
        )
    }

二、关于setState的原理

开发中我们并不能直接通过修改state的值来让界面发生更新:

因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是这种方式的修改React并不知道数据发生了变化;

React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来通过数据劫持监听数据的变化;

我们必须通过setState来告知React数据已经发生了变化;
源码先简单lou一眼:

在这里插入图片描述

1.setState的三种使用方式

我们基于以下组件进行操作

export class Son extends React.Component {
  constructor() {
    super();
    this.state = {
      name: 'zzy',
      age: 18,
    }
  }

  changeName() {
    this.setState(...)
  }

  render() {
    return (
      <div>
        <h1>{this.state.name}</h1>
        <button onClick={() => this.changeName()}>点击修改名字</button>
      </div>

    )
  }
}

(1)基本使用

我们之前用的最多的就是直接传入一个配置对象,然后给state中数据重新赋值。这里的原理是借助了Object.assign(state, newState)state和传入的对象进行合并,如果key重复那么就进行值的覆盖,没改的继续保留

//1.基本使用,传入配置对象,不是覆盖原来的state,而是进行对象的合并
this.setState({
  name: 'ht'  //原理:对象的合并Object.assign(state, newState)
})

(2)传入一个回调

setState的参数除了可以传配置对象外,还可以传入一个回调函数,通过return一个对象,对象中包含我们要修改的值,也可以实现数据的更新和页面的重新渲染。

这个回调可以接收两个参数:state和props,分别对应的是上一个修改状态stateprops的值们。

注意是上一个修改状态!如果在一个回调中多次执行setState更改数据,那么参数state保存的是上一个修改状态的值!如果不明白请看本节2.1.1部分

//2.传入一个回调,可以接收修改之前的state和props
this.setState((state,props) => {
  console.log(state,props);
  return {
    name: 'ht' //这里也可以进行更改
  }
})

(3)第一个参数是对象,第二个参数是回调

setState是一个异步调用。

如果在setState下面使用name,我们会发现拿到的是原来的name,这就证明了setState是一个异步调用,那么如果我们想在数据变化后再基于数据进行一些操作怎么办?这时候可以传入第二个参数:一个回调函数,该回调函数执行的时机就是数据更新完且render调用完毕后。

//3.setState是一个异步调用
//如果想等数据更新后做一些操作,可以传入第二个参数:回调
//第二个参数执行的时机就是数据更新完之后
this.setState({ name: 'ht' }, () => {
  console.log('数据已更新,值为', this.state.name)
})
console.log('数据未更新:', this.state.name); //zzy而不是ht

2.为什么setState要设置成异步

我们参考一下React的开发者Dan Abramov老哥的回答:

(1)提升性能,减少render次数

试想一下,如果我们写了下面这样的代码:

  changeName() {
    this.setState({
      name: this.state.name + 'ht'
    })
    this.setState({
      name: this.state.name + 'ht'
    })
    this.setState({
      name: this.state.name + 'ht'
    })
  }

如果是同步,那么每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用(源码貌似在setState后会执行render),界面重新渲染无数次,这样效率是很低的;

最好的办法应该是获取到多个更新,之后进行批量更新,那么内部是怎么实现的呢?

这里其实用到的是队列,我们把每一个setState的调用放到队列里,然后依次取出每一个更改,对改变的属性依次进行对象的合并,直到都合并完,再去执行render函数,这样的话render函数只执行一次就欧了。

2.1.1多个state合并的小问题

除此之外,这里还有个问题,上面代码执行完毕后,页面结果是'zzyht',不应该是'zzyhththt'吗?其实是这样的,在执行的时候就统一先读出来this.state.name,所以其实上面的代码相当于做了三步同样的操作:

  changeName() {
    this.setState({
      name: 'zzy' + 'ht'
    })
    this.setState({
      name: 'zzy' + 'ht'
    })
    this.setState({
      name: 'zzy' + 'ht'
    })
  }

这样的话每次合并,合并的值都是'zzyht',而不是累加。

那如果传入回调可以解决这个问题吗?

  changeName() {
    this.setState(() => {
      return {
        name: this.state.name + 'ht'
      }
    })
    this.setState(() => {
      return {
        name: this.state.name + 'ht'
      }
    })
    this.setState(() => {
      return {
        name: this.state.name + 'ht'
      }
    })
  }

上面这个写法仍然不能解决,和传入对象是一样的,但是下面这种写法就可以。原因是:这里回调的参数state是上一个合并状态的state,所以是可以在上一个的基础上做操作的!

  changeName() {
    this.setState((state) => {
      return {
        name: state.name + 'ht'
      }
    })
    this.setState((state) => {
      return {
        name: state.name + 'ht'
      }
    })
    this.setState((state) => {
      return {
        name: state.name + 'ht'
      }
    })
  }

(2)避免state和props数据不同步

如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步,这样会在开发中产生很多的问题;什么意思呢?举个例子

父组件:

export class Father extends React.Component {
  constructor() {
    super();
    this.state = {
      age: 18,
    }
  }
  changeAge() {
    this.setState({
      age: 100
    })
    //一大坨代码,要执行一年
  }
  render() {
    return (
      <div>
        <button onClick={(() => this.changeAge())}>点击修改年龄</button>
        <Son age={this.state.age}/>
      </div>
    )
  }
}

子组件:

export class Son extends React.Component {
  render() {
    let {age} = this.props;
    return (
      <div>
        <h1>{age}</h1>
      </div>
    )
  }
}

父组件把state中的age传给子组件,我们假设setState是一个同步的任务,那么如果此时在changeAge这个函数里有一大坨代码,要执行一年,那么要等执行完之后再去render,那在这一年里这state是更新成100了,但是子组件的props的age值始终都是18,这数据就不一样了。

所以意思就是让数据更新完后立马render,所以把setState设置成异步。

3.获取异步修改完数据的结果

两种方式:
1、刚才提到的,第一个参数是对象,第二个参数是回调,在第二个回调中,可以获取
2、在生命周期钩子componentDidUpdate

在React18之前,如果在setState外边包个setTimeout这种宏任务,那么setState会变成同步,但是在React18之后就没用了,怎么搞都是异步

三、PureComponent监测数据的原理

1.先来看一个问题

一般情况下,只要调用setState,就会重新调用render函数,但这样是不太合理的。下面的三个组件,App为父组件,Son1、Son2分别为两个子组件。

export class App extends React.Component {
    constructor() {
        super();
        this.state = {
            name: 'zzy',
            age: 18,
        }
    }

    changeName() {
        this.setState({
            name: 'zzy'
        })
    }

    render() {
        console.log('App的render执行')
        return (
            <div>
                <h1>{this.state.name}</h1>
                <button onClick={() => this.changeName()}>点击修改名字</button>
                <Son1 />
                <Son2 age={this.state.age} />
            </div>
        )
    }
}
export class Son1 extends React.Component {
  render() {
    console.log('Son1的render执行')
    return (
      <div>
        <h2>Son1</h2>
      </div>
    )
  }
}
export class Son2 extends React.Component {
  render() {
    console.log('Son2的render执行')
    let {age} = this.props;
    return (
      <div>
        <h1>{age}</h1>
      </div>
    )
  }
}

点击修改名字按钮时,把名字改成了和原来一样的值,你会发现控制台输出:
在这里插入图片描述
这里涉及到两个问题:
1、如果修改后的数据和原来的值一样,是不是就不用改了
2、如果子组件Son1Son2里没有数据改变,是不是子组件的render不需要每次都跟着父组件的render执行一遍?

2.sholdComponentUpdate()

还记得之前生命周期的那张图吗?
sholdComponentUpdate()是在数据修改前执行(此钩子内数据还没修改),先判断一下是否要执行render,它有两个参数:
参数一:newProps 修改之后,最新的props属性
参数二:newState 修改之后,最新的state属性

该方法返回值是一个boolean类型
返回值为true,那么就需要调用render方法;
返回值为false,那么久不需要调用render方法;
默认返回的是true,也就是只要state发生改变,就会调用render方法;
在这里插入图片描述
1、第一个问题,如果修改后的值和原来的相同,我们可以通过sholdComponentUpdate(newProps, newState)钩子优化。

就是在数据更新之前判断一下新值和旧值是否相等,如果相等就不执行render,如果不相等就执行render。

App中:
shouldComponentUpdate(newProp, newState) {
    console.log(newState); //{name: 'zzy', age: 18}
    if(this.state.name != newState.name) {
        return true;
    } else {
        return false;
    }
}

2、第二个问题,如果子组件没有数据的改变,那么就不需要跟着父组件重新执行render函数了,我们同样可以用sholdComponentUpdate钩子优化。

shouldComponentUpdate(newProps, newState) {
 console.log(newProps, newState);
 if (this.props.age != newProps.age || this.props.name != newProps.name) {
   return true;
 } else {
   return false;
 }

 //如果子组件有自己的state,也要判断一下
 // if (this.state.xxx != newState.xxx) {
 //   return true;
 // } else {
 //   return false;
 // }
}

这样就解决了问题2

3.引出PureComponent

上面解决了这两个问题,但是真的是非常的麻烦,如果有多个数据,往下层也传了多个数据,那么我们要对每一个数据都写一个判断吗?那真是麻烦的一塌糊涂。

不过不用担心,React给我们封装好了解决这两个问题的东西,那就是PureComponent

(1)类组件

直接继承PureComponent而不是Component

class App extends React.PureComponent {...}

(2)函数组件

函数组件没有PureComponent,我们使用memo包裹实现相同效果

import React,{memo} from 'react';

const Son1 = memo(function() {
  console.log('Son1的render执行')
  return (
    <div>Son1</div>
  )
})

export default Son1

4.PureComponent只监测第一层

PureComponent是如何监测stateprops的变化从而执行render函数的呢?源码中PureComponent只能监视第一层数据的改变,也就是复杂数据只看第一层地址变没变。

如果我们继承Component,那么往数组里push新对象并把数组重新赋值,只要执行setState就render,其实重新赋值的这个地址是没变的;

在这里插入图片描述

但是PureComponent的话不会render,因为监测不到第二层数据的改变,数组的地址没变就默认没变

所以我们一般不要直接去修改state中的数据,要修改内层数据的话,最好整个替换掉,给个新地址。可以回去看看我们的案例:购物车案例修改数组中某个对象的属性、删除数组中整个对象

5.PureComponent如何监测深层数据的改变

上面已经提到,我们如果想要在PureComponent下改变第二层第三层的深层数据,我们需要整个替换掉,给个新地址。具体来说就是对原来的state来一个浅拷贝newState,然后修改newState中的相应数据,最后把newState放到setState里,这样地址变了,PureComponent就能监视到,从而执行render。

比如之前的购物车案例:

class App extends React.PureComponent {
	......
	changeCount(index, count) {
	    //1.对原来的数组进行浅拷贝(内部元素地址还是指向原来)
	    const newBooks = [...this.state.books];
	    //2.修改浅拷贝后的里面的值
	    newBooks[index].count += count;
	    //3.此时我们输出books会发现books里面对应的值也变了
	    console.log(this.state.books[index].count);
	    //4.最后调用setState执行render函数更新视图,把newBooks的地址给它
	    this.setState({
	        books: newBooks,
	    })
	}
	......
}

四、ref获取元素或组件实例

1.ref的三种用法

import React,{createRef} from 'react';
export class App extends React.PureComponent {
    constructor() {
        super();
        this.state = {
            name: 'zzy',
            age: 18,
        }

        this.myRef = createRef(); //第二种
        this.getRef = null;//第三种
    }

    getDOM() {
        //1.第一种:标签绑定ref属性,通过this.refs.属性名拿到
        console.log(this.refs.title);
        //2.第二种:提前创建ref对象,createRef(),把创建好的对象绑定到元素上
        console.log(this.myRef.current);//current以最后一个为主
        //3.第三种:通过函数拿到
        console.log(this.getRef);
    }

    render() {
        return (
            <div>
                {/* 1.第一种 */}
                <h1 ref='title'>h1标题</h1>
                {/* 2.第二种 */}
                <h2 ref={this.myRef}>h2标题</h2>
                <h3 ref={this.myRef}>h3标题</h3>
                {/* 3.第三种 */}
                <h4 ref={(el) => {this.getRef = el}}>h4标题</h4>
                
                <button onClick={() => this.getDOM()}>点击获取DOM</button>
            </div>
        )
    }
}
  1. 第一种:标签绑定ref属性,通过this.refs.属性名拿到(目前已经废弃了,一般不用)
  2. 第二种:导入createRef函数,把函数调用结果作为ref属性值(提前保存结果对象,然后把对象给ref),最后通过结果对象的current属性拿到
  3. 第三种:ref属性传入一个回调,回调的参数就是当前元素,可以保存起来,然后拿到。

2.ref获取类组件实例

子组件定义一个方法sayHi

class Son extends PureComponent {
    sayHi() {
        console.log('Hi,I am son');
    }
    render() {
        return (
            <div>
                <h1>Son组件</h1>
            </div>
        )
    }
}

父组件点击按钮获取子组件实例,并调用它的sayHi方法:

export class App extends PureComponent {
    constructor() {
        super();
        this.state = {
            name: 'zzy',
            age: 18,
        }

        this.myRef = createRef(); //第1步.创建ref对象
    }

    getDOM() {
        //第3步.通过this.myRef.current拿到当前东西
        console.log(this.myRef.current);//current以最后一个为主
        this.myRef.current.sayHi(); //拿到子组件并调用其中的方法Hi,I am son
    }

    render() {
        return (
            <div>
                {/* 第2步:把创建的对象给ref属性 */}
                <Son ref={this.myRef}/>
                <button onClick={() => this.getDOM()}>点击获取组件</button>
            </div>
        )
    }
}

3.ref获取函数组件内的某个元素

如果我们的子组件定义为了函数呢?那函数组件哪来的实例,那我们就没办法获取组件实例了,只能获取函数组件的内的某个React元素。

这时候需要用到React.forwardRef

import {forwardRef} from 'react';
const Son = forwardRef(function(props, ref) {
    return (
        <div>
            <h1 ref={ref}>我是函数儿子</h1>
        </div>
    )
})

这样我们可以通过在子组件实例<Son ref={this.myRef}/>拿到子组件内的某个标签。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/382505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android kotlin实战之协程suspend详解与使用

前言 Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同&#xff0c;async 与 await 在 Kotlin 中并不是关键字&#xff0c;甚至都不是标准库的一部分。此外&#xff0c;Kotlin 的 挂起函数 概念为异步操作提供…

π型滤波器 计算_π型滤波电路

滤波器在功率和音频电子中常用于滤除不必要的频率。而电路设计中&#xff0c;基于不同应用有着许多不同种类的滤波器&#xff0c;但它们的基本理念都是一致的&#xff0c;那就是移除不必要的信号。所有滤波器都可以被分为两类&#xff0c;有源滤波器和无源滤波器。有源滤波器用…

重新认识 Java 中的内存映射(mmap)

mmap 基础概念 mmap 是一种内存映射文件的方法&#xff0c;即将一个文件映射到进程的地址空间&#xff0c;实现文件磁盘地址和一段进程虚拟地址的映射。实现这样的映射关系后&#xff0c;进程就可以采用指针的方式读写操作这一段内存&#xff0c;而系统会自动回写脏页到对应的文…

电源程控软件下载安装教程

软件&#xff1a;电源程控软件NS-PowerSupply 语言&#xff1a;简体中文 环境&#xff1a;NI-VISA 安装环境&#xff1a;Win10以上版本&#xff08;特殊需求请后台私信联系客服&#xff09; 硬件要求&#xff1a;CPU2GHz 内存4G(或更高&#xff09;硬盘500G(或更高&#xf…

2023年2月安全事件盘点

一、基本信息 2023年2月安全事件共造成约3796万美元损失&#xff0c;相较于上个月&#xff0c;安全事件数量与损失金额都有显著上升&#xff0c;其中Platypus Finance闪电贷攻击为单次利用损失之最高达850万美元。本月RugPull数量基本与上月持平&#xff0c;损失金额占比显著降…

网站打不开数据库错误等常见问题解决方法

1、“主机开设成功&#xff01;”上传数据后显示此内容&#xff0c;是因为西部数码默认放置的index.htm内容&#xff0c;需要核实wwwroot目录里面是否有自己的程序文件&#xff0c;可以删除index.htm。 2、恭喜&#xff0c;lanmp安装成功&#xff01;这个页面是wdcp的默认页面&…

用 Real-ESRGAN 拯救座机画质,自制高清版动漫资源

内容一览&#xff1a;Real-ESRGAN 是 ESRGAN 升级之作&#xff0c;主要有三点创新&#xff1a;提出高阶退化过程模拟实际图像退化&#xff0c;使用光谱归一化 U-Net 鉴别器增加鉴别器的能力&#xff0c;以及使用纯合成数据进行训练。 关键词&#xff1a;Real-ESRGAN 超分辨率 视…

一文彻底搞懂cookie、session、token、jwt!

前言 随着Web应用程序的出现&#xff0c;直接在客户端上存储用户信息的需求也随之出现。者背后的想象时合法的&#xff1a;与特定用户相关的信息都应该保存在用户的机器上。无论是登录信息、个人偏好、还是其他数据&#xff0c;Web应用程序提供者都需要有办法 将他们保存在客户…

电子技术——CMOS 逻辑门电路

电子技术——CMOS 逻辑门电路 在本节我们介绍如何使用CMOS电路实现组合逻辑函数。在组合电路中&#xff0c;电路是瞬时发生的&#xff0c;也就是电路的输出之和当前的输入有关&#xff0c;并且电路是无记忆的也没有反馈。组合电路被大量的使用在当今的数字逻辑系统中。 晶体管…

Educational Codeforces Round 144 (Rated for Div. 2)(A~C)

A. Typical Interview Problem从1开始&#xff0c;遇到3的倍数就在字符串后面加F&#xff0c;遇到5的倍数就在字符串后面加B&#xff0c;若遇到3和5的倍数&#xff0c;就加入FB&#xff0c;这样可以写一个无限长的字符串&#xff0c;给出一个长度最多为10的字符串&#xff0c;判…

CLion+Opencv+QT开发相关

一、QT安装和配置其实我并没有直接在Qt上开发&#xff0c;下载Qt而是因为&#xff1a;CLion可以通过Qt的MinGW作为Toolset&#xff0c;并且可以将Qt creator作为external tool&#xff1b;在进行Opencv的编译安装中可以用Qt自带的MinGW进行编译和安装&#xff0c;不用另外下载M…

C++类和对象:初始化列表、static成员和友元

目录 一. 初始化列表 1.1 对象实例化时成员变量的创建及初始化 1.2 初始化列表 1.3 使用初始化列表和在函数体内初始化成员变量的效率比较 1.4 成员变量的初始化顺序 1.5 explicit关键字 二. static成员 2.1 static属性的成员变量 2.2 static属性的成员函数 三. 友元 …

废气处理设备远程监控

当今工业迅速的发展&#xff0c;工业带给人们的经济效益显著&#xff0c;而同时污染问题也备受关注。国家环保标准对排放至大气的废气指标提出了更高的要求。面临着环保压力&#xff0c;企业为走可持续发展之路&#xff0c;为维护员工利益、改善工作环境及周边环境不受影响&…

一、Sping框架引入

OCP开闭原则 什么是OCP&#xff1f; OCP是软件七大开发原则当中最基本的一个原则&#xff1a;开闭原则 对什么开&#xff1f;对扩展开放。 对什么闭&#xff1f;对修改关闭。OCP原则是最核心的&#xff0c;最基本的&#xff0c;其他的六个原则都是为这个原则服务的。OCP开闭原则…

计算机行业回暖?看网友怎么说?

就业寒潮之下&#xff0c;去年的应届生们可谓哀嚎一片&#xff0c;不少人晒出自己的0offer秋招战绩。 就连过去无往不利的计算机行业&#xff0c;亦不例外。但今年开始&#xff0c;计算机行业逐渐有了回暖的迹象和讨论。 陆续有不少之前哭诉收获惨淡的计算机专业同学&#x…

防静电和浪涌TVS layout设计要点

电子产品精密化刚看过了CES2023&#xff0c;雷卯的外贸伙伴们看了最新的AR,VR,5G产品&#xff0c;新的电子产品更智能、更复杂&#xff0c;嵌入了脆弱和敏感的集成电路。这些设备的环境往往很恶劣&#xff0c;产生高水平静电和快速瞬态浪涌。这些ESD事件可能会干扰设备&#xf…

IIS之web服务器的安装、部署以及使用教程(图文详细版)

WEB服务器的部署 打开虚拟机后查看已经开放的端口&#xff0c;可以看到没有TCP 80、TCP 443&#xff0c;说明HTTP服务端口没有打开 打开我的电脑—双击CD驱动器 选择安装可选的Windows组件 选择应用程序服务器—打开Internet信息服务—选择万维网服务和FTP服务 一路确…

uniapp-首页配置

为了获取到后台服务器发来的数据&#xff0c;需要配置相应的网络地址。位置在main.js入口文件中。 import { $http } from escook/request-miniprogramuni.$http $http // 配置请求根路径 $http.baseUrl https://api-hmugo-web.itheima.net// 请求开始之前做一些事情 $http.…

Spring-Xml配置

一、Spring 简介 1.简介 文档下载地址&#xff1a;Index of /spring-framework/docs 1.简介 Spring framework 是 Spring 基础框架 学习Spring 家族产品 Spring framework SpringBoot SpringCloud Spring 能用来做什么 开发 WEB 项目 微服务 分布式系统 Spring framew…

云服务HCIE变题当天一把过!分享下学习备考和考试经验

大家好&#xff0c;我是誉天云服务学员刘同学。感谢在誉天的学习&#xff0c;让我在临考变题的情况下通过了云服务HCIE考试&#xff1b;也感谢誉天给我这次机会分享出学习备考和考试的经验。 算起来&#xff0c;我和誉天也是老朋友了&#xff1a;一开始是跟着邹老师学习云计算、…