理解并使用Redux
1.为什么要使用redux
- 像父子组件之间相互传值相互调用的情况,并且值的适用范围仅限于父子组件之间,这时不需要使用Redux.
- 当某个子组件去更新某种状态时,比如更新组织机构数据。而其他的页面又需要依赖这些数据时,此时可以考虑使用redux,把这些状态值放入到redux中进行管理。
2.redux工作的过程图
3.redux的工作流程图
- Redux简介
Redux是针对JavaScript应用的可预测状态容器 。
可预测性(predictable)
状态容器(state container)
JavaScript应用
首先我们需要弄清Redux模型中的几个组成对象:action 、reducer、store :
- action:官方的解释是action是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源;要通过本地或远程组件更改状态,需要分发一个action;
- reducer:action发出了做某件事的请求,只是描述了要做某件事,并没有去改变state来更新界面,reducer就是根据action的type来处理不同的事件;
- store:store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态。
在React中的组件是无法直接更动state(状态)的包含值,要透过setState方法来进行更动,这有很大的原因是为了Virtual DOM(虚拟DOM)的所设计,这是其中一点。另外在组件的树状阶层结构,父组件(拥有者)与子组件(被拥有者)的关系上,子组件是只能由父组件以props(属性)来传递属性值,子组件自己本身无法更改自己的props,这也是为什么一开始在学习React时,都会看到大部份的例子只有在最上层的组件有state,而且都是由它来负责进行当数据改变时的重新渲染工作,子组件通常只有负责呈现数据。
当然,有一个很技巧性的方式,是把父组件中的方法声明由props传递给子组件,然后在子组件触发事件时,调用这个父组件的方法,以此来达到子组件对父组件的沟通,间接来更动父组件中的state。不过这个作法并不直觉,需要事先规范好两边的方法。在简单的应用程序中,这沟通方式还可行,但如果是在有复杂的组件嵌套阶层结构时,例如层级很多或是不同树状结构中的子组件要互相沟通时,这个作法是派不上用场的。
在复杂的组件树状结构时,唯一能作的方式,就是要将整个应用程序的数据整合在一起,然后独立出来,也就是整个应用程序领域的数据部份。另外还需要对于数据的所有更动方式,也要独立出来。这两者组合在一起,就是称之为”应用程序领域的状态”,为了区分组件中的状态(state),这个作为应用程序领域的持久性数据集合,会被称为store(存储)。
说明:以上两段来自慕课网对Redux的总结。
- Redux简单示例(转载):
配置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
首先,删除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为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在项目开发中使用:
用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。但是,一个关键问题没有解决:异步操作怎么办?
Action 发出以后,Reducer 立即算出 State,这叫做同步
但是在实际开发中action大部分的情况是调用接口发送异步请求,也就是说:
Action 发出以后,过一段时间再执行 Reducer,这就是异步
redux-thunk 是一个比较流行的 redux 异步 action 中间件 。
1.导入thunk: import thunk from ‘redux-thunk’。
2.导入中间件: import {createStore,applyMiddleware} from ‘redux’。
3.创建store:let store = createStore(reducer函数,applyMiddleware(thunk))。
4.**redux-thunk中间件,只需要在createStore中加入applyMiddleware(thunk)就可以。5.创建action 创建函数,利用redux-thunk 帮助你统一了异步和同步 action 的调用方式(把异步过程放在 action 级别解决)。
相关使用示例:
//1.相关组件的引入 import { createStore ,applyMiddleware} from 'redux'; import thunk from 'redux-thunk'; //2.新建store怎么加入中间件 const store = createStore(counter,applyMiddleware(thunk)); //3.action函数怎么使用 export function addGunAsync(){ //thunk插件的作用,这里可以返回函数 return dispatch =>{ //异步结束后,手动执行dispatch setTimeout(() => { // addGUN()时你的action dispatch(addGUN()) }, 2000) } }
关于mapStateToProps 、mapDispatchToProps 和connect的用法可以参考
https://www.cnblogs.com/hanmeimei/p/8820621.html借鉴相关文章:
https://blog.****.net/xiangzhihong8/article/details/80518709
https://blog.****.net/qq_42606051/article/details/81907165