redux 学习笔记

redux

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

举个例子,一个带有购物车的网站。在顶部,我们用一个UI组件显示购物车中的商品数量,我们还可以用另一个UI组件,显示购物车中商品的总价。如果用户点击添加到购物车按钮,则这两个组件应该立即更新当前的数据,如果用户从购物车中删除商品,更改商品数目,使用优惠卷或者更改送货地点,则相关的UI组件都应该更新出正确得信息。可以看到,随着功能范围的扩大,一个简单的购物车将会很难保持数据同步。redux 以简单易用的方式构建复杂项目并进行维护。

什么是redux

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

在redux 中所有的数据(比如state)被保存在一个被成为store的容器中,在一个应用程序中只能由一个。store 本质上是一个状态树,保存了所有对象的状态,任何UI组件都是可以直接从store访问特定对象的状态。要通过本地或远程组件更改状态,需要分发一个action,分发在这里意味着奖执行信息发送到store,当一个store 接受到一个action ,他将把这个action代理给相关的reducer,reducer 是一个纯函数,他可以查看之前的状态,执行一个cation 并且返回一个新的状态。

理解不变性

在我们开始实践之前,需要先了解JavaScript中不变性意味着什么。在编码中,我们编写的代码一致在改变变量的值,这是可变性,但是可变性常常会导致意外的错误。如果代码只处理原始数据类型,那么你不用担心,但是,如果在处理Arrays 和 Objects 时,则须小心执行可变操作。接下来演示不变性

  • 打开一个终端并启动node

  • 创建一个数组,并赋值给另一个变量

1     let a = [1,2,3]
2     let b = a
3     b.push(4)
4     a 
5     // [1,2,3,4]
6     b
7     // [1,2,3,4]
View Code

可以看到,更新数组b也会同时改变数组a。这是因为对象和数组是引用数据类型,这意味着这样的数据类型实际上并不保存值,而是存储指向内存单元的指针。将a赋值给b,其实我们只是创建了第二个指向统一存储单元的指针。药解决这个问题,我们需要将引用的值复制到一个新的存储单元,再JavaScript中,有三种不同的实现方式。

  • 使用Immutable.js创建不可变的数据结构。

  • 使用JavaScript库(如Underscore和Lodash)来执行不可变的操作。

  • 使用ES6方法执行不可变操作。

 1     let a = [1,2,3]
 2     let b = Object.assign([], a)
 3     let c = [...a, 4]
 4     b.push(4)
 5     a
 6     // [1,2,3]
 7     b
 8     // [1,2,3,4]
 9     c
10     // [1,2,3,4]
View Code

配置Redux

配置redux 开发环境最快的方法是使用 create-react-app 工具。在开始之前,确保已经安装并更新了node.js,npm,yarn。生成一个redux-shopping-cart项目并且安装redux

1    npx create-react-app my-app
2    cd my-app
3    yarn add redux
4    yarn start
View Code

首先,删除src 文件夹中除index.js 以外的所有文件。打开index.js, 删除代码,输入以下内容

1 import { createStore } from 'redux'
2 const reducer = function(state=[], action) {
3     return state
4 }
5 const store = createStore(reducer)
View Code
  1. 首先,从redux 包中引入createStore()方法

  2. 创建了一个名为reducer 的方法。第一个参数state是当前保存在store中的数据,第二个参数action 是一个容器,用于:

    • type: 一个简单的字符串常量,例如:ADD,DELETE,UPDATE

    • payload: 用于更新状态的数据。

  3. 创建一个Redux 存储区,它只能使用reducer作为参数来构造。存储在Redyx存储区中的数据可以直接被访问,但是只能通过提供的reducer进行更新。

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

 1 // src/index.js
 2 
 3 import { createStore } from "redux";
 4 import { combineReducers } from 'redux';
 5 
 6 const productsReducer = function(state=[], action) {
 7   return state;
 8 }
 9 
