Redux入门教程(快速上手)

典型的Web应用程序一般由共享数据的多个UI组件组成。一般,多个组件的任务是负责展现同一对象的不一样属性。这个对象表示可随时更改的状态。在多个组件之间保持状态的一致性会是一场噩梦,特别是若是有多个通道用于更新同一个对象。

举个?,一个带有购物车的网站。在顶部,咱们用一个UI组件显示购物车中的商品数量。咱们还能够用另外一个UI组件,显示购物车中商
品的总价。若是用户点击添加到购物车按钮,则这两个组件应当即更新当前的数据。若是用户从购物车中删除商品、更改数目、使用优惠券或者更改送货地点,则相关的UI组件都应该更新出正确的信息。
能够看到,随着功能范围的扩大,一个简单的购物车将会很难保持数据同步。javascript

在这篇文章中,我将介绍Redux框架,它能够帮助你以简单易用的方式构建复杂项目并进行维护。为了使学习更容易,咱们将使用一个简化的购物车项目来学习Redux的工做远离。你须要至少熟悉React库,由于你之后须要将其与Redux集成。java

学习前提

在咱们开始之前,确保你熟悉如下知识:node

同时,确保你的设备已经安装:react

什么是Redux

Redux是一个流行的JavaScript框架,为应用程序提供一个可预测的状态容器。Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据能够在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动。 见下图:ios

图片描述

在Redux中,全部的数据(好比state)被保存在一个被称为store的容器中 → 在一个应用程序中只能有一个。store本质上是一个状态树,保存了全部对象的状态。任何UI组件均可以直接从store访问特定对象的状态。要经过本地或远程组件更改状态,须要分发一个action分发在这里意味着将可执行信息发送到store。当一个store接收到一个action,它将把这个action代理给相关的reducerreducer是一个纯函数,它能够查看以前的状态,执行一个action而且返回一个新的状态。git

理解不变性(Immutability)

在咱们开始实践以前,须要先了解JavaScript中的不变性意味着什么。在编码中,咱们编写的代码一直在改变变量的值。这是可变性。可是可变性经常会致使意外的错误。若是代码只处理原始数据类型(numbers, strings, booleans),那么你不用担忧。可是,若是在处理Arrays和Objects时,则须要当心执行可变操做。
接下来演示不变性github

  • 打开终端并启动node(输入node)。
  • 建立一个数组,并将其赋值给另外一个变量。
> let a = [1, 2, 3]
> let b = a
> b.push(8)
> b
[1, 2, 3, 8]
> a
[1, 2, 3, 8]

能够看到,更新数组b也会同时改变数组a。这是由于对象和数组是引用数据类型 → 这意味着这样的数据类型实际上并不保存值,而是存储指向存储单元的指针。
将a赋值给b,其实咱们只是建立了第二个指向同一存储单元的指针。要解决这个问题,咱们须要将引用的值复制到一个新的存储单元。在Javascript中,有三种不一样的实现方式:web

  1. 使用Immutable.js建立不可变的数据结构。
  2. 使用JavaScript库(如UnderscoreLodash)来执行不可变的操做。
  3. 使用ES6方法执行不可变操做。

本文将使用ES6方法,由于它已经在NodeJS环境中可用了,在终端中,执行如下操做:chrome

> a = [1,2,3]
[ 1, 2, 3 ]
> b = Object.assign([],a)
[ 1, 2, 3 ]
> b.push(8)
> b
[ 1, 2, 3, 8 ] // b output
> a
[ 1, 2, 3 ] // a output

在上面的代码中,修改数组b将不会影响数组a。咱们使用Object.assign()建立了一个新的副本,由数组b指向。咱们也可使用操做符(...)执行不可变操做:数据库

> a = [1,2,3]
[ 1, 2, 3 ]
> b = [...a, 4, 5, 6]
[ 1, 2, 3, 4, 5, 6 ]
> a
[ 1, 2, 3 ]

我不会深刻这个主题,可是这里还有一些额外的ES6功能,咱们能够用它们执行不可变操做:

配置Redux

