Redux - 快速指南


Redux - 概述

Redux 是 JavaScript 应用程序的可预测状态容器。随着应用程序的增长,保持其组织和维护数据流变得困难。Redux 通过使用一个名为 Store 的全局对象来管理应用程序的状态来解决这个问题。Redux 基本原则有助于保持整个应用程序的一致性,从而使调试和测试变得更加容易。

更重要的是,它为您提供实时代码编辑和时间旅行调试器。它可以灵活地与任何视图层(例如 React、Angular、Vue 等)配合使用。

Redux 的原理

Redux 的可预测性由以下三个最重要的原则决定:

单一事实来源

整个应用程序的状态存储在单个存储中的对象树中。由于整个应用程序状态存储在单个树中,因此调试变得容易,开发速度更快。

状态为只读

改变状态的唯一方法是发出一个动作,一个描述发生了什么的对象。这意味着没有人可以直接更改应用程序的状态。

使用纯函数进行更改

要指定状态树如何通过操作进行转换,您可以编写纯减速器。减速器是状态修改发生的中心位置。Reducer 是一个以状态和操作作为参数并返回新更新的状态的函数。

Redux - 安装

在安装 Redux 之前,我们必须安装 Nodejs 和 NPM。以下是帮助您安装它的说明。如果您的设备中已安装 Nodejs 和 NPM,则可以跳过这些步骤。

  • 访问https://nodejs.org/并安装包文件。

  • 运行安装程序,按照说明进行操作并接受许可协议。

  • 重新启动您的设备以运行它。

  • 您可以通过打开命令提示符并键入 node -v 来检查安装是否成功。这将向您显示系统中 Node 的最新版本。

  • 要检查 npm 是否安装成功,您可以输入 npm –v ,它会返回最新的 npm 版本。

要安装 redux,您可以按照以下步骤操作 -

在命令提示符中运行以下命令来安装 Redux。

npm install --save redux

要将 Redux 与 React 应用程序一起使用,您需要安装额外的依赖项,如下所示 -

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

应用程序中的每一段代码都无法更改此状态。要更改状态,您需要调度一个操作。

什么是行动?

操作是一个普通对象,它描述了导致类型属性发生更改的意图。它必须有一个 type 属性来告诉正在执行什么类型的操作。操作命令如下 -

return {
   type: 'ITEMS_REQUEST', //action type
   isLoading: true //payload information
}

动作和状态由一个名为“Reducer”的函数结合在一起。调度一个动作的目的是引起改变。这个改变是由reducer执行的。Reducer 是 Redux 中改变状态的唯一方法,使其更加可预测、集中和可调试。处理“ITEMS_REQUEST”操作的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 有一个存储应用程序状态的存储。如果你想根据数据处理逻辑来拆分你的代码,你应该开始拆分你的减速器而不是 Redux 中的存储。

我们将在本教程后面讨论如何拆分减速器并将其与存储结合起来。

Redux 组件如下 -

数据处理逻辑

Redux - 数据流

Redux 遵循单向数据流。这意味着您的应用程序数据将遵循单向绑定数据流。随着应用程序的增长和变得复杂,如果您无法控制应用程序的状态,则很难重现问题并添加新功能。

Redux 通过强制限制状态更新的发生方式和时间,降低了代码的复杂性。这样,管理更新的状态就很容易了。我们已经知道限制是 Redux 的三原则。下图将帮助您更好地理解 Redux 数据流 -

数据流
  • 当用户与应用程序交互时,将调度一个操作。

  • 使用当前状态和分派的操作调用根减速器函数。根减速器可以将任务划分为更小的减速器函数,最终返回一个新状态。

  • 商店通过执行回调函数来通知视图。

  • 视图可以检索更新的状态并再次重新渲染。

Redux - 商店

store 是 Redux 中的一棵不可变的对象树。存储是保存应用程序状态的状态容器。Redux 在您的应用程序中只能有一个存储。每当在 Redux 中创建 store 时,您都需要指定化简器。

让我们看看如何使用 Redux 中的createStore方法创建一个 store 。需要从支持商店创建过程的 Redux 库导入 createStore 包,如下所示 -

import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);

createStore 函数可以具有三个参数。以下是语法 -

createStore(reducer, [preloadedState], [enhancer])

