Effect

Effect

status: Draft tags: React Created time: November 16, 2022 8:36 PM emoji: https://react.dev/favicon.ico

React组件本质上是一个函数,对于它而言最主要的目的是返回一个基于JSX语法的React组件来构建UI,但是除此之外也需要事件处理程序来处理用户交互。在react组件中有这两种逻辑结构:

  • rendering code

  • event handlers

rendering code 指的是描述UI的代码,react系统获取props、state和jsx来构建界面,当props或者state改变时会重新渲染;而event handlers则是用于处理用户交互的事件处理程序。

但是只有这两种实际上是还不够,有些情况下无法满足我们的需求,例如需要在组件首次渲染时发送http请求获取数据。这个时候无论你将request代码放到rendering code还是event handles 中都会有问题。

  • 如果你将request放到组件顶层作为rendering code,那么每次组件重新渲染时都会发送一次请求。

  • 如果你将request作为event handles,那么必须要用户进行交互才能发送请求,这显然不合要求。

因此这个时候需要一个新的概念:Effects。E**ffects允许你指定由渲染本身而不是特定事件引起的副作用。**react提供useEffect这个API来实现函数组件的effect。

useEffect的作用

**useEffect的作用是实现组件和外部系统的同步**。

在react中,实现外部系统和组件同步的常见方式是利用事件处理程序,在事件处理程序中实现组件和外部系统的同步,但是这种同步需要用户交互实现的,而对于没有用户交互的同步就需要使用useEffect了。

例如在组件首次渲染时播放音频:

const [isPlay,setIsPlay] = useState(false)
useEffect(()=> {
	audio.play();
},[isPlay])

useEffect的三种使用方式

useEffect 接收两个参数,第一个参数是回调函数,第二个函数是依赖数组,只有第一个参数是必须的。第二个参数可以传一个依赖数组,空数组,也可以直接不传,但是这三者之间差距却很大。

这里要注意的是,回调中使用到的状态,必须要在依赖数组中显示表明,系统会检测,如果回调中用到的状态并未在依赖数组中,则会报错。

  • 不传第二个参数 如果不传第二个参数,那么组件每次渲染都会执行回调(包含首次以及每次重新渲染)。

  • 第二个参数传递一个空数组 只有组件首次渲染才会执行回调。

  • 第二个参数传依赖数组 如果传递一个依赖数组,首次渲染时会执行回调,并且每次重新渲染时都会通过Object.is来比较依赖数组里的每一项的值与上次渲染时有没有变化,只有当依赖数组每一项都没变化才会跳过执行回调。

我们可以设计一个小程序来验证:

import { useState,useEffect } from "react";
import "./App.css";

function App() {
  const [state,setState] = useState(1)
  useEffect(() => {
    console.log("1. 不传递依赖数组");
  })
  useEffect(() => {
    console.log("2. 传递空的依赖数组");
  }, [])
  useEffect(() => {
    console.log("3. 传递依赖数组");
  },[state])

  function onClick() {
    setState((a)=>a+1)
  }

  return <div className="App">
    <button onClick={onClick}>re-render</button>
  </div>;
}

export default App;

通过点击re-render 按钮来改变state的值,从而触发重新渲染。由此可以实现组件首次渲染和重新渲染的场景,来观察控制台的值。要注意的是,react在开发环境下会执行Effect回调两次以便帮助开发者发现bug,生产环境下不会有这个问题。

cleanup

前面说过useEffect是在react组件中添加副作用,用于同步组件和外部系统,那么有时候我们需要清除这些副作用,例如在组件销毁时要清除定时器等。

注意事项

  1. useEffect应该只同步外部系统,如果没有外部系统,而您只想根据其他状态调整某些状态,则可能不需要useEffect。

  2. react在开发环境下会执行Effect回调两次依赖debug,生产环境下不会有这个问题。

参考

How to handle the Effect firing twice in development?

https://beta.reactjs.org/learn/synchronizing-with-effects