以正确的方式调度行动
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 正在接收数据。
抱歉,帖子太长了,但我想准确一点。
为 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!我会尝试你的建议,我会再次发布。 所以,我已经尝试了你建议的一切。我认为我完成了设置动作的“基本”事情,但仍然存在问题。请检查问题。我更新了它并添加了我所做的更改。以上是关于以正确的方式调度行动的主要内容,如果未能解决你的问题,请参考以下文章