减速器是一个返回应用程序下一个状态的函数。preloadedState 是一个可选参数,是应用程序的初始状态。增强器也是一个可选参数。它将帮助您通过第三方功能增强商店。

商店具有以下三个重要方法 -

获取状态

它可以帮助您检索 Redux 存储的当前状态。

getState 的语法如下 -

store.getState()

派遣

它允许您调度一个操作来更改应用程序中的状态。

调度的语法如下 -

store.dispatch({type:'ITEMS_REQUEST'})

订阅

它可以帮助您注册 Redux 存储在分派操作时调用的回调。一旦 Redux 状态更新,视图就会自动重新渲染。

调度的语法如下 -

store.subscribe(()=>{ console.log(store.getState());})

请注意,订阅函数返回一个用于取消订阅侦听器的函数。要取消订阅侦听器,我们可以使用以下代码 -

const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();

Redux - 动作

根据 Redux 官方文档,操作是存储的唯一信息来源。它携带来自应用程序的信息负载进行存储。

如前所述,操作是纯 JavaScript 对象,必须具有 type 属性来指示所执行操作的类型。它告诉我们发生了什么。类型应在应用程序中定义为字符串常量,如下所示 -

const ITEMS_REQUEST = 'ITEMS_REQUEST';

除了这个类型属性之外,动作对象的结构完全取决于开发人员。建议使您的操作对象尽可能轻,并仅传递必要的信息。

要在商店中进行任何更改,您需要首先使用 store.dispatch() 函数分派一个操作。动作对象如下 -

{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }

行动创造者

动作创建者是封装动作对象创建过程的函数。这些函数只是返回一个普通的 Js 对象,它是一个动作。它促进编写干净的代码并有助于实现可重用性。

让我们了解一下操作创建器,它允许您调度一个操作“ITEMS_REQUEST” ,该操作从服务器请求产品项目列表数据。同时,在'ITEMS_REQUEST'操作类型的reducer中将isLoading状态设置为true,以指示项目正在加载,但仍未从服务器接收到数据。

最初,假设没有加载任何内容,initialState对象中的 isLoading 状态为 false 。当浏览器接收到数据时,相应减速器中的“ITEMS_REQUEST_SUCCESS”操作类型中的 isLoading 状态将返回 false。此状态可以用作反应组件中的道具,以在请求数据时在页面上显示加载程序/消息。动作创建者如下 -

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(itemsRequest(true,1, 20));
dispatch(itemsRequestSuccess(false));

您可以直接使用 store.dispatch() 来调度操作。但是,您更有可能使用名为connect()的 React-Redux 辅助方法来访问它。您还可以使用bindActionCreators()方法将多个动作创建者与调度函数绑定。

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)进行。现在,出现了一个问题:为什么减速器必须是纯函数。

假设您想要调度一个类型为“ADD_TO_CART_SUCCESS”的操作,通过单击“添加到购物车”按钮将商品添加到您的购物车应用程序。

让我们假设减速器正在向您的购物车添加一个项目,如下所示 -

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是状态对象的一个​​属性,它允许您通过返回布尔值 ' true 或 false'来决定何时禁用该项目的“添加到购物车”按钮。这可以防止用户多次添加相同的产品。现在,我们不再返回新对象,而是像上面那样在状态上改变 isAddedToCart 属性。现在,如果我们尝试将商品添加到购物车,则不会发生任何事情。添加到购物车按钮不会被禁用。

这种Behave的原因如下 -

Redux 通过新旧对象的内存位置来比较这两个对象。如果发生任何变化,它期望来自减速器的新对象。如果没有发生变化,它还期望取回旧对象。在这种情况下,也是一样的。由于这个原因,Redux 假设什么都没有发生。

所以,Redux中的reducer必须是纯函数。以下是一种无需突变的编写方法 -

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 - 减速器

减速器是 Redux 中的纯函数。纯函数是可预测的。减速器是 Redux 中改变状态的唯一方法。这是唯一可以编写逻辑和计算的地方。Reducer 函数将接受应用程序和正在分派的操作的先前状态,计算下一个状态并返回新对象。

以下几件事绝对不应该在减速器内部执行 -

  • 函数参数的变异
  • API调用和路由逻辑
  • 调用非纯函数,例如 Math.random()

以下是减速器的语法 -

