:::info 目前类组件存在的问题:

  • 类组件中还存在比较冗余的写法,比如在进行事件监听的时候,需要在类组件的生命周期函数componentDidMount()中进行事件的订阅,在类组件的生命周期componentWillUnmount中移除相关的订阅函数,这个过程是比较复杂的。
  • useEffect Hook 可以完成一些类似于class类组件中生命周期的功能。
  • 事实上,类似于网络请求、手动更新DOM元素、事件的监听、事件的订阅都是React更新DOM的一些副作用(Side Effects)。
  • 执行时机:在组件第一次挂载时执行useEffect hook和组件的状态发生更新的时候执行useEffect hook函数。简单的理解代替我们手动操作DOM,我们只需要管理我们的状态数据,更新状态即可,而不再担心DOM结构发生的变化。

所以对于完成这些功能的Hook被称之为Effect Hook(副作用hook)。 :::

1、useEffect的使用原理

原理:模拟生命周期函数 作用:可以让我们在函数式组件中执行副作用(用于模拟类组件中的生命周期函数) React中的副作用:

  • 发送网络请求/获取数据
  • 事件的订阅与取消、定时器开启与关闭
  • 手动更改真实DOM结构

1.1 useEffect的语法说明

  1. # useEffect的语法说明与使用
  2. // 先引入 useEffect Hook 钩子函数
  3. import React, { useEffect } from 'react';
  4. useEffect(() => {
  5. // 执行相应的逻辑代码 模拟类组件componentDidMount与componentDidUpdate生命周期函数
  6. return () => { // 组件卸载前执行
  7. // 执行相应的逻辑 模拟类组件 componentWillUnmount生命周期函数
  8. // 在此作一些收尾的工作,比如清除定时器/取消事件的订阅
  9. }
  10. }, [])
  11. # 这个函数 最后的参数有三种形式
  12. 1、不传递参数 表示监听组件中所有的状态,一旦组件内部的状态发生变化,组件都会执行这个钩子函数。相当于componentDidUpdate生命周期函数。(第一个回调函数会执行、回调函数的返回值也会执行)
  13. 2、传递一个空数组,不管组件中的状态发生了什么变化,都不会执行这个函数。只会在组件第一次挂载时执行,相当于componentDidMount生命周期函数。如果是一个空数组,这个Hook函数只会在组件第一次render的时候执行一次,如果只需要执行一次的话,那么可以直接传入一个空数组即可,比如说我们的网络请求,只需要发送一次网络请求即可。
  14. 3、在数组中传入组件内部的状态。表示的是这个钩子的执行与传入的状态相互关联,当组件的状态发生变化时,这个钩子将会执行,组件将会重新进行渲染。如果传入的状态的值不发生变化。那么我们的组件将不会再进行渲染。只要我们依赖的状态发生了变化,对应组件的 useEffect Hook函数就会被执行。
  15. # 可以把useEffect看作三个函数的组合、配置和使用更加的灵活多变
  16. componentDidMount()
  17. componentDidUpdate()
  18. componentWillUnmount()

2、实时修改浏览器Title的值

案例:在组件创建的时候,可以修改浏览器title的值,在组件更新的时候,也可以修改title的值。就是需要区分在不同的时刻修改title的值。

2.1 类组件的实现

  1. # 使用类组件 来修改title的值
  2. import React, { PureComponent } from 'react';
  3. export default class ChangeTitle extends PureComponent {
  4. constructor(props) {
  5. super(props)
  6. this.state = {
  7. count: 100
  8. }
  9. }
  10. // 将初始化counter的值显示在浏览器标签页的title上面
  11. componentDidMount() {
  12. document.title = this.state.count
  13. }
  14. // 组件更新的时候 也需要进行展示 每当组件的状态发生变化的时候,组件就会进行重新render()、更新
  15. componentDidUpdate() {
  16. // 组件重新进行渲染的时候 该生命周期函数就会执行
  17. document.title = this.state.count
  18. }
  19. render() {
  20. return (
  21. <div>
  22. <h2>hook的案例演示</h2>
  23. <h2>当前计数: { this.state.count }</h2>
  24. <button onClick={ () => this.setState({ count: this.state.count + 10 })}>+10</button>
  25. </div>
  26. )
  27. }
  28. }

2.2 类组件实现的优化-函数的抽取

在上述的案例中,我们可以发现在componentDidMount生命周期和conponentWillUnmont生命周期中都在执行一个操作,如果是同一个操作,在两个地方都进行执行,把相同的代码抽取到函数中,直接调用函数,避免出现重复性的代码。

  1. # 改造优化后的代码
  2. import React, { PureComponent } from 'react';
  3. export default class ChangeTitle extends PureComponent {
  4. constructor(props) {
  5. super(props)
  6. this.state = {
  7. count: 100
  8. }
  9. }
  10. // 将初始化counter的值显示在浏览器标签页的title上面
  11. componentDidMount() {
  12. this.updateCounterAction()
  13. }
  14. // 组件更新的时候 也需要进行展示 每当组件的状态发生变化的时候,组件就会进行重新render()、更新
  15. componentDidUpdate() {
  16. this.updateCounterAction()
  17. }
  18. // 封装公用的函数 在需要的时候进行调用
  19. updateCounterAction() {
  20. document.title = this.state.count
  21. }
  22. # 进行简单的抽取以后,发现类组件的写法较之前的写法简洁 体现代码的封装性
  23. render() {
  24. return (
  25. <div>
  26. <h2>hook的案例演示</h2>
  27. <h2>当前计数: { this.state.count }</h2>
  28. <button onClick={() => this.setState({ counter: this.state.count + 10 })}>+10</button>
  29. </div>
  30. )
  31. }
  32. }

