likes
comments
collection
share

React useEffect和Vue watchEffect

作者站长头像
站长
· 阅读数 16

前言

最近在学 react 的 useEffect,感觉用法、功能和 vue3 的 watchEffect 很类似,这里就对比下两者的区别。

说明

useEffect:

该 Hook 接收一个包含命令式、且可能有副作用代码的函数。 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。 默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。 —— React 官网文档

watchEffect:

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。 ——Vue 官网文档

官网上两者的描述差别很大,useEffect 用于在函数组件中的副作用操作,组件每次渲染后执行。watchEffect 类似于 watch immediate,立即执行一个函数,依赖改变时重新执行。 两者官网上都提到副作用,副作用是函数式编程的一个概念。

所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。

函数式编程纯函数只能进行数据计算,而副作用就是数据计算无关的操作。

使用

useEffect:

function Welcome(props) {
  useEffect(() => {
    document.title = `Hello, ${props.name}`;
  }, [props.name]);
  return <h1>Hello, {props.name}</h1>;
}

useEffect 第一个参数是一个函数,要执行的副作用函数,第二个参数是一个数组,指定副作用函数的依赖项,当依赖项发生变化时,副作用函数才会执行。如果第二个参数为空,副作用函数在组件初始渲染后执行一次,后面组件重新渲染就不会执行。

watchEffect:

const count = ref(0)

watchEffect(() => console.log(count.value),{})
// -> 输出 0

count.value++
// -> 输出 1

watchEffect 第一个参数也是要执行的副作用函数,watchEffect 会自动监听副作用函数中的依赖,不用指定副作用函数的依赖,第二个参数是一个可选的选项,可以用来调整副作用的刷新时机。 用法上 useEffect 和 watchEffect 类似,useEffect 的描述重点在执行副作用上,也有监听依赖的的功能。watchEffect 的描述重点在监听依赖上,也提到了执行副作用函数。

使用场景

React 中经常会使用 useEffect,使用场景一般是:

  • React 与一些外部系统(网络、订阅、DOM) 同步的方法
import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
   const connection = createConnection(serverUrl, roomId);
    connection.connect();
   return () => {
      connection.disconnect();
   };
  }, [serverUrl, roomId]);
  // ...
}
  • 获取数据
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);

  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    };
  }, [person]);
  • 监听依赖
function useChatRoom({ serverUrl, roomId }) {
  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, serverUrl]);
}
  • 输出日志
  • 事件监听或订阅

由于 useEffect 在第一次渲染之后和每次更新之后都会执行并且依赖通过参数指定,依赖处理不当容易造成执行多次渲染,而且依赖过多非常容易造成循环依赖的问题,而且一旦出了问题,非常难排查很解决,因此不推荐用 useEffect 用于监听依赖,更多的用于其他副作用执行。

watchEffect 使用场景

  • 监听依赖
 const userID = ref(0)
 watchEffect(() => console.log(userID))
  • 防抖请求数据
watchEffect(() => {
      // 异步api调用,返回一个操作对象
      const apiCall = someAsyncMethod(props.userID)

      onInvalidate(() => {
        // 取消异步api的调用。
        apiCall.cancel()
      })
})

watchEffect 不用传参指定依赖,会自动地追踪其依赖,没有太大的使用负担,而且 watchEffect 默认是组件渲染之前执行执行的,执行多次对组件的渲染没有太大影响,watchEffect 更多使用在监听依赖上,而在纯函数的副作用操作上使用比较少。

useEffect 和 watchEffect 的作用很类似,但在具体使用上侧重点不一样。