(state,action) => newState

让我们继续在动作创建者模块中讨论的在网页上显示产品项目列表的示例。下面让我们看看如何编写它的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。在此代码示例中,“ITEMS_REQUEST_SUCCESS”中使用了 JavaScript 的 concat() 函数,该函数不会更改现有数组;相反,返回一个新数组。

这样,就可以避免状态的突变。切勿直接写入状态。在“ITEMS_REQUEST”中,我们必须根据收到的操作设置状态值。

我们已经讨论过,我们可以在reducer中编写我们的逻辑,并可以在逻辑数据的基础上拆分它。让我们看看在处理大型应用程序时如何拆分减速器并将它们组合在一起作为根减速器。

假设,我们要设计一个网页,用户可以在其中访问产品订单状态并查看愿望清单信息。我们可以将不同reducers文件中的逻辑分开,让它们独立工作。让我们假设调度 GET_ORDER_STATUS 操作来获取与某个订单 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 操作来获取相应用户的用户心愿单信息。

/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 mixReducers 实用程序组合两个减速器。mergeReducers 生成一个函数,该函数返回一个对象,该对象的值是不同的减速器函数。您可以导入索引减速器文件中的所有减速器,并将它们组合在一起作为一个具有各自名称的对象。

/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 与谁执行它、花费多少时间或在分派操作之前和之后记录应用程序的状态无关。

在这种情况下,Redux 中间件函数提供了一种媒介,可以在调度的操作到达减速器之前与它们进行交互。可以通过编写高阶函数(返回另一个函数的函数)来创建定制的中间件函数,该函数包装了一些逻辑。多个中间件可以组合在一起以添加新功能,并且每个中间件不需要了解之前和之后发生的事情。您可以想象中间件位于分派的操作和减速器之间。

通常,中间件用于处理应用程序中的异步操作。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));
中间件

中间件将允许您编写一个操作调度程序,它返回一个函数而不是操作对象。相同的示例如下所示 -

function getUser() {
   return function() {
      return axios.get('/get_user_details');
   };
}

条件调度可以写在中间件内部。每个中间件接收 store 的调度,以便它们可以调度新的操作,并且 getState 函数作为参数,以便它们可以访问当前状态并返回一个函数。内部函数的任何返回值都可以用作调度函数本身的值。

以下是中间件的语法 -

({ getState, dispatch }) => next => action

getState 函数可用于根据当前状态决定是获取新数据还是返回缓存结果。

让我们看一个自定义中间件记录器函数的示例。它只是记录操作和新状态。

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));

使用以下代码调度一个操作来检查已调度的操作和新状态 -

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 应用程序的调试平台。它允许我们进行时间旅行调试和实时编辑。官方文档中的一些功能如下 -

  • 它可以让您检查每个状态和操作有效负载。

  • 它可以让您通过“取消”操作回到过去。

  • 如果更改减速器代码,每个“分阶段”操作都将被重新评估。

  • 如果减速器抛出异常,我们可以识别错误以及发生错误的操作。

  • 使用 persistState() 存储增强器,您可以在页面重新加载时保留调试会话。

Redux 开发工具有两种变体,如下所示 -

Redux DevTools - 它可以作为包安装并集成到您的应用程序中,如下所示 -

https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration

Redux DevTools 扩展- 为 Redux 实现相同开发人员工具的浏览器扩展如下 -

https://github.com/zalmoxisus/redux-devtools-extension

现在让我们检查一下如何在 Redux 开发工具的帮助下跳过操作并及时返回。以下屏幕截图解释了我们之前为获取项目列表而执行的操作。在这里我们可以看到检查器选项卡中调度的操作。在右侧,您可以看到“演示”选项卡,其中显示了状态树中的差异。

检查器选项卡

当您开始使用这个工具时,您就会熟悉它。您可以仅通过此 Redux 插件工具来调度操作,而无需编写实际代码。最后一行的调度程序选项将帮助您完成此操作。让我们检查成功获取项目的最后一个操作。

获取成功

我们收到了一组对象作为来自服务器的响应。所有数据均可在我们的页面上显示列表。您还可以通过单击右上角的状态选项卡同时跟踪商店的状态。

状态选项卡

