试图在反应 redux 中包装调度功能

Posted

技术标签:

【中文标题】试图在反应 redux 中包装调度功能【英文标题】:Trying to wrap dispatch function in react 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(动作负载中发送的计数参数)而不是类

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

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

【问题讨论】:

【参考方案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 钩子以及为什么——如上所述——你可以提供一个答案,我将不胜感激,不过谢谢!跨度>

以上是关于试图在反应 redux 中包装调度功能的主要内容,如果未能解决你的问题,请参考以下文章

调度后立即反应 Redux Store 值

反应-Redux |调度员不工作

如何在弹出窗口中包装元素?

包装或不包装:在服务外观中包装数据访问

如何在 Javascript 中包装函数?

如何在多行反应原生 textInput 中包装文本