10 const cartReducer = function(state=[], action) {
11   return state;
12 }
13 
14 const allReducers = {
15   products: productsReducer,
16   shoppingCart: cartReducer
17 }
18 
19 const rootReducer = combineReducers(allReducers);
20 
21 let store = createStore(rootReducer);
View Code

整个index.js 文件

 1 import {createStore, combineReducers} from 'redux'
 2 
 3 // 初始化数据
 4 const initialState = {
 5     cart: [
 6         {
 7             product: 'bread 700g',
 8             quantity: 2,
 9             unitCost: 90
10         },
11         {
12             product: 'milk 700ml',
13             quantity: 2,
14             unitCost: 90
15         }
16     ]
17 }
18 
19 const ADD_TO_CART = 'ADD_TO_CART'
20 
21 const productsReducer = function(state = [], action){
22     return state
23 }
24 const cartReducer = function(state=initialState, action) {
25     switch(action.type) {
26         case ADD_TO_CART: {
27             return {
28                 ...state,
29                 cart: [...state.cart, action.payload]
30             }
31         }
32         default: 
33         return state
34     }
35 }
36 const allReducers = {
37     products: productsReducer,
38     shoppingCart: cartReducer
39 }
40 // 使用combineReducers 组合多个reducer
41 const rootReducer = combineReducers(allReducers)
42 // 创建store 实例
43 const store = createStore(rootReducer)
44 // 订阅监听每次数据变化
45 let unsubscribe = store.subscribe(()=>{
46     console.log(store.getState())
47 })
48 
49 function addToCart (product, quantity, unitCost) {
50     return {
51         type: ADD_TO_CART,
52         payload: { product, quantity, unitCost}
53     }
54 }
55 // 分发指令,改变状态
56 store.dispatch(addToCart('coffee 50gm', 1, 250))
57 store.dispatch(addToCart('Flour 1kg', 2, 220))
58 store.dispatch(addToCart('Juice 2L', 2, 110))
59 
60 unsubscribe();
61 
62 console.log('initial state: ' + store.getState())
View Code

组织 Redux 代码

