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

替换useEffect为自定义usePropertyHistory致useReducer状态重置

自定义usePropertyHistory钩子导致useReducer状态重置问题

我正在开发一个组件,需要在属性值从已定义变为未定义时执行特定逻辑。为此我实现了usePropertyHistory自定义钩子,它和原生useEffect的差异点:

  • 仅接受单个依赖项
  • 回调函数包含前后值参数
  • 采用深度比较而非Object.is进行依赖判断

但替换原生useEffect使用该钩子后,组件内的useReducer状态会被重置为初始值,而用原生useEffect时完全正常。


正常运行的代码(使用原生useEffect)

const MyComponent = ({ value }: ComponentProps) => {
  const initialState: DropDownBaseState = {
    isMultiSelect: allowMultiSelect,
    isListOpen: false,
    isPassedValue: false,
  };

  const [state, dispatcher] = useReducer(reducer, initialState);

  useEffect(() => {
    if (value) {
      dispatcher({
        type: DropDownBaseActionType.SetPassedValue,
        passedValue: value,
      });
    }
  }, [value]);

  // 当上面的dispatcher调用后,状态变更会触发这个effect执行
  useEffect(() => {
    if (state.selectedValue && state.selectedValue.length > 0 && textBoxRef.current) {
      textBoxRef.current.setInnerText(state.selectedValue[0].text);
    } else if (state.selectedValue && state.selectedValue?.length < 1) {
      setShowPlaceholder(true);
    } else if (!state.selectedValue) {
      setShowPlaceholder(true);
    }
  }, [state.selectedValue]);

};

替换为自定义钩子后的问题代码

const MyComponent = ({ value }: ComponentProps) => {
  const initialState: DropDownBaseState = {
    isMultiSelect: allowMultiSelect,
    isListOpen: false,
    isPassedValue: false,
  };

  const [state, dispatcher] = useReducer(reducer, initialState);

  usePropertyHistory((previousValue, currentValue) => {
    if (currentValue) {
      dispatcher({
        type: DropDownBaseActionType.SetPassedValue,
        passedValue: currentValue,
      });
    } else if (previousValue && !currentValue) {
      // 当前测试场景下这段逻辑从未执行
      dispatcher({
        type: DropDownBaseActionType.ClearSelectedValue,
      });
    }
  }, value);

  // 上面的dispatcher调用后,这个effect从未触发...
  // 状态变更会在下一次渲染时发生,但紧接着的渲染中状态会被重置回初始值
  useEffect(() => {
    if (state.selectedValue && state.selectedValue.length > 0 && textBoxRef.current) {
      textBoxRef.current.setInnerText(state.selectedValue[0].text);
    } else if (state.selectedValue && state.selectedValue?.length < 1) {
      setShowPlaceholder(true);
    } else if (!state.selectedValue) {
      setShowPlaceholder(true);
    }
  }, [state.selectedValue]);

};

自定义usePropertyHistory钩子实现

const usePropertyHistory = <T>(callback: (previous: T | undefined, current: T | undefined) => void, property: T) => {
  const previousProperty = useRef<T>();
  if (!deepEqual(previousProperty.current, property)) {
    callback(previousProperty.current, property);
    previousProperty.current = property;
  }
}

已排查的测试结果

  • 问题与React Strict Mode无关
  • 即使仅调用SetPassedValue动作,仍会出现状态重置,排除ClearSelectedValue逻辑的影响
  • 用useMemo包裹初始状态无法解决问题

想知道为什么原生useEffect能正常工作,但自定义钩子会导致状态重置?求各位提供解决思路。


内容的提问来源于stack exchange,提问作者Beehive Software Consultants

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:58:12