【问题标题】:How to create a custom useReducer with built in decision and event triggering如何创建具有内置决策和事件触发的自定义 useReducer
【发布时间】:2021-09-03 18:55:49
【问题描述】:

我想创建一个自定义挂钩,比如说useTextProcessor(initialText, props)
这是一种用于存储和操作文本(字符串)的反应状态。
它使用useReducer 来制作累积状态。
代码是这样的:

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

const useTextProcessor = (initialText: string, props: TextEditorProps) => {
  const [state, dispatch] = useReducer(textReducerFunc, /*initialState: */{
    text  : initialText,
    props : props, // HACK: a dependency is injected here
  });
  state.props = props; // HACK: a dependency is updated here

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

一种黑客方式可以注入props,以便在textReducerFunc 中访问。
textReducerFunc 是处理文本的主要函数(取决于动作类型和道具状态)。

我不知道如何将依赖项 props 插入到 textReducerFunc 以专业的反应方式
如果我在useTextProcessor(initialText, props) 中声明了textReducerFunc
是的,我可以访问 props,但请稍等,因为它是一个子函数,所以每次调用 useTextProcessor 都会重新创建子函数。
这使得useReducer 在下一次渲染中执行textReducerFunc 两次
Ut 正在制作 onTextChanged 也将执行两次
useCallback 包装不会有任何效果,因为onTextChanged 接受任何函数(可能是静态或内联函数)。每次渲染时,内联函数的引用总是不同的。

这里是textReducerFunc 作品的详细信息:

interface TextState {
  props: TextEditorProps // holds my hack
  text: string // the actual data to be processed
}
interface TextAction {
  type: 'APPEND'|'UPPERCASE'
  payload?: string
}
const textReducerFunc = (state: TextState, action: TextAction) => {
  const props = state.props;
  if (props.disabled) return state; // disabled => no change

  switch(action.type) {
    case 'APPEND':
      const appendText = state.text + action.payload;
      props.onTextChanged?.(appendText); // notify the text has been modified
      return {...state, text: appendText};

    case 'UPPERCASE':
      const upperText = state.text.toUpperCase();
      props.onTextChanged?.(upperText); // notify the text has been modified
      return {...state, text: upperText};

    default:
      return state; // unknown type => no change
  } // switch
}

useTextProcessor的用法:

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>
  )
}

你能建议我如何使用 useReducer 与依赖没有任何黑客

Here my running sandbox

【问题讨论】:

    标签: javascript reactjs typescript react-hooks


    【解决方案1】:

    我建议将状态提升到父组件。你需要一个“onTextChange”监听器。这告诉我状态位于错误的组件中。我建议将其放入父组件中。

    type TextAction =
      | { type: 'APPEND'; payload?: string }
      | { type: 'UPPERCASE' }
      | { type: 'SET_DISABLED'; disabled: boolean };
    
    interface TextState {
      text: string;
      disabled: boolean;
    }
    
    const textReducerFunc = (state: TextState, action: TextAction): TextState => {
      console.log(action);
    
      switch (action.type) {
        case 'APPEND': {
          if (state.disabled) return state;
          return { ...state, text: state.text + action.payload };
        }
        case 'UPPERCASE': {
          if (state.disabled) return state;
          return { ...state, text: state.text.toLocaleUpperCase() };
        }
        case 'SET_DISABLED':
          return { ...state, disabled: action.disabled };
      }
    };
    
    function useTextReducer() {
      return useReducer(textReducerFunc, {
        disabled: false,
        text: ''
      });
    }
    
    function TextBox({
      text,
      dispatch
    }: {
      text: string;
      dispatch: Dispatch<TextAction>;
    }) {
      return (
        <React.Fragment>
          <div>{text}</div>
          <button onClick={handleAppendPress}>append 'world'</button>
          <button onClick={handleUppercaseClick}>uppercase</button>
        </React.Fragment>
      );
    
      function handleAppendPress() {
        dispatch({ type: 'APPEND', payload: 'world' });
      }
    
      function handleUppercaseClick() {
        dispatch({ type: 'UPPERCASE' });
      }
    }
    
    function App() {
      const [state, dispatch] = useTextReducer();
      const { text } = state;
    
      useEffect(() => {
        console.log(`the text changed: ${text}`);
      }, [text]);
    
      return (
        <React.Fragment>
          <label>is disabled: </label>
          <input
            checked={state.disabled}
            type="checkbox"
            onChange={handleDisabledChange}
          />
          <TextBox dispatch={dispatch} text={state.text} />
        </React.Fragment>
      );
    
      function handleDisabledChange() {
        dispatch({ type: 'SET_DISABLED', disabled: !state.disabled });
      }
    }
    

    (code here)

    根据我在您的代码中看到的内容(其中已有的按钮),您可能希望将“已禁用”复选框移动到 TextBox 组件中,从而进一步消除状态设置中的冗余。

    【讨论】:

    • 但是,对于每一个动作,我必须通过disabledonTextChanged。如果有很多动作并且TextProps 有很多属性,那么代码的可读性就会降低,并且更难更新/维护。
    • @HeyyyMarco 我想我终于知道你想要什么了。请查看更新后的答案。
    • 您的useTextReducer 与我原来的useTextProcessor 相似。在初始时设置依赖项,并且每次调用自定义挂钩时,看门狗都会更新依赖项。使用 useEffect 和通过 dispatch 更新而不是直接分配 state.depSomething = depSomething 有什么好处? onTextChanged 在每次渲染中的引用总是不同的,所以使用 useEffect 与直接赋值没有什么不同。
    • @HeyyyMarco 好吧,dispatch 实际上会导致挂钩重新“渲染”,而简单的赋值不会。我实际上不确定您的用例是什么。我个人会将onTextChange 回调从挂钩移到组件中。但你似乎不喜欢那样。也许其他人有更好的主意,但我似乎已经突破了自己的局限。
    • 是的,我知道。通常一个反应组件是由&lt;AnotherComponents /&gt;(UI 组合)组成的。我所做的是通过useMyWeirdBehavior() 进行行为组合。感谢您分享您的时间来解决我的问题。不是一个完美的设计,但它可以工作。
    猜你喜欢
    • 2019-10-06
    • 2013-11-05
    • 1970-01-01
    • 1970-01-01
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    • 2020-08-27
    • 1970-01-01
    相关资源
    最近更新 更多