以正确的方式调度行动

Posted

技术标签:

【中文标题】以正确的方式调度行动【英文标题】:Dispatch actions the proper way 【发布时间】:2020-01-21 19:50:37 【问题描述】:

请检查编辑

我正在尝试在我的应用中实现 sagas。

现在我正在以一种非常糟糕的方式获取道具。 我的应用程序主要包含来自其他来源的轮询数据。

目前,这是我的应用程序的工作方式:

我有 容器,其中有 mapStateToProps、mapDispatchToProps。

const mapStateToProps = state => 
  return 
    someState: state.someReducer.someReducerAction,
  ;
;

const mapDispatchToProps = (dispatch) => 
  return bindActionCreators(someAction, someOtherAction, ..., dispatch)
;

const something = drizzleConnect(something, mapStateToProps, mapDispatchToProps);

export default something;

然后,我有操作,如下所示:

import * as someConstants from '../constants/someConstants';

export const someFunc = (someVal) => (dispatch) => 
    someVal.methods.someMethod().call().then(res => 
        dispatch(
            type: someConstants.FETCH_SOMETHING,
            payload: res
        )

    )

reducers,如下所示:

export default function someReducer(state = INITIAL_STATE, action) 
    switch (action.type) 
        case types.FETCH_SOMETHING:
            return (
                ...state,
                someVar: action.payload
            );

我将 reducer 与 redux 的 combineReducers 组合起来,并将它们作为单个 reducer 导出,然后将其导入我的 store。

因为我用的是毛毛雨,所以我的rootSaga是这样的:

import  all, fork  from 'redux-saga/effects'
import  drizzleSagas  from 'drizzle'

export default function* root() 
  yield all(
    drizzleSagas.map(saga => fork(saga)),
  )

所以,现在,当我想在组件的 componentWillReceiveProps 内更新道具时,我会: this.props.someAction()

好的,它有效,但我知道这不是正确的方法。基本上,这是我能做的最糟糕的事情。

那么,现在,我认为我应该做什么:

创建不同的 saga,然后我将其导入到 rootSaga 文件中。这些 sagas 将每隔某个预定义的时间轮询源,并在需要时更新 props。

但我的问题是如何编写这些 sagas。

可以根据我上面提到的action、reducer和container给我举个例子吗?

编辑:

我设法听从了阿帕楚洛的指示。

到目前为止,我做了以下调整:

动作如下:

export const someFunc = (payload, callback) => (
            type: someConstants.FETCH_SOMETHING_REQUEST,
            payload,
            callback
)

还有 reducers,像这样:

export default function IdentityReducer(state = INITIAL_STATE, type, payload) 
    switch (type) 
        case types.FETCH_SOMETHING_SUCCESS:
            return (
                ...state,
                something: payload,
            );
...

我还创建了 someSagas

...variousImports

import * as apis from '../apis/someApi'

function* someHandler( payload ) 
    const response = yield call(apis.someFunc, payload)

    response.data
        ? yield put( type: types.FETCH_SOMETHING_SUCCESS, payload: response.data )
        : yield put( type: types.FETCH_SOMETHING_FAILURE )


export const someSaga = [
    takeLatest(
        types.FETCH_SOMETHING_REQUEST,
        someHandler
    )
]

然后,更新了 rootSaga

import  someSaga  from './sagas/someSagas'

const otherSagas = [
  ...someSaga,
]

export default function* root() 
  yield all([
    drizzleSagas.map(saga => fork(saga)),
    otherSagas
  ])

另外,api如下:

export const someFunc = (payload) => 
    payload.someFetching.then(res => 
        return data: res
    ) //returns 'data' of undefined but just "return data: 'something' returns that 'something'

所以,我想更新我的问题:

    我的 API 取决于商店的状态。如你所见, 我正在构建一个 dApp。所以,Drizzle(我按顺序使用的中间件 访问区块链),需要在我调用之前启动 API 并向组件返回信息。因此,

    一个。尝试使用 getState() 读取状态,返回空合同 (尚未“准备好”的合同) - 所以我无法获取信息 - 我 不喜欢从商店读取状态,但是...

    b.通过组件传递状态(this.props.someFunc(someState),返回我Cannot read property 'data' of undefined 有趣的是我可以console.log 状态(看起来还可以)并尝试仅返回 data: 'someData',props 正在接收数据。

    我是否应该运行 this.props.someFunc(),例如 componentWillMount()?这是更新道具的正确方法吗?

抱歉,帖子太长了,但我想准确一点。

为 1b 编辑:呃,这么多编辑 :) 我用未定义的解决方案解决了这个问题。只需要像这样编写 API:

export function someFunc(payload)  

    return payload.someFetching.then(res => 
            return ( data: res )   
    ) 

【问题讨论】:

【参考方案1】:

我不想强加我使用的模式,但我已经在几个应用程序中成功使用了一段时间(非常感谢任何人的反馈)。最好阅读并尝试找到最适合您和您的项目的方法。

这是我在提出解决方案时阅读的一篇有用的文章。还有一个,如果我能找到它——我会在这里添加它。

https://medium.com/@TomasEhrlich/redux-saga-factories-and-decorators-8dd9ce074923

这是我用于项目的基本设置。 请注意我对 saga util 文件的使用。我确实提供了一个没有它的用​​法示例。您可能会发现自己在此过程中创建了一些东西来帮助您减少此样板。 (甚至可能有助于处理您的投票场景)。

我非常讨厌样板。我什至创建了一个与我的 golang API 一起使用的工具,通过遍历 swagger doc/router 端点来自动生成一些样板。