配置Redux开发环境的最快方法是使用create-react-app工具。在开始以前,确保已经安装并更新了nodejsnpmyarn。咱们生成一个redux-shopping-cart项目并安装Redux

create-react-app redux-shopping-cart

cd redux-shopping-cart
yarn add redux # 或者npm install redux

首先,删除src文件夹中除index.js之外的全部文件。打开index.js,删除全部代码,键入如下内容:

import { createStore } from "redux";

const reducer = function(state, action) {
  return state;
}

const store = createStore(reducer);

让我解释一下上面的代码:

  1. 首先,咱们从redux包中引入createStore()方法。
  2. 咱们建立了一个名为reducer的方法。第一个参数state是当前保存在store中的数据,第二个参数action是一个容器,用于:

    • type - 一个简单的字符串常量,例如ADD, UPDATE, DELETE等。
    • payload - 用于更新状态的数据。
  3. 咱们建立一个Redux存储区,它只能使用reducer做为参数来构造。存储在Redux存储区中的数据能够被直接访问,但只能经过提供的reducer进行更新。

注意到,我在第二点中所提到state。目前,state为undefined或null。要解决这个问题,须要分配一个默认的值给state,使其成为一个空数组:

const reducer = function(state=[], action) {
  return state;
}

让咱们更进一步。目前咱们建立的reducer是通用的。它的名字没有描述它的用途。那么咱们如何使用多个reducer呢?咱们将用到Redux包中提供的combineReducers函数。修改代码以下:

// src/index.js

import { createStore } from "redux";
import { combineReducers } from 'redux';

const productsReducer = function(state=[], action) {
  return state;
}

const cartReducer = function(state=[], action) {
  return state;
}

const allReducers = {
  products: productsReducer,
  shoppingCart: cartReducer
}

const rootReducer = combineReducers(allReducers);

let store = createStore(rootReducer);

在上面的代码中,咱们将通用的reducer修改成productReducercartReducer。建立这两个空的reducer是为了展现如何在一个store中使用combineReducers函数组合多个reducer。

接下来,咱们将为reducer定义一些测试数据。修改代码以下:

// src/index.js

…

const initialState = {
  cart: [
    {
      product: 'bread 700g',
      quantity: 2,
      unitCost: 90
    },
    {
      product: 'milk 500ml',
      quantity: 1,
      unitCost: 47
    }
  ]
}

const cartReducer = function(state=initialState, action) {
  return state;
}

…

let store = createStore(rootReducer);

console.log("initial state: ", store.getState());

咱们使用store.getState()在控制台中打印出当前的状态。你能够在终端中执行npm start或者yarn start来运行dev服务器。并在控制台中查看state

图片描述

如今,咱们的cartReducer什么也没作,但它应该在Redux的存储区中管理购物车商品的状态。咱们须要定义添加、更新和删除商品的操做(action)。咱们首先定义ADD_TO_CART的逻辑:

// src/index.js

…

const ADD_TO_CART = 'ADD_TO_CART';

const cartReducer = function(state=initialState, action) {
  switch (action.type) {
    case ADD_TO_CART: {
      return {
        ...state,
        cart: [...state.cart, action.payload]
      }
    }

    default:
      return state;
  }
}

…

咱们继续来分析一下代码。一个reducer须要处理不一样的action类型,所以咱们须要一个SWITCH语句。当一个ADD_TO_CART类型的action在应用程序中分发时,switch中的代码将处理它。
正如你所看到的,咱们将action.payload中的数据与现有的state合并以建立一个新的state。

接下来,咱们将定义一个action,做为store.dispatch()的一个参数。action是一个Javascript对象,有一个必须的type和可选的payload。咱们在cartReducer函数后定义一个:

…
function addToCart(product, quantity, unitCost) {
  return {
    type: ADD_TO_CART,
    payload: { product, quantity, unitCost }
  }
}
…

在这里,咱们定义了一个函数,返回一个JavaScript对象。在咱们分发消息以前,咱们添加一些代码,让咱们可以监听store事件的更改。

…
let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

