若是要用一句话来归纳Redux,那么可使用官网的这句话:Redux是针对JavaScript应用的可预测状态容器。此句话虽然简单,但包含了如下几个含义:node
Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据能够在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动。react
具体的模型图以下图所示:
ios
为了说明整个模型的运做流程,首先咱们须要弄清Redux模型中的几个组成对象:action 、reducer、store。数据库
在Redux中,全部的数据(好比state)被保存在一个被称为store的容器中 ,在一个应用程序中只能有一个store对象。当一个store接收到一个action,它将把这个action代理给相关的reducer。reducer是一个纯函数,它能够查看以前的状态,执行一个action而且返回一个新的状态。npm
其实说到Redux,就不得不提到Flux,不管是Flux或其余以Flux架构为基础延伸发展的函数库(Alt、Reflux、Redux…)都是为了要解决同一个问题:App state的管理。redux
无论什么应用程序都须要有App state(应用程序状态),不管是在一个须要用户登陆的应用,要有全局的记录着用户登陆的状态,或是在应用程序中不一样操做介面(组件)或各类功能上的数据沟通,都须要用到它。axios
属性React.js的同窗都知道,React被设计为一个MVC架构中的View(视图)的函数库,但实际上它能够做的事情比MVC中的View(视图)还要更多,它甚至能够做相似Model(模型)或Controller(控制器)的事情。设计模式
同时,在React中的组件是没法直接更动state(状态)的包含值,要透过setState方法来进行更动,这有很大的缘由是为了Virtual DOM(虚拟DOM)的所设计,这是其中一点。另外在组件的树状阶层结构,父组件(拥有者)与子组件(被拥有者)的关系上,子组件是只能由父组件以props(属性)来传递属性值,子组件本身自己没法更改本身的props,这也是为何一开始在学习React时,都会看到大部份的例子只有在最上层的组件有state,并且都是由它来负责进行当数据改变时的从新渲染工做,子组件一般只有负责呈现数据。数组
固然,有一个很技巧性的方式,是把父组件中的方法声明由props传递给子组件,而后在子组件触发事件时,调用这个父组件的方法,以此来达到子组件对父组件的沟通,间接来更动父组件中的state。不过这个做法并不直觉,须要事先规范好两边的方法。在简单的应用程序中,这沟通方式还可行,但若是是在有复杂的组件嵌套阶层结构时,例如层级不少或是不一样树状结构中的子组件要互相沟通时,这个做法是派不上用场的。服务器
在复杂的组件树状结构时,惟一能做的方式,就是要将整个应用程序的数据整合在一块儿,而后独立出来,也就是整个应用程序领域的数据部份。另外还须要对于数据的全部更动方式,也要独立出来。这二者组合在一块儿,就是称之为”应用程序领域的状态”,为了区分组件中的状态(state),这个做为应用程序领域的持久性数据集合,会被称为store(存储)。
说明:以上两段来自慕课网对Redux的总结。
单向数据流是Flux架构的核心设计,其流程示意图以下:
这个数据流的位于最中心的设计是一个AppDispatcher(应用发送器),你能够把它想成是个发送中心,不论来自组件何处的动做都须要通过它来发送。每一个store会在AppDispatcher上注册它本身,提供一个callback(回调),当有动做(action)发生时,AppDispatcher(应用发送器)会用这个回调函数通知store。
因为每一个Action(动做)只是一个单纯的对象,包含actionType(动做类型)与数据(一般称为payload),咱们会另外须要Action Creator(动做建立器),它们是一些辅助函数,除了建立动做外也会把动做传给Dispatcher(发送器),也就是调用Dispatcher(发送器)中的dispatch方法。
Dispatcher(发送器)的用途就是把接收到的actionType与数据(payload),广播给全部注册的callbacks。它这个设计并不是是首创的,这在设计模式中相似于pub-sub(发布-订阅)系统,Dispatcher则是相似Eventbus的概念。
配置Redux开发环境的最快方法是使用create-react-app
工具。在开始以前,确保已经安装并更新了nodejs、npm和yarn。下面以生成一个redux-shopping项目并安装Redux为例。
若是没有安装create-react-app
工具,请使用下面的命令先执行安装操做。
npm install -g create-react-app
而后,在使用下面的命令建立redux-shopping项目。
create-react-app redux-shopping cd redux-shopping yarn add redux
首先,删除src文件夹中除index.js之外的全部文件。打开index.js,删除全部代码,键入如下内容:
import { createStore } from "redux"; const reducer = function(state, action) { return state; } const store = createStore(reducer);
上面代码的意思是:
目前,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定义一些测试数据。
// 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());
接下来,咱们能够在终端中执行npm start
或者yarn start
来运行dev服务器,并在控制台中查看state。
如今,咱们的cartReducer什么也没作,但它应该在Redux的存储区中管理购物车商品的状态。咱们须要定义添加、更新和删除商品的操做(action)。此时咱们能够作以下的一些定义:
// 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,做为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会自动刷新,能够在控制台中确认新的商品已经添加了。
会发现,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.js和cart-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拥有不少第三方的调试工具,可用于分析代码和修复bug。最受欢迎的是time-travelling tool,即redux-devtools-extension。设置它只须要三个步骤。
yarn add redux-devtools-extension
一旦安装完成,咱们对store.js稍做修改都会反映到结果上。例如,咱们还能够把src/index.js中日志相关的代码删除掉。返回Chrome,右键单击该工具的图标,打开Redux DevTools面板。
能够看到,Redux Devtools很强大。你能够在action, state和diff(方法差别)之间切换。选择左侧面板上的不一样action,观察状态树的变化,你还能够经过进度条来播放actions序列。
若是你的项目使用的是React,那么Redux能够很方便的与React集成。
yarn add react-redux
目前,已经完成了集成的第一部分。能够启动服务器以查看效果。第二部分涉及到使用刚刚安装的react-redux包中的几个方法。经过这些方法将React组件与Redux的store和action相关联。此外,还可使用Express和Feathers这样的框架来设置API。
在Redux中,咱们还能够安装其余一些包,好比axios等。咱们React组件的state将由Redux处理,确保全部组件与数据库API的同步。想要查看更多的内容,能够访问下面的连接Build a CRUD App Using React, Redux and FeathersJS