Redux
介绍
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览
Redux 除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有2kB,包括依赖)。
流程和使用
安装
npm i redux
store.js
import { createStore } from "redux";
// 创建reducer
// 传入参数,第一个是默认状态,第二个是派发的动作
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
// 创建store
// 将counter包装到createStore,store内就会包含状态和操作
const store = createStore(counter);
export default store;
在React入口文件index.js注册到整个应用中
import React from "react";
import ReactDOM from "react-dom";
import ReduxTest from "./ReduxTest";
// 导入
import store from "./store";
// 将ReactDOM.render包裹导函数内
function render() {
ReactDOM.render(<ReduxTest />, document.querySelector("#root"));
}
render();
// 注册订阅
// 监听state,每次state更新时就会调用render函数重新渲染
// 调用函数时不能加()
store.subscribe(render);
组件中使用
import React, { Component } from "react";
// 导入sotre
import store from "./store";
import { Button } from "antd";
export default class ReduxTest extends Component {
increment = () => {
// 通过dispatch传递action参数,它是一个对象,内包含type属性
// 个值经过switch语句判断
store.dispatch({
type: "INCREMENT",
});
};
decrement = () => {
store.dispatch({
type: "DECREMENT",
});
};
render() {
return (
<div>
<h3>{store.getState()}</h3>
<Button onClick={this.increment}>
+1
</Button>
<Button onClick={this.decrement}>
-1
</Button>
</div>
);
}
}
React-Redux
在React应用使用redux状态渲染组件,每次state更新都会重新render,这造成一些不必要的浪费渲染,这就需要换用react-redux
安装依赖
npm i react-redux
具体步骤
React Redux 提供了 <Provider />,使得Redux store都被应用到应用程序内
修改index.js,不再需要订阅,而是通过Provider将store作为props传入组件使用
import React from "react";
import ReactDOM from "react-dom";
import store from "./store";
import ReduxTest from "./ReduxTest";
import { Provider } from "react-redux";
function render() {
ReactDOM.render(
<Provider store={store}>
<ReduxTest />
</Provider>,
document.querySelector("#root")
);
}
render();
// 订阅不需要了
// store.subscribe(render)
React-Redux提供了 connect 将组件连接到 store
import React, { Component } from "react";
import { Button } from "antd";
import { connect } from "react-redux";
// import store from './store'
// connect的第一个函数参数取得store.js内的state赋值给num用于在组件内获取
const mapStateToProps = (state) => {
return {
num: state,
};
};
// connect的第二个参数取得dispatch来触发store.js内的事件
const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch({ type: "INCREMENT" });
},
decrement: () => {
dispatch({ type: "DECREMENT" });
},
};
};
class ReduxTest extends Component {
render() {
return (
<div>
<h3>{this.props.num}</h3>
<Button onClick={() => this.props.increment()}>
+1
</Button>
<Button onClick={() => this.props.decrement()}>
-1
</Button>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReduxTest);
装饰器写法
抛出原组件,并将connect写在组件上方
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
render() {
return (
<div>
<h3>{this.props.num}</h3>
<Button onClick={() => this.props.increment()}>
+1
</Button>
<Button onClick={() => this.props.decrement()}>
-1
</Button>
</div>
);
}
}
export default ReduxTest;
redux中间件
Redux 入门教程(二):中间件与异步操作 - 阮一峰的网络日志 (ruanyifeng.com)
介绍了 Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染
但是,一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。
怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。
利用redux中间件机制可以在实际action响应前执行其他额外的业务逻辑,特点是可以自由组合,自由插拔的插件机制
中间件概念
为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
想来想去,只有发送 Action 的这个步骤,即 store.dispatch() 方法,可以添加功能
介绍两款比较成熟的中间件
- redux-logger:处理日志记录的中间件
- Redux-thunk:处理异步action
npm i redux-logger redux-thunk
redux-logger
redux-logger的使用早store.js加入
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
// 创建reducer
// 传入参数,第一个是默认状态,第二个是派发的动作
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
// 创建store
// 将counter包装到createStore,store内就会包含状态和操作
// 导入applyMiddleware和logger之后作为第二个参数传入
const store = createStore(counter, applyMiddleware(logger));
// 抛出
export default store;
redux-thunk
使用redux-logger来执行异步操作,导入插件并添加到applyMiddleware
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const store = createStore(counter, applyMiddleware(logger, thunk));
export default store;
组件内使用,通过声明一个函数来返回一个执行异步操作的
import React, { Component } from "react";
import { Button } from "antd";
import { connect } from "react-redux";
const mapStateToProps = (state) => {
return {
num: state,
};
};
// 声明一个函数,这个函数会返回一个函数作为参数传入asyncIncrement方法,内部模拟异步请求
// 返回的函数内传入参数,在请求数据返回后通过dispatch赋值给store
const asyncAdd = () => {
return (dispatch) => {
setTimeout(() => {
dispatch({ type: "INCREMENT" });
}, 1000);
};
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch({ type: "INCREMENT" });
},
decrement: () => {
dispatch({ type: "DECREMENT" });
},
// action默认接收一个对象,执行下一个任务
// 如果是一个函数,则需要使用react-thunk进行异步处理
asyncIncrement: () => {
dispatch(asyncAdd());
},
};
};
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
render() {
return (
<div>
<h3>{this.props.num}</h3>
<Button onClick={() => this.props.asyncIncrement()}>+1</Button>
<Button onClick={() => this.props.decrement()}>-1</Button>
</div>
);
}
}
export default ReduxTest;
模块化
项目代码重构和状态模块划分
在一个大型应用项目中,reducer会有很多,都不会直接写在store中,需要对store进行模块划分
代码划分
store/counter.reducer.js,将reducer的代码放到里面并抛出,reducer需要触发这些方法来修改state,原有定义的mapStateToProps、mapSispatchToProps 和异步方法放在组件内就显得非常冗余了,把这些代码放到counter.reducer.js中后export抛出
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
export const mapStateToProps = (state) => {
return {
num: state,
};
};
// 异步操作
const asyncAdd = () => {
return (dispatch) => {
setTimeout(() => {
dispatch({ type: "INCREMENT" });
}, 1000);
};
};
export const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch({ type: "INCREMENT" });
},
decrement: () => {
dispatch({ type: "DECREMENT" });
},
asyncIncrement: () => {
dispatch(asyncAdd());
},
};
};
export default counter
组件内导入 mapStateToProps 和 mapDispatchToProps
import React, { Component } from "react";
import { Button } from "antd";
import { connect } from "react-redux";
import {mapStateToProps, mapDispatchToProps} from './store/counter.reducer'
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
render() {
return (
<div>
<h3>{this.props.num}</h3>
<Button onClick={() => this.props.asyncIncrement()}>+1</Button>
<Button onClick={() => this.props.decrement()}>-1</Button>
</div>
);
}
}
export default ReduxTest;
store.js内剩余代码放入store/index.js,导入counter.reducer.js
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counter from "./counter.reducer";
const store = createStore(counter, applyMiddleware(logger, thunk));
export default store;
reducer复合
但仅仅是这样,也只能创建一个reducer的store,redux提供了一个combineReducers方法,它接收一个reduicer为成员的对象,对全部的ruducer进行复合,实现状态的模块化
import { createStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counter from "./counter.reducer";
const store = createStore(
combineReducers({
counter,
}),
applyMiddleware(logger, thunk)
);
export default store;
mapStateToProps调用时需加上reducer的这个key来读取,实现模块化的标识
export const mapStateToProps = (state) => {
return {
num: state.counter,
};
};
Mobx
概念
MobX 是一个更加轻量级的状态管理库,它通过透明的函数响应式编程使得状态管理变得简单和可扩展,在小型应用项目中更推荐Mobx。
MobX背后的哲学很简单:
- 任何源自应用状态的东西都应该自动地获得
- 其中包括UI、数据序列化、服务器通讯,等等
React是一个消费者,将应用状态state渲染成组件树对其渲染
Mobx是一个提供者,用于存储和更新状态state
安装依赖
npm i mobx mobx-react
- mobx —— 创建观察者和对应的事件回调函数,以及相关的状态
- mobx-react —— 包装普通组件,让普通组件拥有状态的一些能力
使用
创建store/mobx.js
- observable - 创建观察者对象保存共享状态
- actions - 创建事件回调的方法,修改当前的状态
- computed - 计算属性,本次没有用到,
import { observable, action, computed } from "mobx";
// 创建观察者
const appState = observable({
// 创建一个num属性,默认值为0
num: 0,
});
// actions
appState.increment = action(() => {
appState.num += 1;
});
appState.decrement = action(() => {
appState.num -= 1;
});
export default appState;
组件内使用
import React, { Component } from "react";
import { Button } from "antd";
import { observer } from "mobx-react";
@observer
class MobxTest extends Component {
render() {
return (
<div>
<h2>{this.props.appState.num}</h2>
<Button onClick={() => this.props.appState.increment()}>+1</Button>
<Button onClick={() => this.props.appState.decrement()}>-1</Button>
</div>
);
}
}
export default MobxTest;
导入mobx.js和组件,将appState挂载到组件
import React from "react";
import ReactDOM from "react-dom";
import "./App.less";
import appState from "./store/mobx"
import MobxTest from "./components/MobxTest";
function render() {
ReactDOM.render(
<MobxTest appState={appState} />,
document.querySelector("#root")
);
}
render();
mobx要比redux简单很多,但这只限于小型应用使用
mobx.js也可以换另一种,装饰器写法,observable方法在mobx6.0换为了makeAutoObservable
import { makeAutoObservable, action } from "mobx";
// 创建观察者
class AppState {
num = 0;
constructor() {
makeAutoObservable(this);
}
@action
increment() {
this.num++;
console.log(this.num);
}
@action
decrement() {
this.num--;
console.log(this.num);
}
}
export default new AppState();
对比Redux 和 Mobx
相比于Mobx的轻量级,Redux:
- 学习难度高
- 工作量大
- 内存开销大
- 状态管理集中
- 需遵守样板代码规范
结论:使用Mobx入门简单,构建应用迅速,但是当项目足够大时,还是Redux更适合,开启严格模式,更规范的代码管理
Redux-saga完美方案
redux-saga是一个用于管理应用程序 side effect ( 副作用: 例如异步获取数据,访问浏览器缓存等 ) 的库,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易
redux-saga 使用了 ES6 的 Generator 功能,让一步的流程更易于读取、写入和测试
通过这样的方式,这些异步的流程看起来就像是便准同步的js代码
不同于 redux-thunk,不会再遇到回调地狱问题,可以更容易的测试一步流程并保持action的代码整洁
npm i redux-saga
store/sagas.js
import { call, put, takeEvery } from "redux-saga/effects";
// 模拟登陆的api
const api = {
login() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({ id: 1, name: "Max" });
} else {
reject("用户名或密码错误");
}
}, 1000);
});
},
};
// work saga
function* login(action) {
try {
// 异步代码同步化 (call方法调用上方api对象内的login方法发起请求)
// 返回的result进入put,触发login的action
const result = yield call(api.login);
yield put({ type: "login", result });
} catch (error) {
yield put({ type: "loginError", message: error.message });
}
}
// 将login和saga关联起来,类似监听
function* mySaga() {
yield takeEvery('login_request', login)
}
export default mySaga
index.js
import { createStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import counter from "./counter.reducer";
// 3.1 导入sagas
import mySaga from "./sagas";
// 1.导入并创建中间件
import createSagaMiddleware from "redux-saga";
const mid = createSagaMiddleware();
const store = createStore(
combineReducers({
counter,
}),
// 2.使用中间件
applyMiddleware(logger, mid)
);
// 3.2运行当前中间件
mid.run(mySaga);
export default store;
user.reducer.js内的写法改变
const initState = {
isLogin: false,// 表示用户未登录
userInfo: {}
}
function user(state = initState, action) {
switch (action.type) {
case 'login':
return (isLogin: true)
default:
return initState
}
}
// 异步操作 for redux-thunk
const login = () => {
return (dispatch) => {
setTimeout(() => {
dispatch({ type: "login" });
}, 1000);
};
};
// 异步操作 for redux-saga 纯对象的action
const login = () => {
return { type: "login_request" };
};
export const mapDispatchToProps = dispatch => {
return {
//action默认接收一个对象,执行下一个任务,如果是一个函数,则需要异步处理
login: () => {
dispatch(login())
}
}
}
saga的中间件已经关联到了store中,请求会走到sagas.js,找到takeEvery内的login_request,调用login函数,执行api对象中的login方法发起请求并返回数据,
function* login的result,如果返回正确请求则触发login派发动作并传递登录数据,如果请求错误则触发loginError派发动作并传递错误信息
redux-thunk 和 redux-saga的不同
- thunk可以接受function类型的action,saga则是纯对象的action的解决方案
- saga使用的是generator解决异步问题,非常容易用同步代码编写异步代码,是异步代码同步化写法
- 如果是大型的复杂应用项目,更建议使用redux-saga,反之则是redux-thunk