Redux入门教程详解(快速上手)
典型的Web应用程序通常由共享数据的多个UI组件组成。通常,多个组件的任务是负责展示同一对象的不同属性。这个对象表示可随时更改的状态。在多个组件之间保持状态的一致性会是一场噩梦,特别是如果有多个通道用于更新同一个对象。
举个?,一个带有购物车的网站。在顶部,我们用一个UI组件显示购物车中的商品数量。我们还可以用另一个UI组件,显示购物车中商
品的总价。如果用户点击添加到购物车
按钮,则这两个组件应立即更新当前的数据。如果用户从购物车中删除商品、更改数目、使用优惠券或者更改送货地点,则相关的UI组件都应该更新出正确的信息。
可以看到,随着功能范围的扩大,一个简单的购物车将会很难保持数据同步。
在这篇文章中,我将介绍Redux框架,它可以帮助你以简单易用的方式构建复杂项目并进行维护。为了使学习更容易,我们将使用一个简化的购物车项目
来学习Redux的工作远离。你需要至少熟悉React库,因为你以后需要将其与Redux集成。
学习前提
在我们开始以前,确保你熟悉以下知识:
- 功能性JavaScript
- 面向对象JavaScript
- JavaScript ES6 语法
同时,确保你的设备已经安装:
- NodeJS
- Yarn(或者npm)
什么是Redux
Redux是一个流行的JavaScript框架,为应用程序提供一个可预测的状态容器。Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据可以在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动。 见下图:
在Redux中,所有的数据(比如state)被保存在一个被称为store
的容器中 → 在一个应用程序中只能有一个。store
本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store
访问特定对象的状态。要通过本地或远程组件更改状态,需要分发一个action
。分发在这里意味着将可执行信息发送到store
。当一个store
接收到一个action
,它将把这个action
代理给相关的reducer
。reducer
是一个纯函数,它可以查看之前的状态,执行一个action
并且返回一个新的状态。
理解不变性(Immutability)
在我们开始实践之前,需要先了解JavaScript中的不变性
意味着什么。在编码中,我们编写的代码一直在改变变量的值。这是可变性
。但是可变性
常常会导致意外的错误。如果代码只处理原始数据类型(numbers, strings, booleans),那么你不用担心。但是,如果在处理Arrays和Objects时,则需要小心执行可变操作。
接下来演示不变性
:
- 打开终端并启动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中,有三种不同的实现方式:
- 使用Immutable.js创建不可变的数据结构。
- 使用JavaScript库(如Underscore和Lodash)来执行不可变的操作。
- 使用ES6方法执行不可变操作。
本文将使用ES6方法,因为它已经在NodeJS环境中可用了,在终端中,执行以下操作:
> 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功能,我们可以用它们执行不可变操作:
- spread syntax - 用于追加操作
- map function - 用于更新操作
- filter function - 用于删除操作
配置Redux
配置Redux开发环境的最快方法是使用create-react-app
工具。在开始之前,确保已经安装并更新了nodejs
,npm
和yarn
。我们生成一个redux-shopping-cart
项目并安装Redux
:
create-react-app redux-shopping-cartcd 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);
让我解释一下上面的代码:
- 首先,我们从
redux
包中引入createStore()
方法。 我们创建了一个名为reducer的方法。第一个参数
state
是当前保存在store
中的数据,第二个参数action
是一个容器,用于:type
- 一个简单的字符串常量,例如ADD, UPDATE, DELETE等。payload
- 用于更新状态的数据。
- 我们创建一个Redux存储区,它只能使用reducer作为参数来构造。存储在Redux存储区中的数据可以被直接访问,但只能通过提供的reducer进行更新。
注意到,我在第二点中所提到state
。目前,state
为undefined或null。要解决这个问题,需要分配一个默认的值给state
,使其成为一个空数组:
const reducer = function(state=[], action) {return state;
}
让我们更进一步。目前我们创建的reducer是通用的。它的名字没有描述它的用途。那么我们如何使用多个reducer呢?我们将用到Redux包中提供的combineReducers
函数。修改代码如下:
// src/index.jsimport { 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修改为productReducer
和cartReducer
。创建这两个空的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.jsimport { 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.jsexport 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.jsexport default function(state=[], action) {return state;
}
// src/reducers/cart-reducer.jsimport { 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.jsimport { 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.jsimport { createStore } from "redux";
import rootReducer from './reducers';let store = createStore(rootReducer);export default store;
// src/index.jsimport 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工具调试
如果我们的代码出错了,应该如何调试呢?
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
, state
和diff(方法差异)
之间切换。选择左侧面板上的不同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的store
和action
相关联。此外,还可以使用Express和Feathers这样的框架来设置API。API将为我们的应用程序提供对数据库服务的访问。
在Redux中,我们还可以安装其他一些包,比如axios
等。我们React组件的state
将由Redux处理,确保所有组件与数据库API的同步。想要更进一步的学习,请看Build a CRUD App Using React, Redux and FeathersJS。
总结
我希望本文能对你有所帮助。当然,还有很多相关的内容需要学习。例如,处理异步操作、身份验证、日志记录等。如果觉得Redux适合你,可以看看以下几篇文章:
- Redux State Management in Vanilla JavaScript
- Redux Logging in Production with LogRocket
- Build a CRUD App Using React, Redux and FeathersJS
- Dealing with Asynchronous APIs in Server-rendered React
Redux入门教程详解(快速上手)相关推荐
- python入门教程pdf-Python入门教程详解.pdf
您所在位置:网站首页 > 海量文档  > 计算机 > Python Python入门教程详解.pdf132页 本文档一 ...
- vue 单相绑定_Vuejs第一篇之入门教程详解(单向绑定、双向绑定、列表渲染、响应函数)...
Vuejs第一篇之入门教程详解(单向绑定.双向绑定.列表渲染.响应函数) 2018-12-30 什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素 ...
- k8s入门教程详解(一)
文章目录 Kubernetes 入门教程详解(一) 一. Kubernetes 概述 1. K8S 发展历史由来 2.K8S官网 2.K8S 是什么 3. K8s 优势及特点 3.1 K8S优势 3. ...
- 计算机入门新人必学,电脑新手入门教程 让你快速上手
随着互联网时代的快速发展,电脑发展也相当的成熟,新手学电脑可以快速入门,是每个新手梦寐以求的事情,但是不会所有人都可以快速上手,最近很多用户咨询我关于零基础学电脑先学什么,或者是电脑新手入门必学什么东 ...
- MindManager2020版本入门教程详解
MindManager是一款创造.管理和交流思想的思维导图软件,其直观清晰的可视化界面和强大的功能可以快速捕捉.组织和共享思维.想法.资源和项目进程等等.MindManager新手入门教程专为新手用户 ...
- Python入门教程详解
Python入门教程 目录 1. 简介 2. 安装 3. 基本语法 4. 数据类型 5. 条件语句 6. 循环 7. 函数 8. 模块 9. 异常处理 10. 文件输入输出 1. 简介 Python是 ...
- keyshot9怎么导入材质_keyShot 9如何使用?keyshot 入门教程详解
KeyShot 9 Pro for Mac是应用在Mac上的一款3D渲染和动画制作软件,从科学上准确的材料和环境预设到高级材料编辑和动画,创建交互式产品视觉效果或销售和营销图像从未如此简单. macw ...
- 2022年最新C#入门教程详解
C# 简介 C# 是一个面向对象的编程语言,它是由微软(Microsoft)开发的,由 Ecma 和 ISO 核准认可的.C# 是专为公共语言基础结构(CLI)设计的.CLI 由可执行代码和运行时环境 ...
- KeyShot 9如何使用?keyshot 入门教程详解
KeyShot 9 Pro for Mac是应用在Mac上的一款3D渲染和动画制作软件,从科学上准确的材料和环境预设到高级材料编辑和动画,创建交互式产品视觉效果或销售和营销图像从未如此简单. macw ...
最新文章
- vue在created调用点击方法_vue中created、mounted等方法整理
- .NET 4.0 任务(Task)
- 绕过mysql的id:32933 BUG 实现order by limit 正常取数据.
- java基础day11---空指针异常----引用类型--自定义类型赋值--封装
- django-元选项
- php获取cpu编码,PHP下通过exec获得计算机的唯一标识[CPU,网卡 MAC地址]
- mysql not in 性能_SQL中Execpt和not in 性能区别
- 敏捷开发团队管理系列之一:序言与出发点
- 深入理解jsonp跨域请求原理
- LuaForUnity2:Lua基本数据类型与符号
- Topcoder SRM 601 div1题解
- Linux与Xshell:登陆服务器与后台执行程序
- 电脑网络安全_网络安全月 | 戳视频!帮你的电脑远离“黑客”
- laravel-admin模型表格和详情展示关联数据
- STM32串口DMA方式接收数据。类似环形FIFO。超省CPU资源!
- js alert弹窗函数
- 现代x64的APIC中断控制器架构
- 在Windows下编写的代码,实时在Linux下编译
- 数字证书的创建与使用(采用java)
- 简单聊聊 Ironic 是如何管理裸金属的