首页 > 解决方案 > 响应自定义挂钩以记录页面渲染开始事件

问题描述

我正在为一个用于性能监控的 react js 应用程序编写一个日志框架。基本上,sdk 提供了两种方法来创建时间事件,并停止测量并将时间细节推送到它想要的任何地方。由于我也需要捕获数据获取部分,所以我想将创建的计时事件存储在 redux 存储中,以便稍后在获取数据时停止计时器。为了尽可能多地将视图组件与日志记录分离,我正在尝试编写两个自定义挂钩以用于以下目的。

  1. 如果具有给定键的状态中没有可用的内容,则创建计时事件并将其存储在状态中
  2. 通过提供的键在存储中查找计时事件并停止计时器

以下是我创建的钩子。

export const useStartRenderLogger = (
  component: string,
  key: string,
  eventParams?: any
): void => {
  const dispatch = useDispatch();
  const existingTimer = useGetTimerByKey(key); <= Selector which return timer for key
  const metrics = useMetrics(component); <= sdk hook to create a metric instance
  if (existingTimer) return;
  const timer = metrics.start(key, {
    ...eventParams,
    stage: TimeLogStages.Render,
  });
  dispatch(TimeLogActions.pushLogger(key, timer)); <= dispatch passing timing log and key so it can stored on state
};

export const useStopRenderLogger = (
  key: string,
  pageRendered: boolean,
  params?: any
): void => {
  const timer = useGetTimerByKey(key);
  const dispatch = useDispatch();
  if (!pageRendered || isUndefined(timer)) return;
  timer.stop(params);
  dispatch(TimeLogActions.popLogger(key));
};

我在组件的开头调用第一个,在渲染传递布尔值之前调用另一个,以指示是否已获取数据。

问题是useStartRenderLogger抛出与钩子的反应规则相关的错误。

index.js:1 Warning: React has detected a change in the order of Hooks called by MyComponent. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks

看来调度正在解决问题。我猜想将事件推入状态使useGetTimerByKey选择器更新并导致错误。如果该行被注释,则页面呈现没有错误。

我关于为什么抛出错误的假设是正确的吗?我可以让useGetTimeByKey选择器在状态改变时不更新吗?

标签: reactjsreduxreact-reduxreact-hooks

解决方案


问题:return上钩之前

问题是这两行:

if (existingTimer) return;
const metrics = useMetrics(component);

您必须在每次渲染时以相同的顺序调用相同的钩子。调用钩子不能是有条件的。所以你不能return在你的代码中有一个以上的钩子调用,因为这意味着useMetrics有时会被调用。

如果你想有一些条件逻辑,你可以把它移到useMetrics钩子里面。

可以使纯函数有条件。调用dispatch(action)可以是有条件的——但调用useDispatch不能。所以这段代码没问题:

export const useStartRenderLogger = (
  component: string,
  key: string,
  eventParams?: any
): void => {
  // call all hooks first
  const dispatch = useDispatch();
  const existingTimer = useGetTimerByKey(key);
  const metrics = useMetrics(component);
  // only return after all hooks are called
  if (existingTimer) return;
  // conditionally executed
  const timer = metrics.start(key, {
    ...eventParams,
    stage: TimeLogStages.Render,
  });
  dispatch(TimeLogActions.pushLogger(key, timer));
};

推荐阅读