7

请检查编辑

我正在尝试在我的应用程序中实现 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
            });

我将减速器与 redux 的 combineReducers 结合起来,并将它们导出为单个减速器,然后将其导入我的商店。

因为我使用毛毛雨,所以我的 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。

但我的问题是这些传奇应该如何写。

根据我上面提到的动作、reducers 和容器,你可以给我一个例子吗?

编辑:

我设法按照 apachuilo 的指示行事。

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

动作是这样的:

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

减速器,像这样:

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

我还创建了一些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
    )
]

然后,更新了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。因此,在调用 API 并将信息返回给组件之前,需要启动 Drizzle(我用来访问区块链的中间件)。因此,

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

    湾。通过组件传递状态(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 })   
    }) 
}
4

1 回答 1

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,
      }
    }
}

相当标准的减速器模式 - 注意这里使用 _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 调用与每个端点的数组中的 reducer 调用连接起来以获取资源。然后这会传播到根传奇。有时您可能想要使用 takeEvery 而不是 takeLatest ——这一切都取决于用例。

例如传奇/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。

实用传奇/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 以及在成功时重新加载某些资源之类的东西。有一些处理文件下载的东西。还有另一件事是处理可能必须发生的任何奇怪的内部逻辑(很少使用这个)。

于 2019-09-20T20:24:27.353 回答