【问题标题】:A react `useReducer` with some dependencies具有一些依赖关系的反应`useReducer`
【发布时间】:2021-09-03 17:02:52
【问题描述】:

我正在创建一个反应组件:TxtEditor
在编辑器内部,有一个useReducer 用于操作文本的钩子:
APPEND => 更改当前文本。
UPPERCASE => 转换大写字母。

但是reducer函数不是纯函数。存在一些依赖关系:
props.disabled => 忽略所有 reducer 操作。
props.onTxtChanged => 将在文本修改后执行。

所以我在函数组件内部创建了reducer函数。
这对useReducer 来说是个问题,因为每次渲染组件时,总是会重新创建一个具有不同引用的新的相同函数。
从而使useReducer 在下一次渲染时执行reducer 函数两次 - 并触发props.onTxtChanged 两次。我不知道为什么要这样做。

然后为了解决这个问题,我用useCallback包裹了reducer函数。
它似乎工作,但不是。因为props.onTxtChanged 可能由用户使用an inline function 传递。并且函数总是被重新创建(相同但引用不同),从而使useCallback 无用。

最后我在函数组件外部创建了一个reducer函数。
该功能通过引用始终相同,并使useReducer正常工作。
为了注入依赖项,我做了一个 HACK 像这样:

  const [state, dispatch] = useReducer(txtReducer, /*initialState: */{
    text  : 'hello',
    props : props, // HACK: a dependency is injected here
  });
  state.props = props; // HACK: a dependency is updated here

所以props可以在reducer函数中访问:

const txtReducer = (state, action) => {
  const props = state.props;
  if (props.disabled) return state; // disabled => no change
}

它可以工作,但它包含一个 hack。
我想要以专业的方式来做这些事情。 你有什么建议吗?

See the complete sandbox code here

【问题讨论】:

    标签: reactjs use-reducer


    【解决方案1】:

    这就是我会做的:

    function TxtEditor(props) {
      const [state, dispatch] = useReducer(txtReducer, { text  : 'hello'});
    
      // Wait for the state to change and only then emmit a text change
      useEffect(() => {
        props.onTextChange(state.text);
      }, [state])
    
      return <input onInput={handleInput} />
    
      function handleInput() {
        if (props.disabled) return; // <-- just don't fire an update
    
        // [...] call to your reducer
      }
    }
    

    reducer 只是一个带有一点逻辑的useState。所以只让他们处理一个(单一的)状态,不要让它一次对很多事情负责。他们也应该只负责实际的状态逻辑,而不是外部的东西,比如文本框是否被禁用。


    本文的 cmets 中描述的所需钩子的潜在解决方案:

    我希望你不介意打字稿。我只是觉得使用起来更容易。

    enum TextProcessorMode {
      APPEND,
    }
    
    interface TextProcessorActionOptions {
      disabled?: boolean;
      mode: TextProcessorMode;
    }
    
    interface TextProcesserAction {
      newText: string;
      options: TextProcessorActionOptions;
      /**
       * Is called when the text was successfully processed.
       */
      onChange(text: string): void;
    }
    
    
    export default function textProcessorReducer(
      state: string,
      action: TextProcesserAction
    ): string {
      if (action.options.disabled) return state;
    
      let newState: string;
    
      switch (action.options.mode) {
        case TextProcessorMode.APPEND:
          newState = state + action.newText;
          break;
        // Handle other modes
      }
    
      action.onChange(newState);
      
      return newState;
    }
    

    【讨论】:

    • 实际上我想创建一个自定义挂钩。我希望在钩子内处理条件逻辑和事件。像这样:const [text, dispatch] = useTextProcessor('hello', props),钩子将被多个组件共享,因此每个组件都有一个最少的代码。
    • @HeyyyMarco 嗯...从技术上讲,这将是一个新问题。但我会调查一下。如果这是您想要的,您应该将其添加到问题中/重写问题。
    • @HeyyyMarco 我在答案中附加了一些内容,但我要给你一个确切的答案,你真的必须创建一个新问题或用更多信息更新当前问题。
    • 好的,我将创建一个具有更详细要求的新问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-30
    • 2013-11-05
    • 1970-01-01
    • 1970-01-01
    • 2013-09-23
    • 1970-01-01
    • 2012-09-28
    相关资源
    最近更新 更多