接下来,咱们经过分发消息到store来向购物车中添加商品。将下面的代码添加在unsubscribe()以前:

…
store.dispatch(addToCart('Coffee 500gm', 1, 250));
store.dispatch(addToCart('Flour 1kg', 2, 110));
store.dispatch(addToCart('Juice 2L', 1, 250));

下面是整个index.js文件:

// src/index.js

import { createStore } from "redux";
import { combineReducers } from 'redux';

const productsReducer = function(state=[], action) {
  return state;
}

const initialState = {
  cart: [
    {
      product: 'bread 700g',
      quantity: 2,
      unitCost: 90
    },
    {
      product: 'milk 500ml',
      quantity: 1,
      unitCost: 47
    }
  ]
}

const ADD_TO_CART = 'ADD_TO_CART';

const cartReducer = function(state=initialState, action) {
  switch (action.type) {
    case ADD_TO_CART: {
      return {
        ...state,
        cart: [...state.cart, action.payload]
      }
    }

    default:
      return state;
  }
}

function addToCart(product, quantity, unitCost) {
  return {
    type: ADD_TO_CART,
    payload: {
      product,
      quantity,
      unitCost
    }
  }
}

const allReducers = {
  products: productsReducer,
  shoppingCart: cartReducer
}

const rootReducer = combineReducers(allReducers);

let store = createStore(rootReducer);

console.log("initial state: ", store.getState());

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

store.dispatch(addToCart('Coffee 500gm', 1, 250));
store.dispatch(addToCart('Flour 1kg', 2, 110));
store.dispatch(addToCart('Juice 2L', 1, 250));

unsubscribe();

保存代码后,Chrome会自动刷新。能够在控制台中确认新的商品已经添加了。

图片描述

组织Redux代码

index.js中的代码逐渐变得冗杂。我把全部的代码都写在index.js中是为了起步时的简单易懂。接下来,咱们来看一下如何组织Redux项目。首先,在src文件夹中建立一下文件和文件夹:

src/
├── actions
│ └── cart-actions.js
├── index.js
├── reducers
│ ├── cart-reducer.js
│ ├── index.js
│ └── products-reducer.js
└── store.js

而后,咱们把index.js中的代码进行整理:

// src/actions/cart-actions.js

export const ADD_TO_CART = 'ADD_TO_CART';

export function addToCart(product, quantity, unitCost) {
  return {
    type: ADD_TO_CART,
    payload: { product, quantity, unitCost }
  }
}
// src/reducers/products-reducer.js

export default function(state=[], action) {
  return state;
}
// src/reducers/cart-reducer.js

import  { ADD_TO_CART }  from '../actions/cart-actions';

const initialState = {
  cart: [
    {
      product: 'bread 700g',
      quantity: 2,
      unitCost: 90
    },
    {
      product: 'milk 500ml',
      quantity: 1,
      unitCost: 47
    }
  ]
}

export default function(state=initialState, action) {
  switch (action.type) {
    case ADD_TO_CART: {
      return {
        ...state,
        cart: [...state.cart, action.payload]
      }
    }

    default:
      return state;
  }
}
// src/reducers/index.js

import { combineReducers } from 'redux';
import productsReducer from './products-reducer';
import cartReducer from './cart-reducer';

const allReducers = {
  products: productsReducer,
  shoppingCart: cartReducer
}

const rootReducer = combineReducers(allReducers);

export default rootReducer;
// src/store.js

import { createStore } from "redux";
import rootReducer from './reducers';

let store = createStore(rootReducer);

export default store;
// src/index.js

import store from './store.js';
import { addToCart }  from './actions/cart-actions';

console.log("initial state: ", store.getState());

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

store.dispatch(addToCart('Coffee 500gm', 1, 250));
store.dispatch(addToCart('Flour 1kg', 2, 110));
store.dispatch(addToCart('Juice 2L', 1, 250));

unsubscribe();

整理完代码以后,程序依然会正常运行。如今咱们来添加修改和删除购物车中商品的逻辑。修改cart-actions.jscart-reducer.js文件:

