redux 学习笔记
redux
举个例子,一个带有购物车的网站。在顶部,我们用一个UI组件显示购物车中的商品数量,我们还可以用另一个UI组件,显示购物车中商品的总价。如果用户点击添加到购物车按钮,则这两个组件应该立即更新当前的数据,如果用户从购物车中删除商品,更改商品数目,使用优惠卷或者更改送货地点,则相关的UI组件都应该更新出正确得信息。可以看到,随着功能范围的扩大,一个简单的购物车将会很难保持数据同步。redux 以简单易用的方式构建复杂项目并进行维护。
在redux 中所有的数据(比如state)被保存在一个被成为store的容器中,在一个应用程序中只能由一个。store 本质上是一个状态树,保存了所有对象的状态,任何UI组件都是可以直接从store访问特定对象的状态。要通过本地或远程组件更改状态,需要分发一个action,分发在这里意味着奖执行信息发送到store,当一个store 接受到一个action ,他将把这个action代理给相关的reducer,reducer 是一个纯函数,他可以查看之前的状态,执行一个cation 并且返回一个新的状态。
-
打开一个终端并启动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]
可以看到,更新数组b也会同时改变数组a。这是因为对象和数组是引用数据类型,这意味着这样的数据类型实际上并不保存值,而是存储指向内存单元的指针。将a赋值给b,其实我们只是创建了第二个指向统一存储单元的指针。药解决这个问题,我们需要将引用的值复制到一个新的存储单元,再JavaScript中,有三种不同的实现方式。
-
使用Immutable.js创建不可变的数据结构。
-
-
使用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]
1 npx create-react-app my-app 2 cd my-app 3 yarn add redux 4 yarn start
首先,删除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)
-
-
创建了一个名为reducer 的方法。第一个参数state是当前保存在store中的数据,第二个参数action 是一个容器,用于:
-
type: 一个简单的字符串常量,例如:ADD,DELETE,UPDATE
-
payload: 用于更新状态的数据。
-
-
创建一个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);
整个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())
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 }
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 }
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 }
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 }
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
1 // src/store.js 2 import { createStore } from 'redux' 3 import rootReducer from './reducers' 4 5 let store = createStore(rootReducer) 6 export default store
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()