【问题标题】: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'

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

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

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

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

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

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

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

export function someFunc(payload)  {

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

【问题讨论】:

    标签: javascript reactjs react-redux redux-thunk drizzle


    【解决方案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!我会尝试你的建议,我会再次发布。
    • 所以,我已经尝试了你建议的一切。我认为我完成了设置动作的“基本”事情,但仍然存在问题。请检查问题。我更新了它并添加了我所做的更改。
    猜你喜欢
    • 2019-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-29
    • 2012-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多