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

如何无Hack实现带依赖、内置逻辑与事件触发的自定义useReducer

无hack实现方案

核心思路两点:

  • 用useRef存储最新的props,不需要将props塞入状态,也不需要直接修改state
  • 把reducer改造为无外部依赖的纯函数,副作用(onTextChanged回调)放到useEffect中触发,规避reducer多次执行导致的回调重复调用问题

完整实现代码如下:

import { useReducer, useRef, useEffect } from 'react';

interface TextEditorProps {
  disabled?: boolean
  onTextChanged?: (text: string) => void
}

interface TextState {
  text: string
}

interface TextAction {
  type: 'APPEND'|'UPPERCASE'
  payload?: string
}

// 纯函数reducer,无任何外部依赖,仅负责计算新状态
const textReducerFunc = (state: TextState, action: TextAction): TextState => {
  switch(action.type) {
    case 'APPEND':
      return { text: state.text + (action.payload ?? '') };
    case 'UPPERCASE':
      return { text: state.text.toUpperCase() };
    default:
      return state;
  }
}

const useTextProcessor = (initialText: string, props: TextEditorProps) => {
  // 用ref存储最新的props,更新ref不会触发重渲染
  const propsRef = useRef(props);
  // 每次渲染更新ref,保证永远拿到最新的props引用
  propsRef.current = props;

  const [state, originalDispatch] = useReducer(textReducerFunc, { text: initialText });

  // 封装原生dispatch,加入disabled判断逻辑
  const dispatch = (action: TextAction) => {
    if (propsRef.current.disabled) return;
    originalDispatch(action);
  }

  // 监听text变化,触发变更回调,仅在text实际修改时执行
  useEffect(() => {
    propsRef.current.onTextChanged?.(state.text);
  }, [state.text]);

  return [
    state.text,
    dispatch
  ] as const;
}

// 组件使用方式和原来完全一致,无需修改
export default function TxtEditor(props: TextEditorProps) {
  const [text, dispatch] = useTextProcessor('hello', props);

  return (
    <div>
      <div>{text}</div>
      <button onClick={() => dispatch({type: 'APPEND', payload: ' world'})}>append 'world'</button>
      <button onClick={() => dispatch({type: 'UPPERCASE'})}>uppercase</button>
    </div>
  )
}

方案说明

  1. 用useRef存props的方式完美解决了依赖传递问题,既不需要把props塞到state里,也不需要把reducer声明在hook内部导致重复创建
  2. 副作用从reducer中剥离后,reducer符合纯函数要求,即使React出于内部逻辑多次调用reducer也不会产生额外副作用,彻底解决回调触发两次的问题
  3. 无论传入的onTextChanged是静态函数还是内联生成的函数,propsRef都能拿到最新的引用,不会出现回调过期的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 09:57:01