hooks 系列三:useEffect

快来加入我们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。

"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!

前言

这篇文章,我们主要目的是了解一下 共享状态钩子(useEffect) 的使用.

useEffect

定义

Effect Hook 可以让你在函数组件中执行副作用操作,换句话说可以完成一些类似 Class 中生命周期的功能。

如何使用

useEffect() 的使用如下面语句。

useEffect(() => {
    dosomeing(); // 执行一些副作用操作
},[]);

useEffect 需要参数

  • 第一个参数:该 Hook 接收一个包含命令式、且可能有副作用代码的函数。

  • 第二个参数:该 useEffect 在哪些 state 发生变化时,才重新执行;

useEffect 做了什么

通过 useEffectHook,可以告诉 React 需要在渲染后执行某些操作;而 useEffect 的第一个参数要求我们传递一个函数,在 React 执行完更新 DOM 操作之后,就会执行这个函数,默认情况下,无论是组件第一次渲染之后,还是组件更新之后,都会执行这个函数,我们可以在这个要传递的函数中执行一些类似 Class 组件中生命周期要做的工作,比如:改变 DOM、添加订阅、设置定时器、记录日志等。 要知道,在没有这个 useEffect Hook 之前在函数组件内的这些副作用操作是不被允许的。

提示

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

清除 effect

Class 组件中,某些副作用代码,我们需要在 componentWillUnmount 中清除,比如我们的创建计时器与清除计时器,同样的,在函数组件卸载时也需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。以下就是一个创建计时器的例子:

const [count, addCount] = useState(0)
const [timer, addTimer] = useState(1000)

useEffect(() => {
  const interval = setInterval(() => {
    // 创建计时器
    addCount((val) => val + 1)
  }, timer)
  return () => {
    // 返回清除函数
    clearInterval(interval) // 清除计时器
  }
})

为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除。在上述示例中,意味着组件的每一次更新都会创建新的计时器。若想避免每次更新都触发 effect 的执行,这就需要用到 useEffect 的第二个参数。

effect 的条件执行

默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被重新创建。

然而,在某些场景下这么做可能会矫枉过正。比如,上面计时器的例子中,我们并不需要在每次组件更新时都创建新的计时器,而是仅需要在 source prop 改变时重新创建。

这里就要用到 useEffect 接受的第二个参数,它是 effect 所依赖的值数组。更新后的示例如下:

const [count, addCount] = useState(0)
const [timer, addTimer] = useState(1000)

useEffect(() => {
  const interval = setInterval(() => {
    // 创建定时器
    addCount((val) => val + 1)
  }, timer)
  return () => {
    clearInterval(interval) // 清除定时器
  }
}, [count])

当传入第二参数的时候,在这里也就是当 count 改变后才会重新创建计时器,也就是说,useEffect 会根据我们传入的第二参数,来决定本次更新是否执行。

注意

  • 如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
  • 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。

对比类组件

相信你现在已经对 useEffect 有所了解了,那我们用一个简单的例子,来感受一下在函数组件中使用 HookClass 组件的区别。

有一个简单的计时器,默认以 1000ms 的时间间隔计数,点击加按钮后,计时时间间隔增加,计时器计数变慢。

类组件实现

首先我们要在类组件中声明一个变量 count 来计数,一个变量 timer 来控制计数时间间隔,每隔 timer 个时间, count 加一,然后添加一个按钮,每次点击时, timer 增加 1000ms。同时 count 增加速度变慢。

class Example extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
      timer: 1000,
    }
  }

  componentDidMount() {
    this.interval = setInterval(() => {
      // 创建定时器
      this.setState({
        count: this.state.count + 1,
      })
    }, this.state.timer)
  }
  componentDidUpdate() {
    clearInterval(this.interval) // 先清除上一个定时器
    this.interval = setInterval(() => {
      // 创建以 更新后的timer 计时间隔定时器
      this.setState({
        count: this.state.count + 1,
      })
    }, this.state.timer)
  }
  componentWillUnmount() {
    clearInterval(this.interval) // 卸载时清除定时器
  }

  render() {
    return (
      <div>
        <p>当前count值为:{this.state.count}</p>
        <p>当前计数时间间隔为:{this.state.timer}ms</p>
        <button onClick={() => this.setState({ timer: this.state.timer + 1000 })}>Click me</button>
      </div>
    )
  }
}

我们可以发现,在这个 class 中,我们需要在两个生命周期函数中编写重复的代码。还要在组件卸载时清除计时器,大量的重复代码,显得非常繁琐,那么在函数组件中使用 Effect Hook 会是怎样的呢?

使用 Effect Hook 实现

import React, { useState, useEffect } from 'react'

function Example() {
  const [count, addCount] = useState(0)
  const [timer, addTimer] = useState(1000)

  useEffect(() => {
    const interval = setInterval(() => {
      addCount((val) => val + 1)
    }, timer)
    return () => {
      clearInterval(interval)
    }
  }, [count])

  return (
    <div>
      <p>当前count的值为:{count}</p>
      <p>当前计数时间间隔为: {timer}ms</p>
      <button onClick={() => addTimer((val) => val + 1000)}>Click me</button>
    </div>
  )
}

看了这个例子之后,你是不是很惊奇的发现,对比 Class 组件的 constructor 函数没有了,写在三个生命周期中的函数在函数组件被 useEffect 一个家伙解决了!是不是看起来很精简。相信你有下面几个问题:

  • useEffect 做了什么?

    通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。

  • 为什么在组件内部调用 useEffect

    useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。

  • useEffect 会在每次渲染后都执行吗?

    是的,默认情况下,它在第一次渲染之后每次更新之后都会执行(你可以利用前面提到的第二个参数来控制渲染条件)。而且,React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

小结

  • useEffect Hook 实际上为我们带来了,在函数组件中也能处理副作用的功能,他好比componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

  • useEffect HookClass 函数的生命周期不同的一点:useEffect 在 DOM 都已经更新完毕才执行,componentDidMount 则是在组件挂在后(插入 DOM 树中)立即调用,componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。componentWillUnmount() 会在组件卸载及销毁之前直接调用。

  • useEffect Hook 的出现之前,组件开发中逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。而它的出现帮助我们更好的拆分这些状态逻辑,并非强制按照生命周期划分,为开发增添了许多的可能。

下节预告

在下节中,我们将为大家介绍 useRefs ,敬请期待!

React Hooks是React 16.8版本引的一种新特性,它可以让你在函数组件中使用状态(state)和其他React特性,而无需编写类组件。通过Hooks,你可以在无需修改组件结构的情况下,复用状态逻辑,并且使组件更加简洁和易于理解。 Hooks提供了一系列的钩子函数,最常用的是useState()和useEffect()。useState()可以在函数组件中声明和使用状态,并且可以通过函数调用来更新状态。例如,你可以使用useState()来创建一个计数器: ``` import React, { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } ``` 在上面的例子中,useState(0)初始化了一个名为count的状态变量,并将其初始值设置为0。setCount是一个用于更新count的函数。每次点击按钮时,我们通过调用setCount来更新count的值。 另一个常用的钩子函数useEffect(),它用于处理副作用操作,比如订阅数据、网络请求或者手动修改DOM。useEffect()接受两个数:一个回调函数和一个依赖数组。回调函数将在组件渲染时执行,并且可以返回一个清理函数。依赖数组用于定义在依赖项改变时是否重新运行回调函数。 这只是React Hooks的简单介绍,还有其他很多有用的Hooks,比如useContext()、useReducer()等。Hooks提供了一种更加灵活和直观的方式来管理组件状态和副作用,使得函数组件的编写更加简单和可维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值