useEffect的那些坑,你知道多少

4,574 阅读1分钟

引言

我发现很多前端工程师们在处理一些计算合并、事件流转的过程中,喜欢用 useEffect 来处理各类问题。

例如:

import { useRequest } from 'ahooks';
import React, { useEffect, useState } from 'react';

export function comp1({ setParams }) {
  return <div onClick={() => setParams({ a: 1 })}>comp1</div>;
}

export function comp2({ defaultParams }) {
  const { run } = useRequest(async () => {});

  // 利用 defaultParams 的变化,运行某个事件
  useEffect(() => run(), [defaultParams]);

  return <div>comp2</div>;
}

export function Page() {
  const [defaultParams, setDefaultParams] = useState({});
  return (
    <>
      <comp1 setParams={setDefaultParams} />
      <comp2 defaultParams={defaultParams} />
    </>
  );
}
import React, { useEffect, useState } from 'react';

export function Page({ defaultParams }) {
  const [params, setParams] = useState(defaultParams);
  useEffect(() => {
    setParams({ ...defaultParams, appid: 'appid' });
  }, [defaultParams]);

  return <>{JSON.stringify(params)}</>;
}

上面两个例子算是比较典型的例子,都是想要说:当 xxx 变化,然后执行 xxx。

这都是典型的错误使用了 useEffect,因为对于useEffectsetState 的操作,对于 React 来说并不是同步执行的,React 对于 useEffect 的解释非常清楚,useEffect

image.png

image.png

useEffectuseState这类方法都是有延时的执行,因为对于 React 来说,状态的变更可能还需要处理 VDom 的生成,Diff算法,状态合并等等。这些都需要借助异步的执行才更为高效。useEffect更是需要等待渲染结束后才会执行。

虽然这可能只是几毫秒的事情,但是如果依赖这些来做事件传递,流程处理,就意味着会出现许多中间态,因为他们不是线性同步在运行。

如果给一句话就是,请不要依赖 React 的状态变化及渲染,来干扰数据变化

最后给下上述2个例子的正确解法:

跨组件事件通信

import { useEventEmitter, useRequest } from 'ahooks';
import React from 'react';

export function comp1({ event$ }) {
  return <div onClick={() => event$.emit()}>comp1</div>;
}

export function comp2({ event$ }) {
  const { run } = useRequest(async () => {});

  event$.useSubscription(() => run());

  return <div>comp2</div>;
}

export function Page() {
  // 可以采用 Context 进行跨组件层级传递
  const event$ = useEventEmitter();
  return (
    <>
      <comp1 event$={event$} />
      <comp2 event$={event$} />
    </>
  );
}

计算属性

import { useCreation } from 'ahooks';
import React from 'react';

export function Page({ defaultParams }) {
  const params = useCreation(() => ({ ...defaultParams, append: 'append' }), [defaultParams]);

  return <>{JSON.stringify(params)}</>;
}