分享 Redux Actions 和 Reducers
介绍
在 Redux 中,reducer 和 action 通常是一对一映射。对于大多数实际用途来说,这是成立的,因为我们期望单个 action 的结果会对存储中的单个点产生影响。但是对于复杂的应用程序,需要 action 和 Reducer 相互共享。在本指南中,我们将探讨两种这样的实际场景以及可用的生产级解决方案。
为了提供示例的背景,我们创建了一个简单的表单提交应用程序。下面给出了表单 UI、操作和表单的 Reducer。
// SimpleForm.jsx
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { formSubmitAction } from 'formActions.js';
const SimpleForm = (props) => {
const formData = useState({});
const dispatch = useDispatch();
return (
<div class="myform">
...
<button onClick={() => { dispatch(formSubmitAction(formData)) }}>Submit Form</button>
</div>
)
}
export default SimpleForm;
// formActions.js
import axios from 'axios';
export const FORM_ACTION_REQUEST = "FORM_ACTION_REQUEST";
export const FORM_ACTION_SUCCESS = "FORM_ACTION_SUCCESS";
export const FORM_ACTION_ERROR = "FORM_ACTION_ERROR";
export function formActionRequest(){
return {
type: FORM_ACTION_REQUEST
}
}
export function formActionSuccess(result){
return {
type: FORM_ACTION_SUCCESS,
payload: result,
message: "Form Action Success!"
}
}
export function formActionError(error){
return {
type: FORM_ACTION_SUCCESS,
error: error
}
}
export const formAction = (formData) => {
return async function(dispatch) {
dispatch(formActionRequest());
try{
let response = (await axios.post("http://yourapi.com/form", formData)).data;
dispatch(formActionSuccess(response));
}catch(error){
dispatch(formActionError(response.error));
}
}
}
//formReducer.js
const initState = {
responseData: {}
};
export function formReducer(state = initState, action){
// reducer is kept blank since we do not have anything specific
// for the moment
return state;
}
在本指南中,我们将参考上述组件来阐明示例。
在多个 Reducer 之间共享单个 Action
假设我们正在构建一个复杂的 Web 应用,该应用通过弹出通知为用户操作提供反馈。例如,当用户提交表单时,会弹出通知以确认提交成功或报告任何错误。
虽然这听起来很简单,但在使用 React 和 Redux 实现时会出现一些复杂情况。从逻辑上讲,Redux 操作(例如formAction)应该能够执行以下操作:
- 在调度操作时,将用户输入传达给 API
- 成功后,将 API 响应数据发送到相关 Reducer 进行处理
- 成功后,显示确认弹出窗口,通知用户操作成功
- 发生错误时,将 API 错误数据定向到相关的 Reducer
- 发生错误时,向用户显示错误通知,通知相关错误
第一个想到的解决方案可能是在相关的 Reducer 中单独存储确认和错误,并让NotificationComponent监听这些内容。下面是此类 Reducer 的概述。
//formReducer.js
// following shows how notification data can be kept inside the
// form reducer itself
import { FORM_ACTION_SUCCESS, FORM_ACTION_ERROR} from 'formActions.js';
const initState = {
error: null,
message: null
};
export function formReducer(state = initState, action){
switch(action.type){
case FORM_ACTION_SUCCESS:
state.message = action.message;
return state;
case FORM_ACTION_ERROR:
state.error = action.error;
return state;
}
}
假设我们需要另外五个这样的 Reducer 来对应我们应用的各个元素。我们最终会在每个 Reducer 中定义错误和通知存储。此外,NotificationComponent需要随着每个新的 Reducer 而更新。理想情况下,我们需要关注点分离。因此,我们需要一种模式,在分派操作时,它会被多个 Reducer 捕获:(1) 操作的预期 Reducer,以及 (2) 通知 Reducer。在使用 React 和 Redux 进行集中式错误处理中,我们讨论了如何实现这种集中式错误处理解决方案。由于实现通知只是上述内容的一种通用解决方案,因此我将让您参考上述指南,其中提供了有关实现的更深入的详细信息。
在另一个动作中共享动作
另一个常见情况是需要同时分派多个 Redux 操作。例如,假设在上面讨论的表单中,我们需要在用户点击时禁用提交按钮(以防止用户强制触发按钮),并在收到 API 响应时重新启用该按钮。有两种方法可以处理这种情况。
首先,我们可以使用与之前相同的逻辑,即我们为按钮设置一个 UI Reducer,用于监听一组特定的操作。我们将 formActionRequest 包含在监听操作集合中,并在按钮被调度时禁用按钮。然后,我们监听formActionSuccess和formActionError操作,并在其中任何一个被调度时启用按钮。虽然这是一个合理的解决方案,但它大大降低了代码的可读性。与通知系统不同,按钮不是核心要求,也不需要以这种方式处理。
或者,我们可以使用单独的 UI 操作,声明为disableSubmitButton和enableSubmitButton。在我们的formAction中,我们首先在 API 请求之前调度disableSubmitButton,并在收到 API 的响应后调度enableSubmitButton。
// uiActions.js
export const ENABLE_SUBMIT_BUTTON = "ENABLE_SUBMIT_BUTTON";
export const DISABLE_SUBMIT_BUTTON = "DISABLE_SUBMIT_BUTTON";
export function enableSubmitButton(){
return {
type: ENABLE_SUBMIT_BUTTON
}
}
export function disableSubmitButton(){
return {
type: DISABLE_SUBMIT_BUTTON
}
}
// uiReducer.js
// this reducer is responsible for UI changes in the form
// note that the actual efficiency comes with the ability to separate app logic
// from the UI logic by splitting these into different reducers
import { ENABLE_SUBMIT_BUTTON, DISABLE_SUBMIT_BUTTON} from 'uiAcions.js';
const initState = {
formSubmit: {
enabled: true
}
};
export function uiReducer(state = initState, action){
switch(action.type){
case ENABLE_SUBMIT_BUTTON:
state.formSubmit.enabled = true;
return state;
case DISABLE_SUBMIT_BUTTON:
state.formSubmit.enabled = false;
return state;
}
}
现在我们修改formAction来利用上述操作。
import { enableSubmitButton, disableSubmitButton } from 'uiActions.js';
export const formAction = (formData) => {
return async function(dispatch) {
dispatch(formActionRequest());
dispatch(disableSubmitButton())
try{
let response = (await axios.post("http://yourapi.com/form", formData)).data;
dispatch(formActionSuccess(response));
}catch(error){
dispatch(formActionError(response.error));
}finally{
dispatch(enableSubmitButton())
}
}
}
如上所示,这大大提高了代码的可读性,并且无需在 UI Reducer 端维护一个“操作集”来监听。虽然上面的代码片段仅演示了它的用法,但它的实际威力在于需要在单个操作中分派多个此类操作的复杂场景。
结论
在本指南中,我们探索了 Redux 操作和 Reducer 的一对一映射概念。虽然一对一映射在大多数情况下效果很好,但我们探讨了两种实际场景,在这些场景中,共享操作和 Reducer 比在多个位置复制逻辑更为可靠。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~