为什么创建自定义挂钩总是会触发重新渲染,而直接使用 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 };
};
我在一个项目中使用了一个模式,其中大部分状态都驻留在 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 };
};