在前面的部分中,我们学习了时间旅行调试。现在让我们检查如何跳过一项操作并及时返回来分析应用程序的状态。当您单击任何操作类型时,将会出现两个选项:“跳转”和“跳过”。

通过单击特定操作类型上的跳过按钮,您可以跳过特定操作。它表现得好像该动作从未发生过。当您单击某些动作类型上的跳转按钮时,它将带您到该动作发生时的状态,并按顺序跳过所有剩余的动作。这样您就可以在发生特定操作时保留状态。此功能对于调试和查找应用程序中的错误非常有用。

跳跃按钮

我们跳过了最后一个操作,后台的所有列表数据都消失了。它回到项目数据尚未到达的时间,并且我们的应用程序没有数据可在页面上呈现。它实际上使编码变得更容易,调试也更容易。

Redux - 测试

测试 Redux 代码很容易,因为我们主要编写函数,而且大多数都是纯函数。所以我们可以测试它,甚至不用嘲笑它们。在这里,我们使用 JEST 作为测试引擎。它工作在node环境下,不访问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。让我们检查一下如何为操作创建者和缩减者编写测试用例。

动作创建者的测试用例

让我们假设您有动作创建者,如下所示 -

export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

这个动作创建者可以进行测试,如下所示 -

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

减速器测试用例

我们已经了解到,当应用操作时,Reducer 应该返回一个新的状态。因此,reducer 对此Behave进行了测试。

考虑一个减速器,如下所示 -

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;

为了测试上述减速器,我们需要将状态和操作传递给减速器,并返回一个新状态,如下所示 -

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 组件需要以不同的方式显示相同的数据,而不将其作为 props 传递给从顶级组件到下层组件的所有组件。将其存储在反应组件之外是理想的选择。因为它有助于更​​快地检索数据,因为您无需将数据一直传递到不同的组件。

让我们讨论一下 Redux 如何实现这一点。Redux 提供了 React-Redux 包来将 React 组件与两个实用程序绑定在一起,如下所示 -

  • 提供者
  • 连接

提供者使商店可供应用程序的其余部分使用。Connect 函数帮助反应组件连接到存储,响应存储状态中发生的每个变化。

让我们看一下根 index.js文件,该文件创建存储并使用一个提供程序,该提供程序使该存储能够连接到 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()。在此函数中,我们准确指定需要向反应组件提供哪种状态。

在下面解释的 connect() 函数的帮助下,我们将这些应用程序的状态连接到反应组件。Connect() 是一个以组件为参数的高阶函数。它执行某些操作并返回一个带有我们最终导出的正确数据的新组件。

在 mapStateToProps() 的帮助下,我们将这些存储状​​态作为 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() 函数接收调度函数作为参数,并将回调 props 作为传递给 React 组件的普通对象返回。

在这里,您可以将 fetchData 作为 React 列表组件中的 prop 进行访问,该组件会调度一个操作来进行 API 调用。mapDispatchToProps() 用于调度要存储的操作。在react-redux中,组件无法直接访问store。唯一的方法是使用 connect()。

让我们通过下图了解react-redux是如何工作的 -

React Redux 工作

STORE - 将所有应用程序状态存储为 JavaScript 对象

PROVIDER - 使商店可用

CONTAINER - 获取应用程序状态并将其作为组件提供

COMPONENT - 用户通过视图组件进行交互

操作- 导致商店发生变化,它可能会也可能不会改变您的应用程序的状态

REDUCER - 更改应用程序状态、接受状态和操作并返回更新状态的唯一方法。

然而,Redux 是一个独立的库,可以与任何 UI 层一起使用。React-redux是官方的Redux,UI与react绑定。此外,它鼓励良好的 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);

下面给出的是负责视图部分的反应组件 -

/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;

以下是负责创建动作的动作创建者 -

/actions/index.js
export function increment() {
   return {
      type: 'INCREMENT'
   }
}
export function decrement() {
   return {
      type: 'DECREMENT'
   }
}
export function reset() {
   return { type: 'RESET' }
}

下面,我们展示了负责更新 Redux 中状态的 reducer 文件的代码行。

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 开发工具会发生什么 -

Redux 开发工具

应用程序的状态将移动到仅调度增量操作并跳过其余操作的时间。

我们鼓励自己开发一个小型的 Todo App 作为作业,并更好地理解 Redux 工具。