You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Redux Toolkit自定义Hook无法获取最新状态值的问题

问题

自定义Hook中使用useSelector获取Redux状态时,始终只能拿到初始值,无法响应状态更新,但相同用法在其他组件和Hook中正常工作。

相关代码如下:

1. 自定义Hook useGoodsSetupWizardController

import { isNullOrUndefined } from '../../../../../../utils/utils';
import { useSelector } from 'react-redux';

export const useGoodsSetupWizardController = () => {
    const productSetupWizard = useSelector((state) => state.productSetupWizard.value);

    function isNextButtonDisabled() {
        let returnValue = false;
        switch(productSetupWizard?.step) {
            case 2:
                if(isNullOrUndefined(productSetupWizard?.product?.productName) || productSetupWizard?.product?.productName.trim().length === 0) {
                    returnValue = true;
                }
        }
        return returnValue;
    }

    return {
        isNextButtonDisabled,
    };
}

2. Redux Toolkit Slice代码

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  value: { 
    step: 1, 
    product: null 
  },
};

export const productSetupWizardSlice = createSlice({
  name: 'productSetupWizard',
  initialState,
  reducers: {
    setProductSetupWizard: (state, action) => {
      state.value = action.payload
    },
    increaseStep: (state, action) => {
      state.value.step++;
    },
    decreaseStep: (state, action) => {
      state.value.step--;
    }
  },
});

export const { setProductSetupWizard, increaseStep, decreaseStep } = productSetupWizardSlice.actions;

export default productSetupWizardSlice.reducer;

3. 组件调用逻辑

UI组件 ProductSetupWizard

import { useProductTypeSetupWizard } from '../../../../factories/productSetup.factory';

function ProductSetupWizard(props) {
    const [productSetupController, setProductSetupController] = useState(null);
    ...
    const { getController } = useProductTypeSetupWizard();
    ...
    
    useEffect(() => {
            setProductSetupController(getController(productTypeId));
    },[productTypeId]);
   
    return (
        <>
        ...
        </>
    );
}

工厂Hook useProductTypeSetupWizard

import { isNullOrUndefined } from "../utils/utils";
import { useGoodsSetupWizardController } from '../components/managementInterface/shop/tabs/productSetupWizard/setups/goodsSetupWizard.controller';

export const useProductTypeSetupWizard = () => {
    const {
        getComponentGoodsController,
        isNextButtonDisabledGoodsController,
        needsNextStepToBeSkippedGoodsController
    } = useGoodsSetupWizardController();

    function getController(productTypeId) {
        if(isNaN(parseInt(productTypeId))) {
            return null;
        }
        switch(parseInt(productTypeId)) {
            case 1:
                return {
                    getComponent: getComponentGoodsController,
                    isNextButtonDisabled: isNextButtonDisabledGoodsController,
                    needsNextStepToBeSkipped: needsNextStepToBeSkippedGoodsController
                };
            case 3:
                //apply like useGoodsSetupWizardController
        }
    }

    return { 
        getController
    };
}

疑问:自定义Hook在React-Redux(Toolkit)中的使用是否有特殊要求?该问题的原因是否是自定义Hook仅初始化一次导致始终拿到初始状态值?


解决方案

问题根源

问题出在闭包陷阱和状态缓存方式上:

  1. useProductTypeSetupWizard 初始化时只调用一次 useGoodsSetupWizardController,此时返回的 isNextButtonDisabledGoodsController 函数会捕获当时的productSetupWizard初始值,后续Redux状态更新时,这个旧闭包不会同步更新,所以始终返回初始值的判断结果。
  2. 组件里用useState缓存getController返回的对象,除非productTypeId变化,否则这个状态不会更新;即使Redux状态变了,缓存的函数还是旧的闭包,拿不到最新状态。

修复步骤

1. 调整工厂Hook的逻辑,避免提前缓存函数

修改useProductTypeSetupWizard,不在顶层提前解构Hook返回值,而是直接返回对应Hook的完整实例(确保Hook在顶层调用,符合Hook规则):

import { isNullOrUndefined } from "../utils/utils";
import { useGoodsSetupWizardController } from '../components/managementInterface/shop/tabs/productSetupWizard/setups/goodsSetupWizard.controller';

export const useProductTypeSetupWizard = () => {
    function getController(productTypeId) {
        if(isNaN(parseInt(productTypeId))) {
            return null;
        }
        switch(parseInt(productTypeId)) {
            case 1:
                return useGoodsSetupWizardController();
            case 3:
                // 对应其他类型的Hook调用
                // return useOtherSetupWizardController();
        }
    }

    return { 
        getController
    };
}

2. 组件中直接使用最新的控制器实例,不存入useState

修改ProductSetupWizard,每次渲染时直接获取最新的控制器,避免缓存旧函数:

import { useProductTypeSetupWizard } from '../../../../factories/productSetup.factory';

function ProductSetupWizard(props) {
    const { productTypeId } = props;
    const { getController } = useProductTypeSetupWizard();
    // 每次渲染都拿到最新的控制器和内部函数
    const productSetupController = getController(productTypeId);
   
    return (
        <>
            <button disabled={productSetupController?.isNextButtonDisabled()}>下一步</button>
            ...
        </>
    );
}

3. 可选:优化useSelector的更新逻辑

如果需要减少不必要的重渲染,可以给useSelector添加浅比较函数:

import { useSelector, shallowEqual } from 'react-redux';

export const useGoodsSetupWizardController = () => {
    // 浅比较value对象,只有属性变化时才更新
    const productSetupWizard = useSelector(
        (state) => state.productSetupWizard.value,
        shallowEqual
    );

    // 剩余逻辑不变
}

React-Redux中自定义Hook的使用注意事项

  • 严格遵循Hook规则:只能在组件或自定义Hook的顶层调用,不能放在循环、条件判断或嵌套函数里。
  • Hook返回的函数会捕获当前渲染周期的状态值,要避免缓存这些函数,否则会陷入闭包陷阱,拿不到最新状态。
  • 不要把Hook返回的函数存入useState或useRef中,除非你明确知道缓存的目的和后果。

内容的提问来源于stack exchange,提问作者dns_nx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 00:15:43