# 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`了。

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

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

### useEffect的三种使用方式

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

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

* 不传第二个参数 **如果不传第二个参数，那么组件每次渲染都会执行回调（包含首次以及每次重新渲染）。**
* 第二个参数传递一个空数组 **只有组件首次渲染才会执行回调。**
* 第二个参数传依赖数组 **如果传递一个依赖数组，首次渲染时会执行回调，并且每次重新渲染时都会通过`Object.is`来比较依赖数组里的每一项的值与上次渲染时有没有变化，只有当依赖数组每一项都没变化才会跳过执行回调。**

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

```tsx
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#how-to-handle-the-effect-firing-twice-in-development)

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