Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

初识Redux Middleware

Raion 2019-01-25 21:41:00 阅读数:285 评论数:0 点赞数:0 收藏数:0

前言

原先改变store是通过dispatch(action) = > reducer;那Redux的Middleware是什么呢?就是dispatch(action) = > reducer过程中搞点事情,既不更改原代码,还能扩展原有功能,这就是Redux的中间件。

至于Redux的Middleware是怎么演变来的,推荐去看看Redux的官网文档,讲得很不错,诸位一定要多看几遍。如果你发现还是不好理解,那请你花点时间,细心看看这篇文章。文章内容比较多,希望你跟着我一步一步敲着代码学习,这样收获更多,要是有什么疑惑或者不对的地方,请指出!

  • 基础环境

这里使用create-react-app搭建环境,方便快速。注意请先自行安装node。

sudo npm i -g create-react-app (如果已经安装过,请忽略;期间根据提示输入密码即可。window用户执行npm i -g create-react-app) 
create-react-app redux-middleware cd redux-middleware yarn add redux react-redux mockjs axios(create-react-app默认使用yarn作为包管理,这里就照着用)

// 下面是版本号
"dependencies": {
"axios": "^0.18.0",
"mockjs": "^1.0.1-beta3",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-redux": "^6.0.0",
"react-scripts": "2.1.3",
"redux": "^4.0.1"
}
  • 一个小例子

为了方便,使用mock拦截,模仿后台接口,根据输入内容,返回模拟数据。现在改写App.js,其余不变。

import React, { Component } from 'react';
import axios from 'axios';
import Mock from 'mockjs';
Mock.mock('http://test.com/search', {
'list|0-5': [{
'id|+1': 1,
name: '@character("lower")',
'version': '@float(1, 10, 2, 2)',
publisher: '@cname'
}]
});
class App extends Component {
state = {
data: [],
searchValue: ''
};
handleSearch = e => {
e.preventDefault();
if (this.state.searchValue) {
axios.get(`http://test.com/search`).then(result => {
if (result.status === 200) {
this.setState({ data: result.data.list.map(item => ({...item, name: `${this.state.searchValue}${item.name}`})) });
}
})
}
};
changeValue = e => {
this.setState({ searchValue: e.target.value });
};
render() {
return (
<div style={{ textAlign: 'center', margin: '40px' }}>
<form onSubmit={this.handleSearch}>
<input type="text" value={this.state.searchValue} onChange={this.changeValue} />
<button type="submit">搜索</button>
</form>
<ul>
{this.state.data.map(item => (
<li key={item.id} style={{ listStyle: 'none' }}>
<p>{item.name}</p>
<p>
{item.publisher} publish {item.version}
</p>
</li>
 ))}
</ul>
</div>
 );
}
}
export default App;
  • 开始redux中间件之旅

现在将App组件与redux关联起来,数据存入state中。

更改index.js。

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './App';
import * as serviceWorker from './serviceWorker';
function listReducer(state = { list: [] }, action) {
switch (action.type) {
case 'receive':
return {
list: action.data
};
default:
return state;
}
}
const store = createStore(listReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));

serviceWorker.unregister();

更改App.js。

