以正确的方式调度动作

Geo*_*Pal 6 javascript drizzle reactjs redux-thunk react-redux

请检查编辑

我正在尝试在我的应用程序中实现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;
Run Code Online (Sandbox Code Playgroud)

然后,我有一些动作,像这样:

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

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

    })
}
Run Code Online (Sandbox Code Playgroud)

减速器,如下所示:

export default function someReducer(state = INITIAL_STATE, action) {
    switch (action.type) {
        case types.FETCH_SOMETHING:
            return ({
                ...state,
                someVar: action.payload
            });
Run Code Online (Sandbox Code Playgroud)

我将reduce和redux的combinedReducers组合在一起,并将它们导出为单个reduce,然后导入到我的商店中。

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

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

export default function* root() {
  yield all(
    drizzleSagas.map(saga => fork(saga)),
  )
}
Run Code Online (Sandbox Code Playgroud)

因此,现在,当我想要更新道具时,在componentWillReceiveProps组件的内部,我做了: this.props.someAction()

好的,它可以工作,但是我知道这不是正确的方法。基本上,这是我最糟糕的事情。

所以,现在,我认为我应该做的是:

创建不同的sagas,然后将其导入rootSaga文件中。这些sagas会每隔预定时间轮询一次源,并在需要时更新道具。

但是我的问题是应该如何编写这些故事。

您是否可以根据我上面提到的操作,reducer和容器给我一个示例?

编辑:

我设法遵循了apachuilo的指示。

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

行动是这样的:

export const someFunc = (payload, callback) => ({
            type: someConstants.FETCH_SOMETHING_REQUEST,
            payload,
            callback
})
Run Code Online (Sandbox Code Playgroud)

reducers,像这样:

export default function IdentityReducer(state = INITIAL_STATE, {type, payload}) {
    switch (type) {
        case types.FETCH_SOMETHING_SUCCESS:
            return ({
                ...state,
                something: payload,
            });
...
Run Code Online (Sandbox Code Playgroud)

我还创建了一些Sagas

...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
    )
]
Run Code Online (Sandbox Code Playgroud)

然后,更新了rootSaga

import { someSaga } from './sagas/someSagas'

const otherSagas = [
  ...someSaga,
]

export default function* root() {
  yield all([
    drizzleSagas.map(saga => fork(saga)),
    otherSagas
  ])
}
Run Code Online (Sandbox Code Playgroud)

此外,API如下:

export const someFunc = (payload) => {
    payload.someFetching.then(res => {
        return {data: res}
    }) //returns 'data' of undefined but just "return {data: 'something'} returns that 'something'
Run Code Online (Sandbox Code Playgroud)

因此,我想更新我的问题:

  1. 我的API取决于商店的状态。如您所知,我正在构建dApp。因此,在调用API并将信息返回给组件之前,必须启动Drizzle(我用来访问区块链的中间件)。从而,

    一种。尝试使用getState()读取状态时,会向我返回空合同(尚未“就绪”的合同)-因此我无法获取信息-我不喜欢从商店中读取状态,但是...

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

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

抱歉,很长的帖子,但我想保持准确。

编辑1b:嗯,这么多的编辑:)我用未定义的解决方法解决了这个问题。只需编写如下的API:

export function someFunc(payload)  {

    return payload.someFetching.then(res => {
            return ({ data: res })   
    }) 
}
Run Code Online (Sandbox Code Playgroud)

apa*_*ilo 1

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

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

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

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

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

编辑:添加了容器示例。

示例组件

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)
Run Code Online (Sandbox Code Playgroud)

示例操作/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]
  )
}
Run Code Online (Sandbox Code Playgroud)

相当基本的 redux 操作文件。

示例减速器/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,
      }
    }
}
Run Code Online (Sandbox Code Playgroud)

相当标准的减速器模式 - 请注意此处使用 _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
  )
]
Run Code Online (Sandbox Code Playgroud)

某些资源的示例 saga 文件。这是我将 api 调用与每个端点数组中的化简器调用连接起来以获取资源的地方。然后这会传播到根传奇中。有时您可能想使用 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)
}
Run Code Online (Sandbox Code Playgroud)

简单的根传奇,看起来有点像根减速器。

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

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