2.3 函数式组件实现-使用useEffect hook函数

  1. import React, { useState, useEffect } from 'react';
  2. export default function UpdateDocumentTitle() {
  3. // 使用useState hook
  4. const [count, setCount] = useState(110)
  5. // 表示的是 一旦我们的count状态发生了变化 组件就会重新进行相应的渲染
  6. useEffect(() => {
  7. document.title = count
  8. console.log('组件挂载了')
  9. return () => {
  10. console.log('组件即将被卸载了');
  11. }
  12. }, [count])
  13. // 当我们传入的参数count的值发生变化的时候 useEffect会自动进行调用 相当于监听count的值是否发生变化。
  14. return (
  15. <div>
  16. <p>hook案例的演示</p>
  17. <h2>当前计数{count}</h2>
  18. <button onClick={ () => setCount(count+ 10) }>修改数据</button>
  19. </div>
  20. )
  21. }

3、模拟生命周期函数中事件的订阅与取消

:::info useEffect可用于事件的订阅,useEffect的第一个参数是一个箭头函数,当我们组件第一次挂载和组件的状态发生更新的时机,那么useEffect的回调函数就会被执行,这个函数有一个返回值,当我们组件被卸载的时候,这个返回值的回调函数就会被执行。
useEffect钩子的第二个参数是一个数组,当我们传一个空数组的时候,表示这个useEffect钩子不依赖任何状态,只要我们的组件一更新,那么useEffect的回调函数就会执行。第二个数组参数 实际上可以传入依赖的状态,当依赖的状态发生改变的时候,我们useEffect的回调函数才会执行。 :::

  1. // 订阅与取消订阅的事件
  2. import { useEffect } from 'react';
  3. export default function Demo() {
  4. useEffect(() => {
  5. // 订阅事件 模拟类组件挂载的生命周期 相当于componentDidMount生命周期
  6. console.log('事件被订阅了')
  7. return () => {
  8. // 相当于componentWillUnmount组件生命周期
  9. console.log('事件取消订阅了')
  10. }
  11. // 第一个参数是 箭头函数 第二个参数为只读的数组类型, 这里为了做性能优化的测试
  12. // 如果不传递参数的化 每当我们组件的状态发生了变化 组件都会重新进行渲染 但是实际上我们组件的状态并没有发生相应的变化 不需要重新进行渲染。这是性能可以优化的一个点。
  13. // 自己组件内部发生变化的时候 组件也会进行渲染 需要进行性能优化
  14. }, [])
  15. return (
  16. <>
  17. <p>useEffect hook的具体使用</p>
  18. <div>测试事件的订阅和取消</div>
  19. </>
  20. )
  21. }

4、多个useEffect一起进行使用

  1. import React, { useState, useEffect } from 'react';
  2. export default function MultiuseEffectHookDemo() {
  3. // 定义初始化的状态
  4. const [counter, setCounter] = useState(200);
  5. // 可定义使用多个useEffect钩子 依赖conter状态的变化 counter状态发生了变化 这个useEffect将会重新执行
  6. useEffect(() => {
  7. console.log('操作dom');
  8. }, [counter])
  9. // 第二个参数是空数组 只会在第一次组件挂载的时候执行这个hook 不管组件的状态发生什么变化 这个hook都不会再执行
  10. useEffect(() => {
  11. console.log('订阅事件');
  12. }, [])
  13. // 如果不传递第二个参数的话 第一次组件挂载的时候会执行这个hook
  14. 在组件的其他状态发生改变的时候,不管组件的什么状态发生改变 都会执行这个hook函数
  15. useEffect(() => {
  16. console.log('网络请求');
  17. })
  18. // 对于只执行一次的操作 我们可以在useEffect函数的第二个参数中 传入一个空数组 便于性能优化
  19. // 数组中可传入相应的状态 以此来决定我们的组件是否进行更新渲染
  20. return (
  21. <div>
  22. <h2>MultiuseEffectHookDemo</h2>
  23. <h2>当前计数: { counter }</h2>
  24. <button onClick={ () => setCounter(counter + 10) }>修改数据</button>
  25. </div>
  26. )
  27. }
  28. # 注意:上述多个useEffect的执行顺序是按照useEffect定义的顺序进行执行的 定义在前面 就先进行回调
  29. # 如果传入的是一个空的数组 那么这个useEffect钩子函数只会在初始化的时候执行一次
  30. 后面的参数是一个数组,当我们传入的值 发生变化的时候 此时的useEffect将会被执行 由传入的参数决定
  31. 简单的说 就是由我们传入的参数来决定 传入的参数的值发生变化 那么对应的useEffect将会被执行
  32. 如果传入的参数的值 没有发生变化 那么对应useEffect将不会被执行。

5、useEffect Hook第二个参数的意义

:::info

  1. 当我们不传递第2个参数的时候,表示这个hook函数依赖组件所有的状态,一旦组件的状态发生改变,这个hook函数重新执行并进行相应的数据渲染。
  2. 当我们传一个空数组的时候,表示我们当前这个useEffect什么都不依赖,只会在组件第一次挂载的时候执行一次,后来不会再执行了。
  3. 当我们向数组里面传入一个状态变量的时候,表明我们只依赖这个状态,只有当我们这个状态发生改变的时候,我们这个hook函数才会执行,那么这个hook函数的第一个参数回调函数才会执行,我们可以在回调函数里面做相应的逻辑处理。
  4. 主要作用:用于react的性能优化,希望在某些时候我们在更改状态的时候,不会执行不相关的hook函数,这样的话,对于我们对组件内部状态的控制更加细腻,操作的颗粒度更小。 :::