import React, { Component } from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import Mock from 'mockjs';
Mock.mock('http://test.com/search', {
'list|0-5': [{
'id|+1': 1,
name: '@character("lower")',
'version': '@float(1, 10, 2, 2)',
publisher: '@cname'
}]
});
class App extends Component {
state = {
searchValue: ''
};
handleSearch = e => {
e.preventDefault();
if (this.state.searchValue) {
axios.get(`http://test.com/search`).then(result => {
if (result.status === 200) {
this.props.changeList(result.data.list.map(item => ({...item, name: `${this.state.searchValue}${item.name}`})));
}
})
}
};
changeValue = e => {
this.setState({ searchValue: e.target.value });
};
render() {
return (
<div style={{ textAlign: 'center', margin: '40px' }}>
<form onSubmit={this.handleSearch}>
<input type="text" value={this.state.searchValue} onChange={this.changeValue} />
<button type="submit">搜索</button>
</form>
<ul>
{this.props.list.map(item => (
<li key={item.id} style={{ listStyle: 'none' }}>
<p>{item.name}</p>
<p>
{item.publisher} publish {item.version}
</p>
</li>
 ))}
</ul>
</div>
 );
}
}
function mapStateToProps(state) {
return {
list: state.list
}
}
function mapDispatchToProps(dispatch) {
return {
changeList: function (data) {
dispatch({ type: 'receive', data });
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

测试一下,我们搜索rxjs,结果如下:

不错,现在是时候了解下redux的中间件了。首先,我想实现一个日志记录的中间件,在dispatch(action) => reducer的过程中能打印派发的action和更改后的store。在没有中间件时,我们来更改下。

function MapStateToProps(state) {
console.log('nextState: ', state);
return {
list: state.list
}
}
function mapDispatchToProps(dispatch) {
return {
changeList: function (data) {
const action = { type: 'receive', data };
console.log('dispatch: ', action); dispatch(action);
}
}
}

 

很可惜,虽然实现了,但在组件初始化时,却打印了初始化的state。原因在于mapStateToProps方法无法判断是初始化返回的数据还是dispatch(action) => reducer引发的state更改。当然这里你可以用一个变量去保存是不是初始化组件(即第一次调用mapStateToProps),但是加入了额外的开销不说,还手动更改了mapStateToProps和mapDispatchToProps方法,代码一下子不好看了。这该怎么办呢?先回溯到更改state过程:dispatch(action) => reducer => state。由于reducer是一个纯函数,只要函数参数唯一,返回结果必定唯一。通过reducer来实现日志当然可以,但是感觉reducer不纯了。你本来只负责生成新state,不能有自己的小心思。那只好把目光放到dispatch上,先来看看dispatch内部实现。

function dispatch(action) {
...// 数据校验
try {
isDispatching = true
currentState = currentReducer(currentState, action)
// 哈哈,新state在此
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}

dispatch是store提供的一个方法,要访问只能在调用dispatch时做些文章。那我试试替换dispatch方法呢?

function logMiddleware(action) {
console.log('dispatch: ', action);
dispatch(action);
console.log('nextState: ', currentState);
}
return {
dispatch: logMiddleware,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}

这样明目张胆改了源码不好,换一种方式。更改index.js。

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './App';
import * as serviceWorker from './serviceWorker';
function listReducer(state = { list: [] }, action) {
switch (action.type) {
case 'receive':
return {
list: action.data
};
default:
return state;
}
}
const store = createStore(listReducer);
let next = store.dispatch;
store.dispatch = function logMiddleware(action) {
console.log('dispatch: ', action);
next(action);
console.log('nextState: ', store.getState()); };
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));

serviceWorker.unregister();

删除App.js中我们加入的日志记录代码。再次搜索如下:

这样乍一看好像可行,如果再加一个中间件呢?再次更改index.js。

...
let next = store.dispatch; store.dispatch = function logMiddleware(action) { console.log('dispatch: ', action); next(action); console.log('nextState: ', store.getState()); }; let next2 = store.dispatch; // 这里的store.dispatch是logMiddleware store.dispatch = function logMiddleware2(action) { console.log('logMiddleware2 start'); next2(action); console.log('logMiddleware2 end'); };
...

糟糕,每次加入一个中间件都得用个变量去存吗?这样的代码太滑稽了,那该如何优雅的获取上一个中间件呢?换个角度思考下,如果将中间件作为参数传递,那效果是不是不一样呢?更改后的index.js如下:

...
function
logMiddleware(dispatch, action) { console.log('dispatch: ', action); let next = dispatch(action); console.log('nextState: ', store.getState()); return next; } function logMiddleware2(dispatch, action) { console.log('logMiddleware2 start'); let next = dispatch(action); console.log('logMiddleware2 end'); return next; } let dispatch = logMiddleware2(logMiddleware(store.dispatch, action), action); store.dispatch = dispatch;
...

这里action这样传递是有问题的,得琢磨琢磨。既然是dispatch(action),那中间件返回一个函数,函数的参数就是action呢?修改index.js如下:

...
function
logMiddleware(dispatch) { return function (action) { console.log('dispatch: ', action); let next = dispatch(action); console.log('nextState: ', store.getState()); return next; } } function logMiddleware2(dispatch) { return function (action) { console.log('logMiddleware2 start'); let next = dispatch(action); console.log('logMiddleware2 end'); return next; } } let dispatch = logMiddleware2(logMiddleware(store.dispatch)); store.dispatch = dispatch;
...

Yes,we can!!!但是细看代码还是不完美,logMiddleware中store是直接获取的,耦合在一起。如果将logMiddleware单独放入一个模块文件中,代码就不能正常运行了。那还不简单,将store导出,再导入到logMiddleware模块中不就完了。可是这样还是耦合,只是换了一种方式而已(你写的中间件应该是其他小伙伴拿来即用的,不应该有其他骚操作)。骚年,还得再想想办法。index.js,不要抱怨,还得再改改你。

...
function
logMiddleware(store) { return function (dispatch) { return function (action) { console.log('dispatch: ', action); let next = dispatch(action); console.log('nextState: ', store.getState()); return next; } } } function logMiddleware2(store) { return function (dispatch) { return function (action) { console.log('logMiddleware2 start'); let next = dispatch(action); console.log('logMiddleware2 end'); return next; } } } let dispatch = logMiddleware2(store)(logMiddleware(store)(store.dispatch)); store.dispatch = dispatch;
...

我们可以发现logMiddleware中dispatch是store.dispatch,logMiddleware2中的dispatch是logMiddleware中间件。既然如此,那换个名称,以免误会。这里统一改成next。最后let dispatch = ...只是为了让大家看懂过程,现在也改一下。

...
function
logMiddleware(store) { return function (next) { return function (action) { console.log('dispatch: ', action); let result = next(action); console.log('nextState: ', store.getState()); return result; } } } function logMiddleware2(store) { return function (next) { return function (action) { console.log('logMiddleware2 start'); let result = next(action); console.log('logMiddleware2 end'); return result; } } } const middlewares = [logMiddleware2, logMiddleware]; const chain = middlewares.map(middleware => middleware(store)); const chains = chain.reduce((a, b) => (...args) => a(b(...args))); let dispatch = chains(store.dispatch); store.dispatch = dispatch;
...

讲到这里,基础也差不多讲完了,希望你能对redux中间件有一个比较初步的认识。

 

版权声明
本文为[Raion]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/raion/p/10312056.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领