// src/reducers/cart-actions.js
…
export const UPDATE_CART = 'UPDATE_CART';
export const DELETE_FROM_CART = 'DELETE_FROM_CART';
…
export function updateCart(product, quantity, unitCost) {
  return {
    type: UPDATE_CART,
    payload: {
      product,
      quantity,
      unitCost
    }
  }
}

export function deleteFromCart(product) {
  return {
    type: DELETE_FROM_CART,
    payload: {
      product
    }
  }
}
// src/reducers/cart-reducer.js
…
export default function(state=initialState, action) {
  switch (action.type) {
    case ADD_TO_CART: {
      return {
        ...state,
        cart: [...state.cart, action.payload]
      }
    }

    case UPDATE_CART: {
      return {
        ...state,
        cart: state.cart.map(item => item.product === action.payload.product ? action.payload : item)
      }
    }

    case DELETE_FROM_CART: {
      return {
        ...state,
        cart: state.cart.filter(item => item.product !== action.payload.product)
      }
    }

    default:
      return state;
  }
}

最后,咱们在index.js中分发这两个action

// src/index.js
…
// Update Cart
store.dispatch(updateCart('Flour 1kg', 5, 110));

// Delete from Cart
store.dispatch(deleteFromCart('Coffee 500gm'));
…

保存完代码以后,能够在浏览器的控制台中检查修改和删除的结果。

使用Redux工具调试

若是咱们的代码出错了,应该如何调试呢?

Redux拥有不少第三方的调试工具,可用于分析代码和修复bug。最受欢迎的是time-travelling tool,即redux-devtools-extension。设置它只须要三个步骤。

  • 首先,在Chrome中安装Redux Devtools扩展。
  • 而后,在运行Redux应用程序的终端里使用Ctrl+C中止服务器。并用npm或yarn安装redux-devtools-extension包。
yarn add redux-devtools-extension
  • 一旦安装完成,咱们对store.js稍做修改:
// src/store.js
import { createStore } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers';

const store = createStore(rootReducer, composeWithDevTools());

export default store;

咱们还能够把src/index.js中日志相关的代码删除掉。返回Chrome,右键单击该工具的图标,打开Redux DevTools面板:

图片描述

图片描述

能够看到,Redux Devtools很强大。你能够在action, statediff(方法差别)之间切换。选择左侧面板上的不一样action,观察状态树的变化。你还能够经过进度条来播放actions序列。甚至能够经过工具直接分发操做信息。具体的请查看文档

集成React

在本文开头,我提到Redux能够很方便的与React集成。只须要简单的几步。

  • 首先,中止服务器,并安装react-redux包:
yarn add react-redux
  • 接下来,在index.js中加入React代码。咱们还将使用Provider类将React应用程序包装在Redux容器中:
// src/index.js
…
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

const App = <h1>Redux Shopping Cart</h1>;

ReactDOM.render(
  <Provider store={store}>
    { App }
  </Provider> ,
  document.getElementById('root')
);
…

目前,已经完成了集成的第一部分。能够启动服务器以查看效果。第二部分涉及到使用刚刚安装的react-redux包中的几个方法。经过这些方法将React组件与Redux的storeaction相关联。此外,还可使用ExpressFeathers这样的框架来设置API。API将为咱们的应用程序提供对数据库服务的访问。

感谢网友整理了本文的相关代码,如须要,请移步这里

在Redux中,咱们还能够安装其余一些包,好比axios等。咱们React组件的state将由Redux处理,确保全部组件与数据库API的同步。想要更进一步的学习,请看Build a CRUD App Using React, Redux and FeathersJS

总结

我但愿本文能对你有所帮助。固然,还有不少相关的内容须要学习。例如,处理异步操做、身份验证、日志记录等。若是以为Redux适合你,能够看看如下几篇文章:

这篇文章是看到比较简明的Redux教程。固然也是翻译过来哒,文中提到了不少延伸文章,我还在一个个学习当中,遇到不错的依然会翻译给你们的。

?喜欢的话记得收藏哦!

相关文章
相关标签/搜索