【问题标题】:Trying to wrap dispatch function in react redux试图在反应 redux 中包装调度功能
【发布时间】:2020-08-25 10:10:23
【问题描述】:

嗨,最近我遇到了 useDispatch 钩子,它应该给我一个 mapDispatchToProps 的替代方案,我发现在每个 onPress 中重复 () => dispatch(action(args)) 所以我开始考虑一些通用的东西。我的目标是制作一个使用 useDispatch() 的钩子,并包装它获取的函数并返回 () => dispatch(theWrappedAction(theActionArgs)) 例如,如果我有一个动作 upCounterActionCreator 如下:

export const upCounterActionCreator = (count: number = 1): AppActions => {
const action: UpCounterAction = {
    type: 'UP_COUNTER',
    count
};
return action;
};

我的目标是做这样的事情:
const [upAfterDispatch] = useActions(upCounterActionCreator);

然后我可以这样做:

<Button onPress={upAfterDispatch(1)} title='+' />

我尝试做的如下:

export const useActions = (...actions: ((...args: any) => AppActions)[]) => {
const dispatch = useDispatch<Dispatch<AppActions>>();
const actionsWithDispach: ((...args: any) => () => (...args: any) => AppActions)[] = [];

actions.forEach(action => {
    actionsWithDispach.push((...args: any) => () => (...args: any) => dispatch(action(...args)));
});
return actionsWithDispach;

};

要将包装好的函数放在 onPress 上,我需要这样做

&lt;Button onPress={upAfterDispatch(1)()} title='+' /&gt; - 调用它,这不是很好的选择。 然后,当我确实调用它时,确实正在调度该动作,但是当我在我的有效负载上进行调试时,我有一个计数对象,如下所示: 这是一个类-

我做错了什么?我需要做什么才能:

  1. 获取数字 1(动作负载中发送的计数参数)而不是类

  2. 从 useActions 调用返回的函数,而不是像这样调用它onPress={upAfterDispatch(1)()

** 我认为 args 中收到的对象是 react native onPress 事件,如何避免它覆盖我的 count 参数? 先谢谢了!

【问题讨论】:

    标签: reactjs typescript react-native react-redux react-hooks


    【解决方案1】:

    我认为这就是你想要做的:

    export const useActions = (...actions: ((...args: any) => AppActions)[]) => {
      const dispatch = useDispatch<Dispatch<AppActions>>();
      const actionsWithDispach: ((...args: any) => () => (...args: any) => AppActions)[] = [];
    
      actions.forEach(action => {
        actionsWithDispach.push((...args: any) => () => dispatch(action(...args)));
      });
      return actionsWithDispach;
    };
    

    您添加了一个额外的(...args: any) =&gt;,但使用上面的代码您可以执行onClick={theAction(1)}

    const { Provider, useDispatch, useSelector } = ReactRedux;
    const { createStore } = Redux;
    const initialState = {
      count: 0,
    };
    const reducer = (state, { type, payload }) => {
      if (type === 'UP') {
        return { count: state.count + payload };
      }
      return state;
    };
    const store = createStore(
      reducer,
      { ...initialState },
      window.__REDUX_DEVTOOLS_EXTENSION__ &&
        window.__REDUX_DEVTOOLS_EXTENSION__()
    );
    //action
    const add = (howMuch) => ({ type: 'UP', payload: howMuch });
    const useAction = (action) => {
      const dispatch = useDispatch();
    
      return React.useMemo(
        () => (...args) => () => dispatch(action(...args)),
        [action, dispatch]
      );
    };
    const Button = React.memo(function Button({ up, howMuch }) {
      const rendered = React.useRef(0);
      rendered.current++;
      return (
        <button onClick={up(howMuch)}>
          Rendered: {rendered.current} times, add {howMuch}
        </button>
      );
    });
    
    const App = () => {
      const up = useAction(add);
      const count = useSelector((state) => state.count);
      return (
        <div>
          <h2>count:{count}</h2>
          <Button up={up} howMuch={1} />
          <Button up={up} howMuch={1} />
          <Button up={up} howMuch={1} />
        </div>
      );
    };
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
    
    
    <div id="root"></div>

    【讨论】:

    • @MD10 如果它不起作用,那么你做错了什么,我添加了一个工作代码 sn-p 与我的答案中的确切代码。这样做会导致不必要的渲染,因为您的动作每次都会重新创建,即使它们不需要创建。您可以在 useActions 中使用 useMemo 来防止这种情况发生。
    • 我没注意,你说得对!非常感谢你是最棒的!你能告诉我如何在这里使用 useMemo 吗?只是包装整个功能?它的目的是什么?
    • @MD10 我更新了答案,但使用 useMemo 您一次只能执行一项操作,否则即使操作没有更改,它也会重新创建操作。在示例中,您可以看到按钮没有重新呈现,因为它们是纯组件(使用 React.memo)并且向上操作永远不会改变(使用 useMemo)
    • @MD10 useMemo 不适用于多个参数,因为 ...args 每次调用钩子时都会创建一个新数组。因此,即使数组中的元素相同,useMemo 也会将其视为一个新数组,并且不会记忆调度动作函数。不记忆不一定会对性能造成很大影响,这取决于不必要的渲染发生的频率以及重新渲染的元素数量。它还取决于您的目标平台,低端手机将比高端台式机遭受更多损失。
    • @MD10 如果您在 Parent 中执行 &lt;Child onChange={()=&gt;console.log('hello')} /&gt; ,那么您始终将 onChange 的新引用传递给该组件,即使回调永远不会改变。但是当 Parent 永远不会重新渲染时,Child 也不会。在我的示例中,我有一个名为 App 的组件,它在每次计数更改时重新呈现,但 App 中的 Button 组件永远不会重新呈现,因为它们在单击时所做的事情保持不变。
    【解决方案2】:

    type ActionCreator = (...args: any) => AppActions
    export const useActions = (...actions: ActionCreator[]) => {
      const dispatch = useDispatch<Dispatch<AppActions>>();
      return actions.map(action => (...args: any) => () => dispatch(action(...args)))
    }

    【讨论】:

    • 如果我在调用之前添加 () => 就可以工作,因此它没有达到我的意图:/
    • 想避免使用 () => 总是
    • 上面的评论解决了我的问题,只是想弄清楚如何使用 useMemo 钩子以及为什么——如上所述——你可以提供一个答案,我将不胜感激,不过谢谢!跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-13
    • 2019-02-07
    • 2018-01-26
    • 1970-01-01
    • 2020-05-05
    相关资源
    最近更新 更多