Skip to content

React Effect Event

首先有 3 个概念:

  • Event 事件
  • Effect 副作用
  • Effect Event 副作用事件

Event 的概念

Event 的概念是由行为触发,而不是状态触发的逻辑。

Effect 的概念

Effect 是由某些状态触发,而不是某些行为触发的逻辑。 提交表单是一个 Event(由提及行为触发),Event 逻辑应该只写在事件回掉中,而不是 useEffect 中。

js
function App() {
  const [data, updateData] = useState(null);

  useEffect(() => {
    fetchData().then((data) => {
      // ...一些业务逻辑
      // 更新data
      updateData(data);
    });
  }, []);

  function onSubmit(opt) {
    fetchData(opt).then((data) => {
      // ...一些业务逻辑
      // 更新data
      updateData(data);
    });
  }

  // ...
}

但在实际项目中,随着逻辑越来越复杂,可能出现下面的代码:

js
useEffect(() => {
  fetchData(options).then((data) => {
    // ...一些业务逻辑
    // 更新data
    updateData(data);
  });
}, [options, xxx, yyy, zzz]);

很难区分 fetchData 会在什么情况下执行,因为:

  1. useEffect 依赖项太多了
  2. 很难掌握每个依赖项变化的逻辑。

useEffect 的依赖问题

下面的代码 useEffect 里我要读取最新的 theme,但是不想 theme 添加到依赖列表里,因为不符合逻辑。

js
useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();

  connection.on("connected", () => {
    showNotification("连接成功!", theme);
  });

  return () => connection.disconnect();
}, [roomId, theme]);

这个时候 useEffectEvent 就登场了:

js
function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification("连接成功!", theme);
  });

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    connection.on("connected", () => {
      onConnected();
    });

    return () => {
      connection.disconnect();
    };
  }, [roomId]);

  // ...
}

useEffectEvent 的实现的源码是:

js
function updateEvent(callback) {
  const hook = updateWorkInProgressHook();
  // 保存callback的引用
  const ref = hook.memoizedState;
  // 在useEffect执行前更新callback的引用
  useEffectEventImpl({ ref, nextImpl: callback });

  return function eventFn() {
    if (isInvalidExecutionContextForEventFunction()) {
      throw new Error("A function wrapped in useEffectEvent can't be called during rendering.");
    }
    return ref.impl.apply(undefined, arguments);
  };
}

在 useEffect 执行前会将 callback 保存到 ref 的引用上,并且必须保证他在 Effect (isInvalidExecutionContextForEventFunction)里面执行,否则就会抛出一个错误。另外他每次都返回一个全新的函数 eventFn(),而不是像 useCallback(()=>{}, [])返回一个不变的引用。

总结

  • Event 由某些行为触发
  • Effect 由某些状态变化触发
  • Effect Event 在 Effect 内执行,但 Effect 并不依赖其中的状态逻辑。并且它始终返回不同的引用。