index.js 中的代码逐渐变得冗余,重新组织文件结构。

 1 // cart-actions.js
 2 export const ADD_TO_CART = 'ADD_TO_CART'
 3 export const UPDATE_CART = 'UPDATE_CART'
 4 export const DELETE_FROM_CART = 'DELETE_FROM_CART'
 5 
 6 export function addToCart(product, quantity, unitCost) {
 7     return {
 8         type: ADD_TO_CART,
 9         payload: {product, quantity, unitCost}
10     }
11 }
12 
13 export function updateCart(product, quantity, unitCost) {
14     return {
15         type: UPDATE_CART,
16         payload: {product, quantity, unitCost}
17     }
18 }
19 
20 export function deleteFromCart(product) {
21     return {
22         type: DELETE_FROM_CART,
23         payload: {product}
24     }
25 }
View Code
 1 // products-actions.js
 2 export const ADD_TO_PRODUCT = 'ADD_TO_PRODUCT'
 3 export const UPDATE_PRODUCT = 'UPDATE_PRODUCT'
 4 export const DELETE_PRODUCT = 'DELETE_PRODUCT'
 5 
 6 export function addToProduct(name, price, storageNum){
 7     return {
 8         type: ADD_TO_PRODUCT,
 9         payload: {name, price, storageNum}
10     }
11 }
12 
13 export function updateProduct(name, price, storageNum) {
14     return {
15         type: UPDATE_PRODUCT,
16         payload: {name, price, storageNum}
17     }
18 }
19 
20 export function deleteProduct(product) {
21     return {
22         type: DELETE_PRODUCT,
23         payload: {product}
24     }
25 }
View Code
 1 // cart-reducer.js
 2 import { ADD_TO_CART, UPDATE_CART, DELETE_FROM_CART } from '../actions/cart-actions'
 3 
 4 const initialState = {
 5     cart: [
 6         {
 7             product: 'bread 700g',
 8             quantity: 2,
 9             unitCost: 90
10         },
11         {
12             product: 'milk 500ml',
13             quantity: 1,
14             unitCost: 30
15         },
16         {
17             product: 'coffee 300ml',
18             quantity: 2,
19             unitCost: 180
20         }
21     ]
22 }
23 
24 export default function (state = initialState, action) {
25     switch (action.type) {
26         case ADD_TO_CART: {
27             return {
28                 ...state,
29                 cart: [...state.cart, action.payload]
30             }
31         }
32         case UPDATE_CART: {
33             return {
34                 ...state,
35                 cart: state.cart.map(item => item.product === action.payload.product ? action.payload: item)
36             }
37         }
38         case DELETE_FROM_CART: {
39             return {
40                 ...state,
41                 cart: state.cart.filter(item => item.product !== action.payload.product)
42             }
43         }
44         default:
45             return state
46     }
47 }
View Code
 1 // products-reducer.js
 2 import { ADD_TO_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT } from '../actions/products-actions'
 3 const initialState = {
 4     products: [
 5         {
 6             name: '冰激凌',
 7             price: 35,
 8             storageNum: 100,
 9         },
10         {
11             name: '锅巴',
12             price: 5,
13             storageNum: 100
14         }
15     ],
16 }
17 
18 export default function (state = initialState, action) {
19     switch(action.type){
20         case ADD_TO_PRODUCT: {
21             return {
22                 ...state,
23                 products: [...state.products, action.payload]
24             }
25         }
26         case UPDATE_PRODUCT: {
27             return {
28                 ...state,
29                 products: state.products.map(item => item.name === action.payload.name ? action.payload: item)
30             }
31         }
32         case DELETE_PRODUCT: {
33             return {
34                 ...state,
35                 products: state.products.filter(item => item.name !== action.payload.name)
36             }
37         }
38         default: {
39             return state
40         }
41     }
42 }
View Code
 1 // reducers/index.js
 2 import {combineReducers} from 'redux'
 3 import productsReducer from './products-reducer'
 4 import carReducer from './cart-reducer'
 5 
 6 const allReducers = {
 7     products: productsReducer,
 8     shoppingCart: carReducer
 9 }
10 
11 const rootReducer = combineReducers(allReducers)
12 export default rootReducer
View Code
1 // src/store.js
2 import { createStore } from 'redux'
3 import rootReducer from './reducers'
4 
5 let store = createStore(rootReducer)
6 export default store
View Code
 1 // src/index.js
 2 import store from './store.js'
 3 import { addToCart, updateCart, deleteFromCart } from './actions/cart-actions'
 4 import { addToProduct, updateProduct, deleteProduct } from './actions/products-actions'
 5 console.log("initial state:", store.getState())
 6 
 7 // 订阅监听每次值变化
 8 let unsubscribe = store.subscribe(() => {
 9     console.log(store.getState())
10 })
11 
12 // 新增
13 store.dispatch(addToCart('Flour 1kg', 2, 100))
14 store.dispatch(addToCart('Juice 2L', 1, 200))
15 // 修改
16 store.dispatch(updateCart('Flour 1kg', 5, 110))
17 // 删除
18 store.dispatch(deleteFromCart('coffee 300ml'))
19 
20 store.dispatch(addToProduct('辣条', 10, 100))
21 store.dispatch(addToProduct('酸奶', 13, 150))
22 store.dispatch(addToProduct('方便面', 10, 100))
23 
24 store.dispatch(updateProduct('辣条', 100, 6))
25 store.dispatch(deleteProduct('方便面'))
26 
27 store.dispatch(addToProduct('酸辣粉', 60, 8))
28 unsubscribe()
View Code

 

posted @ 2020-03-17 15:36  ichthyo-plu  阅读(106)  评论(0编辑  收藏  举报