Redux 快速指南
Redux - 概述
Redux 是一个用于 JavaScript 应用的可预测状态容器。随着应用程序的增长,保持其组织性和维护数据流变得越来越困难。Redux 通过使用名为 Store 的单个全局对象管理应用程序的状态来解决此问题。Redux 的基本原则有助于在整个应用程序中保持一致性,从而使调试和测试更容易。
更重要的是,它提供了实时代码编辑以及时间旅行调试器。它可以灵活地与任何视图层一起使用,例如 React、Angular、Vue 等。
Redux 的原则
Redux 的可预测性由以下三个最重要的原则决定:
单一数据源
整个应用程序的状态存储在单个 store 中的一个对象树内。由于整个应用程序状态存储在一个单一树中,因此它使调试更容易,开发速度更快。
状态是只读的
更改状态的唯一方法是发出一个 action(动作),一个描述发生了什么的对象。这意味着没有人可以直接更改应用程序的状态。
更改使用纯函数进行
为了指定如何通过 action 转换状态树,您需要编写纯 reducers(化简器)。Reducer 是状态修改发生的核心位置。Reducer 是一个函数,它接受 state(状态)和 action(动作)作为参数,并返回一个新更新的状态。
Redux - 安装
在安装 Redux 之前,**我们必须安装 Nodejs 和 NPM**。以下说明将帮助您安装它。如果您已经在设备上安装了 Nodejs 和 NPM,则可以跳过这些步骤。
访问 https://node.org.cn/ 并安装程序包文件。
运行安装程序,按照说明操作并接受许可协议。
重启您的设备以运行它。
您可以通过打开命令提示符并键入 node -v 来检查安装是否成功。这将向您显示系统中 Node 的最新版本。
要检查 npm 是否已成功安装,您可以键入 npm –v,它将返回最新的 npm 版本。
要安装 redux,您可以按照以下步骤操作:
在您的命令提示符中运行以下命令以安装 Redux。
npm install --save redux
要在 react 应用程序中使用 Redux,您需要安装以下附加依赖项:
npm install --save react-redux
要为 Redux 安装开发者工具,您需要安装以下依赖项:
在您的命令提示符中运行以下命令以安装 Redux 开发者工具。
npm install --save-dev redux-devtools
如果您不想安装 Redux 开发者工具并将其集成到您的项目中,您可以为 Chrome 和 Firefox 安装**Redux DevTools 扩展程序**。
Redux - 核心概念
让我们假设我们的应用程序状态由一个名为**initialState**的普通对象描述,如下所示:
const initialState = { isLoading: false, items: [], hasError: false };
应用程序中的任何代码片段都不能更改此状态。要更改状态,您需要分派一个 action。
什么是 action(动作)?
action 是一个普通对象,它描述了导致更改的意图,并具有 type 属性。它必须具有一个 type 属性,该属性指示正在执行的动作类型。action 的命令如下:
return { type: 'ITEMS_REQUEST', //action type isLoading: true //payload information }
action 和 state 通过名为 Reducer 的函数联系在一起。分派 action 的目的是引起更改。此更改由 reducer 执行。Reducer 是更改 Redux 中状态的唯一方法,使其更易于预测、集中和调试。处理“ITEMS_REQUEST”action 的 reducer 函数如下:
const reducer = (state = initialState, action) => { //es6 arrow function switch (action.type) { case 'ITEMS_REQUEST': return Object.assign({}, state, { isLoading: action.isLoading }) default: return state; } }
Redux 有一个存储应用程序状态的单个 store。如果您想根据数据处理逻辑拆分代码,则应开始拆分 reducers 而不是 Redux 中的 store。
我们将在本教程的后面讨论如何拆分 reducers 并将其与 store 结合使用。
Redux 组件如下:
Redux - 数据流
Redux 遵循单向数据流。这意味着您的应用程序数据将遵循单向绑定数据流。随着应用程序的增长和变得复杂,如果您无法控制应用程序的状态,则很难重现问题并添加新功能。
Redux 通过限制如何以及何时更新状态来降低代码的复杂性。这样,管理更新后的状态就很容易了。我们已经了解了 Redux 的三个原则中的限制。下图将帮助您更好地理解 Redux 数据流:
当用户与应用程序交互时,将分派一个 action。
使用当前状态和已分派的 action 调用根 reducer 函数。根 reducer 可以将任务分配给较小的 reducer 函数,最终返回一个新状态。
store 通过执行其回调函数来通知视图。
视图可以检索更新后的状态并重新渲染。
Redux - Store(数据仓库)
store 在 Redux 中是一个不可变的对象树。store 是一个状态容器,它保存应用程序的状态。Redux 在您的应用程序中只能拥有一个 store。每当在 Redux 中创建 store 时,您都需要指定 reducer。
让我们看看如何使用 Redux 中的**createStore**方法创建一个 store。需要从支持 store 创建过程的 Redux 库导入 createStore 包,如下所示:
import { createStore } from 'redux'; import reducer from './reducers/reducer' const store = createStore(reducer);
createStore 函数可以有三个参数。以下是语法:
createStore(reducer, [preloadedState], [enhancer])
reducer 是一个返回应用程序下一个状态的函数。preloadedState 是一个可选参数,是应用程序的初始状态。enhancer 也是一个可选参数。它将帮助您使用第三方功能增强 store。
store 有三个重要的 methods(方法),如下所示:
getState
它帮助您检索 Redux store 的当前状态。
getState 的语法如下:
store.getState()
dispatch
它允许您分派一个 action 来更改应用程序中的状态。
dispatch 的语法如下:
store.dispatch({type:'ITEMS_REQUEST'})
subscribe
它帮助您注册一个回调函数,Redux store 将在分派 action 后调用该函数。一旦 Redux 状态更新,视图将自动重新渲染。
dispatch 的语法如下:
store.subscribe(()=>{ console.log(store.getState());})
请注意,subscribe 函数返回一个用于取消订阅侦听器的函数。要取消订阅侦听器,我们可以使用以下代码:
const unsubscribe = store.subscribe(()=>{console.log(store.getState());}); unsubscribe();
Redux - Actions(动作)
根据 Redux 官方文档,action 是 store 的唯一信息来源。它承载着从您的应用程序到 store 的信息负载。
如前所述,action 是必须具有 type 属性以指示所执行 action 类型的普通 JavaScript 对象。它告诉我们发生了什么。类型应在您的应用程序中定义为字符串常量,如下所示:
const ITEMS_REQUEST = 'ITEMS_REQUEST';
除了此 type 属性外,action 对象的结构完全取决于开发者。建议使 action 对象尽可能轻量,并且只传递必要的信息。
要导致 store 中的任何更改,您需要首先使用 store.dispatch() 函数分派一个 action。action 对象如下所示:
{ type: GET_ORDER_STATUS , payload: {orderId,userId } } { type: GET_WISHLIST_ITEMS, payload: userId }
Actions Creators(动作创建器)
Actions Creators 是封装 action 对象创建过程的函数。这些函数只是返回一个普通的 Js 对象,它是一个 action。它促进了编写简洁的代码并有助于实现可重用性。
让我们了解一下 action creator,它允许您分派一个 action,“ITEMS_REQUEST”,该 action 向服务器请求产品项目列表数据。同时,在“ITEMS_REQUEST”action 类型中,reducer 将 isLoading 状态设置为 true,以指示项目正在加载,并且仍未从服务器接收数据。
最初,在**initialState**对象中,isLoading 状态为 false,假设没有任何内容正在加载。当浏览器接收到数据时,在相应的 reducer 中,“ITEMS_REQUEST_SUCCESS”action 类型将 isLoading 状态返回为 false。此状态可用作 react 组件中的 prop,在请求数据的过程中在页面上显示加载程序/消息。action creator 如下所示:
const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ; const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ; export function itemsRequest(bool,startIndex,endIndex) { let payload = { isLoading: bool, startIndex, endIndex } return { type: ITEMS_REQUEST, payload } } export function itemsRequestSuccess(bool) { return { type: ITEMS_REQUEST_SUCCESS, isLoading: bool, } }
要调用 dispatch 函数,您需要将 action 作为参数传递给 dispatch 函数。
dispatch(itemsRequest(true,1, 20)); dispatch(itemsRequestSuccess(false));
您可以直接使用 store.dispatch() 分派 action。但是,您更有可能使用名为**connect()**的 react-Redux 辅助方法访问它。您还可以使用**bindActionCreators()**方法将许多 action creators 与 dispatch 函数绑定。
Redux - 纯函数
函数是一个过程,它接受称为参数的输入,并产生一些称为返回值的输出。如果函数遵守以下规则,则称为纯函数:
对于相同的参数,函数返回相同的结果。
它的计算没有副作用,即它不会更改输入数据。
不更改局部和全局变量。
它不依赖于外部状态,例如全局变量。
让我们以一个函数为例,该函数返回传递给函数的输入值的二倍。通常,它写成 f(x) => x*2。如果使用参数值 2 调用函数,则输出将为 4,f(2) => 4。
让我们用 JavaScript 编写函数的定义,如下所示:
const double = x => x*2; // es6 arrow function console.log(double(2)); // 4
此处,double 是一个纯函数。
根据 Redux 中的三个原则,必须通过纯函数(即 Redux 中的 reducer)进行更改。现在,问题出现了,为什么 reducer 必须是纯函数。
假设您想分派一个类型为“ADD_TO_CART_SUCCESS”的 action,通过单击“添加到购物车”按钮将商品添加到您的购物车应用程序。
让我们假设 reducer 正在将商品添加到您的购物车,如下所示:
const initialState = { isAddedToCart: false; } const addToCartReducer = (state = initialState, action) => { //es6 arrow function switch (action.type) { case 'ADD_TO_CART_SUCCESS' : state.isAddedToCart = !state.isAddedToCart; //original object altered return state; default: return state; } } export default addToCartReducer ;
让我们假设**isAddedToCart**是 state 对象上的一个属性,它允许您通过返回布尔值“true 或 false”来决定何时禁用商品的“添加到购物车”按钮。这可以防止用户多次添加同一产品。现在,我们不是返回一个新对象,而是像上面那样更改 state 上的 isAddedToCart 属性。现在,如果我们尝试将商品添加到购物车,则不会发生任何事情。“添加到购物车”按钮不会被禁用。
此行为的原因如下:
Redux 通过两个对象的内存位置来比较旧对象和新对象。如果发生任何更改,它期望 reducer 返回一个新对象。如果没有任何更改,它也期望返回旧对象。在这种情况下,它是相同的。由于这个原因,Redux 假设没有任何事情发生。
因此,reducer 在 Redux 中必须是一个纯函数。以下是在不进行更改的情况下编写它的方法:
const initialState = { isAddedToCart: false; } const addToCartReducer = (state = initialState, action) => { //es6 arrow function switch (action.type) { case 'ADD_TO_CART_SUCCESS' : return { ...state, isAddedToCart: !state.isAddedToCart } default: return state; } } export default addToCartReducer;
Redux - Reducers(化简器)
Reducers 是 Redux 中的纯函数。纯函数是可预测的。Reducers 是更改 Redux 中状态的唯一方法。它是您可以编写逻辑和计算的唯一地方。Reducer 函数将接受应用程序的先前状态和正在分派的 action,计算下一个状态并返回新对象。
以下几件事永远不应该在 reducer 中执行:
- 更改函数参数
- API 调用和路由逻辑
- 调用非纯函数,例如 Math.random()
以下是 reducer 的语法:
(state,action) => newState
让我们继续在 action creators 模块中讨论的显示网页上产品项目列表的示例。让我们在下面看看如何编写其 reducer。
const initialState = { isLoading: false, items: [] }; const reducer = (state = initialState, action) => { switch (action.type) { case 'ITEMS_REQUEST': return Object.assign({}, state, { isLoading: action.payload.isLoading }) case ‘ITEMS_REQUEST_SUCCESS': return Object.assign({}, state, { items: state.items.concat(action.items), isLoading: action.isLoading }) default: return state; } } export default reducer;
首先,如果您没有将状态设置为“initialState”,Redux 会使用未定义的状态调用 reducer。在这个代码示例中,JavaScript 的 concat() 函数用于“ITEMS_REQUEST_SUCCESS”,它不会更改现有数组;而是返回一个新数组。
这样,您可以避免状态突变。切勿直接写入状态。“ITEMS_REQUEST”中,我们必须根据接收到的 action 设置状态值。
前面已经讨论过,我们可以在 reducer 中编写我们的逻辑,并可以基于逻辑数据对其进行拆分。让我们看看在处理大型应用程序时,如何拆分 reducer 并将它们组合在一起作为根 reducer。
假设,我们想设计一个网页,用户可以在其中访问产品订单状态并查看愿望清单信息。我们可以将逻辑分离到不同的 reducer 文件中,并使它们独立工作。让我们假设 GET_ORDER_STATUS action 被分派以获取与某个订单 ID 和用户 ID 对应的订单状态。
/reducer/orderStatusReducer.js import { GET_ORDER_STATUS } from ‘../constants/appConstant’; export default function (state = {} , action) { switch(action.type) { case GET_ORDER_STATUS: return { ...state, orderStatusData: action.payload.orderStatus }; default: return state; } }
类似地,假设 GET_WISHLIST_ITEMS action 被分派以获取用户相对于用户的愿望清单信息。
/reducer/getWishlistDataReducer.js import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’; export default function (state = {}, action) { switch(action.type) { case GET_WISHLIST_ITEMS: return { ...state, wishlistData: action.payload.wishlistData }; default: return state; } }
现在,我们可以使用 Redux combineReducers 实用程序组合这两个 reducer。combineReducers 生成一个函数,该函数返回一个对象,其值是不同的 reducer 函数。您可以将所有 reducer 导入 index reducer 文件中,并将它们组合在一起作为具有各自名称的对象。
/reducer/index.js import { combineReducers } from ‘redux’; import OrderStatusReducer from ‘./orderStatusReducer’; import GetWishlistDataReducer from ‘./getWishlistDataReducer’; const rootReducer = combineReducers ({ orderStatusReducer: OrderStatusReducer, getWishlistDataReducer: GetWishlistDataReducer }); export default rootReducer;
现在,您可以按如下方式将此 rootReducer 传递给 createStore 方法:
const store = createStore(rootReducer);
Redux - 中间件
Redux 本身是同步的,那么像**异步**操作(如**网络请求**)如何与 Redux 一起工作?这里中间件派上用场了。如前所述,reducer 是编写所有执行逻辑的地方。reducer 与谁执行它、花费多少时间或记录 action 分派前后应用程序的状态无关。
在这种情况下,Redux 中间件函数提供了一种在 action 达到 reducer 之前与已分派 action 交互的媒介。可以通过编写高阶函数(返回另一个函数的函数)来创建自定义中间件函数,该函数围绕某些逻辑进行包装。可以将多个中间件组合在一起以添加新功能,并且每个中间件都不需要了解之前和之后的内容。您可以将中间件想象成 action 分派和 reducer 之间的某个地方。
通常,中间件用于处理应用程序中的异步 action。Redux 提供了一个名为 applyMiddleware 的 API,它允许我们使用自定义中间件以及 Redux 中间件,如 redux-thunk 和 redux-promise。它将中间件应用于存储。使用 applyMiddleware API 的语法如下:
applyMiddleware(...middleware)
这可以应用于存储,如下所示:
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers/index'; const store = createStore(rootReducer, applyMiddleware(thunk));
中间件允许您编写一个 action 分派器,该分派器返回一个函数而不是一个 action 对象。下面的示例显示了相同的内容:
function getUser() { return function() { return axios.get('/get_user_details'); }; }
可以在中间件中编写条件分派。每个中间件接收 store 的 dispatch,以便它们可以分派新的 action,并将 getState 函数作为参数,以便它们可以访问当前状态并返回一个函数。内部函数的任何返回值都将作为 dispatch 函数本身的值可用。
中间件的语法如下:
({ getState, dispatch }) => next => action
getState 函数可用于根据当前状态确定是否要获取新数据或返回缓存结果。
让我们看一个自定义中间件日志记录函数的示例。它只记录 action 和新状态。
import { createStore, applyMiddleware } from 'redux' import userLogin from './reducers' function logger({ getState }) { return next => action => { console.log(‘action’, action); const returnVal = next(action); console.log('state when action is dispatched', getState()); return returnVal; } }
现在通过编写以下代码行将日志记录中间件应用于存储:
const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));
分派一个 action 来检查使用以下代码分派的 action 和新状态:
store.dispatch({ type: 'ITEMS_REQUEST', isLoading: true })
另一个中间件示例,您可以在其中处理何时显示或隐藏加载程序,如下所示。当您请求任何资源时,此中间件会显示加载程序,并在资源请求完成后将其隐藏。
import isPromise from 'is-promise'; function loaderHandler({ dispatch }) { return next => action => { if (isPromise(action)) { dispatch({ type: 'SHOW_LOADER' }); action .then(() => dispatch({ type: 'HIDE_LOADER' })) .catch(() => dispatch({ type: 'HIDE_LOADER' })); } return next(action); }; } const store = createStore( userLogin , initialState = [ ] , applyMiddleware(loaderHandler) );
Redux - 开发者工具
Redux-Devtools 为 Redux 应用程序提供调试平台。它允许我们执行时间旅行调试和实时编辑。官方文档中的一些功能如下:
它允许您检查每个状态和 action 负载。
它允许您通过“取消” action 返回过去。
如果更改 reducer 代码,每个“分阶段” action 都将重新评估。
如果 reducer 抛出异常,我们可以识别错误以及发生此错误的 action。
使用 persistState() store enhancer,您可以跨页面重新加载持久化调试会话。
Redux dev-tools 有两种变体,如下所示:
**Redux DevTools** - 它可以作为包安装并集成到您的应用程序中,如下所示:
https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration
**Redux DevTools Extension** - 一个浏览器扩展,它实现相同的 Redux 开发工具,如下所示:
https://github.com/zalmoxisus/redux-devtools-extension
现在让我们检查如何跳过 action 并借助 Redux dev 工具返回过去。以下屏幕截图解释了我们之前为获取项目列表而分派的 action。在这里,我们可以看到在检查器选项卡中分派的 action。在右侧,您可以看到演示选项卡,它向您显示状态树的差异。
当您开始使用此工具时,您会熟悉它。您可以只从这个 Redux 插件工具分派 action,而无需编写实际代码。最后一行中的分派器选项将在此方面为您提供帮助。让我们检查项目成功获取的最后一个 action。
我们从服务器收到一个对象数组作为响应。所有数据都可用于在我们的页面上显示列表。您还可以通过单击右上角的状态选项卡来同时跟踪 store 的状态。
在前面的章节中,我们学习了时间旅行调试。现在让我们检查如何跳过一个 action 并返回过去以分析应用程序的状态。当您单击任何 action 类型时,将出现两个选项:“跳转”和“跳过”。
通过单击特定 action 类型的跳过按钮,您可以跳过特定 action。它就像 action 从未发生过一样。当您单击特定 action 类型的跳转按钮时,它将带您到该 action 发生时的状态,并跳过序列中所有剩余的 action。通过这种方式,您将能够保留特定 action 发生时的状态。此功能有助于调试和查找应用程序中的错误。
我们跳过了最后一个 action,所有来自后台的列表数据都消失了。它返回到项目数据尚未到达的时间,我们的应用程序没有数据可在页面上呈现。它实际上使编码更容易,调试也更容易。
Redux - 测试
测试 Redux 代码很容易,因为我们主要编写函数,而且大多数函数都是纯函数。因此,我们甚至无需模拟它们就可以对其进行测试。在这里,我们使用 JEST 作为测试引擎。它在节点环境中工作,并且不访问 DOM。
我们可以使用以下代码安装 JEST:
npm install --save-dev jest
使用 babel,您需要安装 **babel-jest**,如下所示:
npm install --save-dev babel-jest
并在 .babelrc 文件中将其配置为使用 babel-preset-env 功能,如下所示:
{ "presets": ["@babel/preset-env"] } And add the following script in your package.json: { //Some other code "scripts": { //code "test": "jest", "test:watch": "npm test -- --watch" }, //code }
最后,**运行 npm test 或 npm run test**。让我们检查如何为 action 创建者和 reducer 编写测试用例。
Action 创建者的测试用例
让我们假设您有如下所示的 action 创建者:
export function itemsRequestSuccess(bool) { return { type: ITEMS_REQUEST_SUCCESS, isLoading: bool, } }
此 action 创建者可以按如下所示进行测试:
import * as action from '../actions/actions'; import * as types from '../../constants/ActionTypes'; describe('actions', () => { it('should create an action to check if item is loading', () => { const isLoading = true, const expectedAction = { type: types.ITEMS_REQUEST_SUCCESS, isLoading } expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction) }) })
Reducers 的测试用例
我们已经了解到,当应用 action 时,reducer 应该返回一个新的状态。因此,reducer 在此行为上进行了测试。
考虑如下所示的 reducer:
const initialState = { isLoading: false }; const reducer = (state = initialState, action) => { switch (action.type) { case 'ITEMS_REQUEST': return Object.assign({}, state, { isLoading: action.payload.isLoading }) default: return state; } } export default reducer;
要测试上面的 reducer,我们需要将状态和 action 传递给 reducer,并返回一个新的状态,如下所示:
import reducer from '../../reducer/reducer' import * as types from '../../constants/ActionTypes' describe('reducer initial state', () => { it('should return the initial state', () => { expect(reducer(undefined, {})).toEqual([ { isLoading: false, } ]) }) it('should handle ITEMS_REQUEST', () => { expect( reducer( { isLoading: false, }, { type: types.ITEMS_REQUEST, payload: { isLoading: true } } ) ).toEqual({ isLoading: true }) }) })
如果您不熟悉编写测试用例,您可以查看JEST的基础知识。
Redux - 集成 React
在前面的章节中,我们学习了什么是 Redux 以及它是如何工作的。现在让我们检查视图部分与 Redux 的集成。您可以向 Redux 添加任何视图层。我们还将讨论 react 库和 Redux。
比方说,如果各种 react 组件需要以不同的方式显示相同的数据,而无需将其作为 prop 从顶级组件一直传递到各个组件。将其存储在 react 组件之外是理想的选择。因为它有助于更快地检索数据,因为您无需将数据一直传递到不同的组件。
让我们讨论一下 Redux 如何实现这一点。Redux 提供 react-redux 包来绑定 react 组件,并提供如下所示的两个实用程序:
- Provider
- Connect
Provider 使 store 可用于应用程序的其余部分。Connect 函数帮助 react 组件连接到 store,响应 store 状态中发生的每个更改。
让我们看一下**根 index.js** 文件,它创建 store 并使用一个 provider 来使 store 可用于 react-redux 应用程序中的其余应用程序。
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore, applyMiddleware } from 'redux'; import reducer from './reducers/reducer' import thunk from 'redux-thunk'; import App from './components/app' import './index.css'; const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(thunk) ) render( <Provider store = {store}> <App /> </Provider>, document.getElementById('root') )
每当 react-redux 应用程序中发生更改时,都会调用 mapStateToProps()。在这个函数中,我们精确地指定我们需要提供给 react 组件的状态。
借助下面解释的 connect() 函数,我们将这些应用程序的状态连接到 react 组件。Connect() 是一个高阶函数,它将组件作为参数。它执行某些操作并返回一个具有正确数据的新组件,我们最终将其导出。
借助 mapStateToProps(),我们将这些 store 状态作为 prop 提供给我们的 react 组件。此代码可以包装在一个容器组件中。其目的是分离关注点,例如数据获取、呈现关注点和可重用性。
import { connect } from 'react-redux' import Listing from '../components/listing/Listing' //react component import makeApiCall from '../services/services' //component to make api call const mapStateToProps = (state) => { return { items: state.items, isLoading: state.isLoading }; }; const mapDispatchToProps = (dispatch) => { return { fetchData: () => dispatch(makeApiCall()) }; }; export default connect(mapStateToProps, mapDispatchToProps)(Listing);
在 services.js 文件中进行 api 调用的组件定义如下:
import axios from 'axios' import { itemsLoading, itemsFetchDataSuccess } from '../actions/actions' export default function makeApiCall() { return (dispatch) => { dispatch(itemsLoading(true)); axios.get('http://api.tvmaze.com/shows') .then((response) => { if (response.status !== 200) { throw Error(response.statusText); } dispatch(itemsLoading(false)); return response; }) .then((response) => dispatch(itemsFetchDataSuccess(response.data))) }; }
mapDispatchToProps() 函数接收 dispatch 函数作为参数,并返回您传递给 react 组件的普通对象作为回调 prop。
在这里,你可以访问`fetchData`作为你React列表组件中的一个prop,它会分发一个action来进行API调用。`mapDispatchToProps()`用于向store分发action。在react-redux中,组件不能直接访问store。唯一的方法是使用`connect()`。
让我们通过下面的图表来了解react-redux是如何工作的:
STORE − 将所有应用程序状态存储为一个JavaScript对象
PROVIDER − 使store可用
CONTAINER − 获取应用状态并将其作为prop提供给组件
COMPONENT − 用户通过视图组件进行交互
ACTIONS − 导致store发生变化,它可能改变也可能不改变应用程序的状态
REDUCER − 唯一改变应用程序状态的方法,接收状态和action,并返回更新后的状态。
然而,Redux是一个独立的库,可以与任何UI层一起使用。React-redux是Redux官方的React UI绑定。此外,它鼓励良好的React Redux应用程序结构。React-redux内部实现了性能优化,以便只有在需要时才重新渲染组件。
总而言之,Redux的设计目的并非编写最短、最快的代码。它旨在提供一个可预测的状态管理容器。它帮助我们理解某个状态何时发生变化,或者数据来自哪里。
Redux - React 示例
这是一个React和Redux应用程序的小例子。你也可以尝试开发小型应用程序。下面给出了增加或减少计数器的示例代码:
这是根文件,负责创建store和渲染我们的React应用程序组件。
/src/index.js import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux'; import reducer from '../src/reducer/index' import App from '../src/App' import './index.css'; const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ) render( <Provider store = {store}> <App /> </Provider>, document.getElementById('root') )
这是我们的React根组件。它负责渲染计数器容器组件作为子组件。
/src/app.js import React, { Component } from 'react'; import './App.css'; import Counter from '../src/container/appContainer'; class App extends Component { render() { return ( <div className = "App"> <header className = "App-header"> <Counter/> </header> </div> ); } } export default App;
以下是容器组件,负责向React组件提供Redux的状态:
/container/counterContainer.js import { connect } from 'react-redux' import Counter from '../component/counter' import { increment, decrement, reset } from '../actions'; const mapStateToProps = (state) => { return { counter: state }; }; const mapDispatchToProps = (dispatch) => { return { increment: () => dispatch(increment()), decrement: () => dispatch(decrement()), reset: () => dispatch(reset()) }; }; export default connect(mapStateToProps, mapDispatchToProps)(Counter);
下面是负责视图部分的React组件:
/component/counter.js import React, { Component } from 'react'; class Counter extends Component { render() { const {counter,increment,decrement,reset} = this.props; return ( <div className = "App"> <div>{counter}</div> <div> <button onClick = {increment}>INCREMENT BY 1</button> </div> <div> <button onClick = {decrement}>DECREMENT BY 1</button> </div> <button onClick = {reset}>RESET</button> </div> ); } } export default Counter;
以下是负责创建action的action creators:
/actions/index.js export function increment() { return { type: 'INCREMENT' } } export function decrement() { return { type: 'DECREMENT' } } export function reset() { return { type: 'RESET' } }
下面,我们展示了reducer文件的代码片段,它负责更新Redux中的状态。
reducer/index.js const reducer = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 case 'RESET' : return 0 default: return state } } export default reducer;
最初,应用程序如下所示:
当我点击两次“递增”按钮时,输出屏幕将如下所示:
当我们点击一次“递减”按钮时,它将显示以下屏幕:
而“重置”将使应用程序返回初始状态,即计数器值为0。如下所示:
让我们了解一下第一次递增操作发生时Redux开发者工具会发生什么:
应用程序的状态将移动到仅分发递增操作且其余操作被跳过的时间点。
我们鼓励你独立开发一个小的Todo应用程序作为练习,以便更好地理解Redux工具。