【问题标题】:React Hooks - useReducerWithCallbackReact Hooks - useReducerWithCallback
【发布时间】:2021-03-29 04:54:45
【问题描述】:

我正在重构我之前使用钩子“useStateWithCallback”的一个上下文的复杂状态管理。

我决定使用“useReducer”重构代码,使其更具可读性。

目前,我正在这样做:

export function ContentsProvider({ children }) {
  const [contents, setContents] = useStateWithCallback(new Map([]));

  const addContents = (newContents, callback = undefined) => {
    setContents(
      (prevContents) => new Map([...prevContents, ...newContents]),
      callback
    );
  };
  
  ...

现在,在我的任何使用此上下文的应用程序组件上,我都可以这样做:

contents.addContents([ ... ], () => { ... });

只有当状态发生变化时才会执行回调。

有什么方法可以用 useReducer 实现这种行为吗?我的意思是,如果我将回调作为参数传递给我的提供者的方法,那么我将无法运行

useEffect(() => { callback?.() } , [contents]);

有什么想法吗?

我试过这个:

import { useReducer, useEffect } from 'react'

export const useReducerWithCallback = (
  initialState,
  reducer,
  callback = undefined
) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    callback?.(state);
  }, [state, callback]);

  return [state, dispatch];
};

但它不起作用,因为在我的用例中,回调并不总是相同的,所以我不能用回调声明reducer。

【问题讨论】:

    标签: javascript reactjs react-native react-hooks


    【解决方案1】:

    如果我了解您在寻找什么,那么我认为使用 React ref 和效果的自定义钩子可能会实现您所寻求的行为。

    1. 使用 React ref 保存对回调的引用
    2. 当状态更新时使用useEffect调用回调
    3. 返回一个自定义的dispatch 函数来设置回调并向reducer 函数分派一个动作。

    代码

    const useReducerWithCallback = (reducer, initialState, initializer) => {
      const callbackRef = useRef();
      const [state, dispatch] = useReducer(reducer, initialState, initializer);
    
      useEffect(() => {
        callbackRef.current?.(state);
      }, [state]);
    
      const customDispatch = (action, callback) => {
        callbackRef.current = callback;
        dispatch(action);
      };
    
      return [state, customDispatch];
    };
    

    const useReducerWithCallback = (reducer, initialState, initializer) => {
      const callbackRef = React.useRef();
      const [state, dispatch] = React.useReducer(reducer, initialState, initializer);
    
      React.useEffect(() => {
        callbackRef.current && callbackRef.current(state);
      }, [state]);
    
      const customDispatch = (action, callback) => {
        callbackRef.current = callback;
        dispatch(action);
      };
    
      return [state, customDispatch];
    };
    
    const reducer = (state, action) => {
      switch (action.type) {
        case "ADD":
          return state + action.payload;
    
        case "SUBTRACT":
          return state - action.payload;
    
        default:
          return state;
      }
    };
    
    function App() {
      const [state, dispatch] = useReducerWithCallback(reducer, 0);
      return (
        <div className="App">
          State: {state}
          <button
            type="button"
            onClick={() =>
              dispatch({ type: "ADD", payload: 1 }, (state) =>
                console.log("Added 1, state", state)
              )
            }
          >
            +
          </button>
          <button
            type="button"
            onClick={() =>
              dispatch({ type: "SUBTRACT", payload: 1 }, (state) =>
                console.log("Subtracted 1, state", state)
              )
            }
          >
            -
          </button>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      rootElement
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

    【讨论】:

    • 太棒了!我正在寻找这个!从我们更新回调引用的方法调用调度就可以了!非常感谢
    【解决方案2】:

    Drew 的回答很棒!如果您是 TypeScript 用户,您可能会使用 @types/react 声明文件中的类型键入此函数:

    /**
     * Defines the custom dispatch function that wraps the dispatch returned from useReducer. 
     */
    type CustomReactDispatchWithCallback<A, S> = (action: A, callback?: DispatchCallback<S>) => void;
    
    /**
     * Defines the callback contract. It should be a function that receives the updated state.
     */
    type DispatchCallback<S> = (state: S) => void;
    
    /**
     * Wraps `React.useReducer` and provides a custom dispatch function that accepts 
     * a callback that will be cached and then invoked when the reducer state changes.
     */
    export function useReducerWithCallback<R extends React.Reducer<any, any>, I>(
      reducer: R,
      initialState: I & React.ReducerState<R>,
      initializer: (arg: I & React.ReducerState<R>) => React.ReducerState<R>
    ) {
      const callbackRef = React.useRef<DispatchCallback<React.ReducerState<R>>>();
    
      const [state, dispatch] = React.useReducer(reducer, initialState, initializer);
    
      React.useEffect(() => {
        callbackRef.current?.(state);
      }, [state]);
    
      const customDispatch: CustomReactDispatchWithCallback<
        React.ReducerAction<R>,
        React.ReducerState<R>
      > = (action, callback) => {
        callbackRef.current = callback;
        dispatch(action);
      };
    
      return [state, customDispatch] as const;
    }
    

    这并不能解释React.useReducer 的所有重载,但对我的情况有效。

    【讨论】:

    • 谢谢。不幸的是,这在&lt;R extends React.Reducer&lt;any, any&gt;, I&gt; 中的extends 中遇到了一个linting 错误:解析错误:意外的令牌,预期的“,”
    猜你喜欢
    • 1970-01-01
    • 2019-11-08
    • 2019-11-06
    • 2019-09-30
    • 2021-04-19
    • 2020-01-25
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多