编辑:添加容器示例。

示例组件

import React,  Component  from 'react'

import  connect  from 'react-redux'
import  bindActionCreators  from 'redux'
import  getResource  from '../actions/resource'

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    
      getResource
    ,
    dispatch
  )

class Example extends Component 
  handleLoad = () => 
    this.props.getResource(
      id: 1234
    )
  

  render() 
    return <button onClick=this.handleLoad>Load</button>
  


export default connect(
  null,
  mapDispatchToProps
)(Example)

示例操作/resource.js

import  useDispatch  from 'react-redux'

const noop = () => 
const empty = []

export const GET_RESOURCE_REQUEST = 'GET_RESOURCE_REQUEST'
export const getResource = (payload, callback) => (
  type: GET_RESOURCE_REQUEST,
  payload,
  callback,
)

// I use this for projects with hooks!
export const useGetResouceAction = (callback = noop, deps = empty) => 
  const dispatch = useDispatch()

  return useCallback(
    payload =>
      dispatch( type: GET_RESOURCE_REQUEST, payload, callback ),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [dispatch, ...deps]
  )

相当基本的 redux 动作文件。

reducers/resource.js 示例

export const GET_RESOURCE_SUCCESS = 'GET_RESOURCE_SUCCESS'

const initialState = 
  resouce: null


export default (state = initialState,  type, payload ) => 
  switch (type) 
    case GET_RESOURCE_SUCCESS: 
      return 
        ...state,
        resouce: payload.Data,
      
    

相当标准的 reducer 模式 - 注意这里使用 _SUCCESS 而不是 _REQUEST。这很重要。

示例 saga/resouce.js

import  takeLatest  from 'redux-saga/effects'

import  GET_RESOUCE_REQUEST  from '../actions/resource'

// need if not using the util
import  GET_RESOURCE_SUCCESS  from '../reducers/resource'

import * as resouceAPI from '../api/resource'

import  composeHandlers  from './sagaHandlers'

// without the util
function* getResourceHandler( payload ) 
    const response = yield call(resouceAPI.getResouce, payload);

    response.data
      ? yield put( type: GET_RESOURCE_SUCCESS, payload: response.data )
      : yield put(
          type: "GET_RESOURCE_FAILURE"
        );
  

export const resourceSaga = [
  // Example that uses my util
  takeLatest(
    GET_RESOUCE_REQUEST,
    composeHandlers(
      apiCall: resouceAPI.getResouce
    )
  ),
  // Example without util
  takeLatest(
    GET_RESOUCE_REQUEST,
    getResourceHandler
  )
]

某些资源的示例 saga 文件。这是我将 api 调用与每个端点的reosurce 数组中的reducer 调用连接起来的地方。然后这会传播到根传奇。有时您可能想使用 takeEvery 而不是 takeLatest —— 这一切都取决于用例。

示例 saga/index.js

import  all  from 'redux-saga/effects'

import  resourceSaga  from './resource'

export const sagas = [
  ...resourceSaga,
]

export default function* rootSaga() 
  yield all(sagas)

简单的root saga,看起来有点像root reducer。

util saga/sagaHandlers.js

export function* apiRequestStart(action, apiFunction) 
  const  payload  = action

  let success = true
  let response = 
  try 
    response = yield call(apiFunction, payload)
   catch (e) 
    response = e.response
    success = false
  

  // Error response
  // Edit this to fit your needs
  if (typeof response === 'undefined') 
    success = false
  

  return 
    action,
    success,
    response,
  


export function* apiRequestEnd( action, success, response ) 
  const  type  = action
  const matches = /(.*)_(REQUEST)/.exec(type)
  const [, requestName] = matches

  if (success) 
    yield put( type: `$requestName_SUCCESS`, payload: response )
   else 
    yield put( type: `$requestName_FAILURE` )
  

  return 
    action,
    success,
    response,
  


// External to redux saga definition -- used inside components
export function* callbackHandler( action, success, response ) 
  const  callback  = action
  if (typeof callback === 'function') 
    yield call(callback, success, response)
  

  return action


export function* composeHandlersHelper(
  action,
  
    apiCall = () => 
   = 
) 
  const  success, response  = yield apiRequestStart(action, apiCall)

  yield apiRequestEnd( action, success, response )

  // This callback handler is external to saga
  yield callbackHandler( action, success, response )


export function composeHandlers(config) 
  return function*(action) 
    yield composeHandlersHelper(action, config)
  

这是我的 saga util 处理程序的一个非常简短的版本。消化起来可能很多。如果你想要完整版,我会看看我能做什么。我的完整版处理诸如在 api 成功/错误时自动生成 toast 以及在成功时重新加载某些资源之类的东西。有一些处理文件下载的东西。还有一件事是处理可能发生的任何奇怪的内部逻辑(很少使用这个)。

【讨论】:

非常感谢您为回答我的问题付出的努力和时间,apachuilo!我会尝试你的建议,我会再次发布。 所以,我已经尝试了你建议的一切。我认为我完成了设置动作的“基本”事情,但仍然存在问题。请检查问题。我更新了它并添加了我所做的更改。

以上是关于以正确的方式调度行动的主要内容,如果未能解决你的问题,请参考以下文章

javascript Vuex101:行动和调度

手工场景-调度-笔记

链接 Redux 操作

Linux调度器 - deadline调度器

如何以编程方式为 Kendo Ui 调度程序调用导航事件

了解 dispatch_queues 和同步/异步调度