如何将 thunk 或回调函数传递到 redux 操作中。在 redux 存储中序列化函数以获取模态和 toast 确认通知

How to pass a thunk or callback function into a redux action. Serializing functions in a redux store for modals and toast confirm notifications

使用带有确认按钮的通用模态或 toast 时,能够将操作传递到此组件会变得很有用,以便在您单击确认时可以分派它。

操作可能看起来像这样:

export function showConfirm({modalConfirm}) {
  return {
    type: 'MODALS/SHOW_MODAL',
    payload: {
      modalId: getUuid(),
      modalType: 'CONFIRM',
      modalConfirm : modalConfirm,
    },
  };
}

其中modalConfirm是另一个动作对象如:

const modalConfirm = {
  type: 'MAKE_SOME_CHANGES_AFTER_CONFIRM',
  payload: {}
}

modalConfirm 动作使用 dispatch(modalConfirm) 甚至 dispatch(Object.assign({}, modalConfirm, someResultFromTheModal)

在模态组件内部调度

不幸的是,此解决方案仅在 modalConfirm 是一个简单的 redux 操作对象时才有效。这个系统显然非常有限。无论如何你可以传递一个函数(比如一个 thunk)而不是一个简单的对象吗?

理想情况下,像这样功能齐全的东西:

    const modalConfirm = (someResultFromTheModal) => {
      return (dispatch, getState){
        dispatch({
          type: 'MAKE_SOME_UPDATES',
          payload: someResultFromTheModal
        })
        dispatch({
          type: 'SAVE_SOME_STUFF',
          payload: http({
            method: 'POST',
            url: 'api/v1/save',
            data: getState().stuffToSave
          })
        })
      }
    }

有趣的是,将一个动作对象放入商店并将其作为道具传递给通用对话框正是我自己想出的方法。实际上,我有一篇博客 post 正在等待发布,其中描述了这个想法。

您问题的答案是 "Yes, but...."。根据位于 http://redux.js.org/docs/FAQ.html#organizing-state-non-serializable 的 Redux 常见问题解答,完全可以将不可序列化的值(例如函数)放入您的操作和商店中。但是,这通常会导致时间旅行调试无法按预期工作。如果这不是您关心的问题,那就继续吧。

另一种选择是将模态确认分成两部分。让初始模态确认仍然是一个普通的操作对象,但使用中间件来监视正在调度的那个,并从那里做额外的工作。这是 Redux-Saga 的一个很好的用例。

我最终将字符串别名用于集中注册动作的动作库。

模态发射器动作包含具有 functionAliasfunctionInputs

的对象
export function confirmDeleteProject({projectId}) {
  return ModalActions.showConfirm({
    message: 'Deleting a project it permanent. You will not be able to undo this.',
    modalConfirm: {
      functionAlias: 'ProjectActions.deleteProject',
      functionInputs: { projectId }
    }
  })
}

其中 'ProjectActions.deleteProject' 是任何类型的复杂操作的别名,例如:

export function deleteProject({projectId}) {
  return (dispatch)=>{
    dispatch({
      type: 'PROJECTS/DELETE_PROJECT',
      payload: http({
        method: 'DELETE',
        url: `http://localhost:3000/api/v1/projects/${projectId}`,
      }).then((response)=>{
        dispatch(push(`/`))
      }),
      meta: {
        projectId
      }
    });
  }
}

函数在库模块中注册如下:

import * as ProjectActions from '../../actions/projects.js';

const library = {
  ProjectActions: ProjectActions,
}

export const addModule = (moduleName, functions) => {
  library[moduleName] = functions
}

export const getFunction = (path) => {
  const [moduleName, functionName] = path.split('.');

  // We are getting the module only
  if(!functionName){
    if(library[moduleName]){
      return library[moduleName]
    }
    else{
      console.error(`Module: ${moduleName} could not be found.`);
    }
  }
  // We are getting a function
  else{
    if(library[moduleName] && library[moduleName][functionName]){
      return library[moduleName][functionName]
    }
    else{
      console.error(`Function: ${moduleName}.${functionName} could not be found.`);
    }
  }
}

modalConfirm对象通过props传入modal。模态组件需要上面模块中的 getFunction 函数。 modalConfirm对象转化为函数如下:

const modalConfirmFunction = (extendObject, modalConfirm) => {
  const functionFromAlias = getFunction(modalConfirm.functionAlias);
  if(functionFromAlias){
    dispatch(functionFromAlias(Object.assign({}, modalConfirm.functionInputs, extendObject)));
  }
}

如你所见,这个函数可以接受来自模态的输入。它可以执行任何类型的复杂操作或 thunk。这个系统不会打破时间旅行,但集中式图书馆有点缺点。