为什么创建自定义挂钩总是会触发重新渲染,而直接使用 useDispatch 和 useSelector 则不会?

Why creating a custom hook always triggers re-renderings and using useDispatch and useSelector directly do not?

我在一个项目中使用了一个模式,其中大部分状态都驻留在 redux 上。我还尝试使应用程序逻辑接近 redux,因此我使用选择器并将所有可能的操作包装到自定义挂钩中。

运行 一些分析测试我注意到,当我使用我的自定义挂钩(下面的示例)时,所有使用它们的组件都会在 redux 状态更改时重新渲染:

export const useDefinitionForm = () => {
  const { description, step } = useAppSelector((state) => {
    const { name, color, icon, duration } = state.descriptionForm;
    return { description: { name, color, icon, duration }, step: state.descriptionForm.step };
  });

  const dispatch = useAppDispatch();

  return { description, step, actions: bindActionCreators(actions, dispatch) };
};

但是,当我直接在目标组件上使用 useDispatch 和 useSelector 挂钩时,这些组件只会在特定的 redux 部分发生变化时重新渲染:

const DurationSection = () => {
  const dispatch = useAppDispatch();
  const duration = useAppSelector((state) => state.descriptionForm.duration);
  return (
    <Section title="Expected duration" hint={duration} step="duration">
      <Typography variant="subtitle1">How much will this task usually last?</Typography>
      <DurationSlider
        value={duration}
        onChange={(value) => dispatch(setDuration(value))}
        valueLabelDisplay="on"
      />
    </Section>
  );
};

这是为什么?如何更改自定义挂钩,使其与直接在组件上使用挂钩一样高效?我尽量让组件保持愚蠢。

自定义挂钩中的选择器return每次都是一个新对象:

export const useDefinitionForm = () => {
  const { description, step } = useAppSelector((state) => {
    const { name, color, icon, duration } = state.descriptionForm;
    return { description: { name, color, icon, duration }, step: state.descriptionForm.step };
  });

  const dispatch = useAppDispatch();
  //you are returning a new object here every time
  return { description, step, actions: bindActionCreators(actions, dispatch) };
};

您的另一个示例中的选择器没有:

const duration = useAppSelector(
  (state) => 
    //you are only returning a value from state here
    state.descriptionForm.duration
);

您传递给 useSelecor 的所有函数将在每次状态更改时执行,如果该函数 return 的值已更改,您的组件将重新呈现。

您可以使用重新选择来记住先前选择器的结果,并且只有 return 一个新对象,如果任何重新使用的选择器 return 一个更改的值:

const selectDescriptionForm = state => state.descriptionForm;
//you can create selectors to selectStep, selectDuration, selectIcon ...
//  and use those instead if this still causes needless re renders
const selectDuration = createSelector(
  [selectDescriptionForm],
  //the following function will only be executed if
  //  if selectDescriptionForm returns a changed value
  ({ name, color, icon, duration, step })=>({
    description: { 
      name, color, icon, duration 
    }, step
  })
)
export const useDefinitionForm = () => {
  const { description, step } = useAppSelector(selectDuration);
  const dispatch = useAppDispatch();
  //making sure actions is not needlessly re created
  const actions = useMemo(
    ()=>bindActionCreators(actions, dispatch),
    [dispatch]
  )
  return { description, step, actions };
};

这是一个更新版本,选择您需要的每个描述项,如果任何项发生更改,将重新创建组件数据:

const selectDescriptionForm = (state) =>
  state.descriptionForm;
const selectName = createSelector(
  [selectDescriptionForm],
  (descriptionForm) => descriptionForm.name
);
const selectColor = createSelector(
  [selectDescriptionForm],
  (descriptionForm) => descriptionForm.color
);
const selectIcon = createSelector(
  [selectDescriptionForm],
  (descriptionForm) => descriptionForm.icon
);
const selectDuration = createSelector(
  [selectDescriptionForm],
  (descriptionForm) => descriptionForm.duration
);
const selectStep = createSelector(
  [selectDescriptionForm],
  (descriptionForm) => descriptionForm.step
);
const selectCompoentData = createSelector(
  [
    selectName,
    selectColor,
    selectIcon,
    selectDuration,
    selectStep,
  ],
  //the following function will only be executed if
  //  any of the functions in the previous list returns
  //  a changed value
  (name, color, icon, duration, step) => ({
    description: {
      name,
      color,
      icon,
      duration,
    },
    step,
  })
);
export const useDefinitionForm = () => {
  const { description, step } = useAppSelector(
    selectCompoentData
  );
  const dispatch = useAppDispatch();
  //making sure actions is not needlessly re created
  const actions = useMemo(
    () => bindActionCreators(actions, dispatch),
    [dispatch]
  );
  return { description, step, actions };
};