文章目录
1. 重点提炼
- react 与 redux 的配合
- react-redux 库
- provider 组件:通过
provider
组件的store
属性注入状态数据(注入到应用中) - connect 方法:包装组件,把
store
中的,dispatch
,state
,props
等注入到组件的props
属性下面 - Redux DevTools extension:浏览器插件,对
store
进行开发调试 - middleware(可为每次的请求和响应进行扩展):为
dispatch
(提交action
对象) 提供更多的扩展(如一些日志记录)- redux-chunk:对
dispatch
进行功能扩展,使其能够处理异步任务(函数) - logger (日志)中间件
- redux-chunk:对
- provider 组件:通过
- (提问题的关注点)我想要什么?我做了什么?现在出现了什么问题?
2. example01
引例
2.1 example01-1
框子
App.js
import React from 'react';
import List from './components/List'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {items : [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
]}
}
render() {
return(
<div>
<List data={this.state.items}/>
</div>
)
}
}
export default App;
components/List.js
import React from 'react';
export default class List extends React.Component {
render() {
// 当前这个组件希望接受items数据
console.log(this.props.data)
return(
<div>
</div>
);
}
}
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-1
Branch:branch2commit description:v0.01-1-example01-1(引例—框子)
tag:v0.01-1
2.2 example01-2
框子
React-Redux01\app\src\components\Item.js
import React from 'react';
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
// 当前这个组件也希望接受items数据
console.log(this.props.datas)
return(
<div>
</div>
);
}
}
export default Item;
React-Redux01\app\src\components\List.js
import React from 'react';
import Item from "./Item";
export default class List extends React.Component {
render() {
// 当前这个组件希望接受items数据
console.log(this.props.data)
return(
<div>
<Item datas={this.props.data}/>
</div>
);
}
}
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-2
Branch:branch2commit description:v0.01-2-example01-2(引例—框子)
tag:v0.01-2
2.3 example01-3
数据是可以通过 props
方式传递个子组件,但是如果我们的应用的组件嵌套层级比较多,那么这个时候,数据的传递将会变成灾难。怎么解决呢?
把数据存储在一个大家都能很方便(很直接)就能访问的位置
- localStorage(内存限制:5MB左右)
- 后端
App.js
import React from 'react';
import List from './components/List'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
]
}
// 数据存在本地
localStorage.setItem('items', JSON.stringify(this.state.items));
}
render() {
return(
<div>
<List />
</div>
)
}
}
export default App;
src/components/List.js
import React from 'react';
import Item from './Item';
export default class List extends React.Component {
render() {
let items = localStorage.getItem('items');
// items如果存在,就将其转为对象
items = items && JSON.parse(items);
console.log(items);
return(
<div>
<Item />
</div>
);
}
}
src/components/Item.js
import React from 'react';
export default class Item extends React.Component {
render() {
// 当前这个组件也希望接受 items 数据
// console.log(this.props.datas);
let items = localStorage.getItem('items');
items = items && JSON.parse(items);
console.log(items);
return(
<div>
</div>
);
}
}
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-3
Branch:branch2commit description:v0.01-3-example01-3(引例—localStorage存放数据)
tag:v0.01-3
- 把数据存储在一个大家都能很方便(很直接)就能访问的位置
- localStorage(内存限制:5MB左右)
- 虽然我们可以通过 localStorage 来存储数据,但是,数据的操作是不是很随意的(很不安全!一不小心可能就把数据给改了,其他组件使用的时候,很容易挂掉。)
- 后端(也可存在后端)
- 因此我们需要寻找更好的办法:(redux完美符合)
- 能够共享数据
- 能够管理数据
- localStorage(内存限制:5MB左右)
2.4 example01-4
我们使用redux:
npm i redux
src/store/reducer/users.js
let users = [{
id: 1,
username: 'zs',
password: '123'
},
{
id: 2,
username: 'ls',
password: '123'
},
{
id: 3,
username: 'ww',
password: '123'
},
{
id: 4,
username: 'xm',
password: '123'
}];
export default (state = users, action) => {
switch (action.type) {
default:
return state;
}
}
src/store/reducer/items.js
let items = [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
];
export default (state = items, action) => {
switch (action.type) {
default:
return state;
}
}
src/store/index.js
/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers} from 'redux';
import users from './reducer/users';
import items from './reducer/items';
const store = createStore(
combineReducers({
users,
items
})
);
export default store;
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from "./store";
console.log(store.getState());
ReactDOM.render(
<App />,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
我们可在整个应用的最顶层index
里导入:
我希望在list
和item
中都能用这个数据,我们如何去取呢?直接导入即可。
src/components/List.js
import React from 'react';
import Item from "./Item";
import store from "../store";
export default class List extends React.Component {
render() {
console.log(store.getState());
return(
<div>
<Item datas={this.props.data}/>
</div>
);
}
}
我们其实很容易发现redux
实际就是localstorage
的变体版,无非是存储的位置不一样,这里是往内存中存储的,通过一个变量去管理这些数据的,而刚刚是存在localstorage
中的。实际上后端道理也一样,只不过数据交给后端维护了而已。
src/components/Item.js
import React from 'react';
// 引用的路径会随着当前文件的位置变化而变化(如果组件嵌套非常复杂,后期修改这个引用位置变化会很麻烦,不太方便引用管理)
// 如果想更好的解决这个问题,就用react-redux
import store from "../store";
export default class Item extends React.Component {
render() {
console.log(store.getState());
return(
<div>
</div>
);
}
}
引用的路径会随着当前文件的位置变化而变化(如果组件嵌套非常复杂,后期修改这个引用位置变化会很麻烦,不太方便引用管理)
如果想更好的解决这个问题,就用react-redux
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-4
Branch:branch2commit description:v0.01-4-example01-4(引例—redux管理数据)
tag:v0.01-4
- redux会存在一些问题
- 引用的路径会随着当前文件的位置变化而变化(如果组件嵌套非常复杂,后期修改这个引用位置变化会很麻烦,不太方便引用管理)
- 更新问题,我们举一个例子:
2.5 example01-5
React+Redux
更新问题
React-Redux01\app\src\components\Item.js
import React from 'react';
import store from "../store";
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
let {items} = store.getState();
return(
<div>
<ul>
{
items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default Item;

做一个添加功能:
store\reducer\items.js
let items = [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
];
let maxId = 6; // 当前的id
export default (state = items, action) => {
switch (action.type) {
case 'ADD_ITEM':
return [...state, {
id: ++maxId,
name: action.payload.name,
price: 10000
}];
default:
return state;
}
}
components\Item.js
import React from 'react';
import store from "../store";
class Item extends React.Component {
constructor(props) {
super(props);
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
let val = this.el.value;
// console.log(val);
store.dispatch({
type: 'ADD_ITEM',
payload: {
name: val
}
});
this.el.value = '';
// 如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是 props的变化
console.log(store.getState());
}
render() {
let {items} = store.getState();
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default Item;
我们打印发现store
中数据增加了,但是页面还未有反应。
如果直接使用 store
,会绕开组件的更新,因为它既不是 state
的变化,也不是props
的变化。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-5
Branch:branch2commit description:v0.01-5-example01-5(引例—redux管理数据—数据增加无法自动触发视图更新)
tag:v0.01-5
2.6 example01-6
我们需要在state
中保存一下,每次更新再重新赋值给state
,这样操作会麻烦一些,有没有更加好的解决办法呢?
React-Redux01\app\src\components\Item.js
import React from 'react';
import store from "../store";
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
items: store.getState()
}
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
let val = this.el.value;
// console.log(val);
store.dispatch({
type: 'ADD_ITEM',
payload: {
name: val
}
});
this.el.value = '';
// 如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是 props的变化
console.log(store.getState());
this.setState(store.getState())
}
render() {
let {items} = store.getState();
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default Item;
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-6
Branch:branch2commit description:v0.01-6-example01-6(引例—redux管理数据—数据增加实现触发视图更新)
tag:v0.01-6
3. react-redux
再次强调的是,redux 与 react 并没有直接关系,它是一个独立的 JavaScript 状态管理库,如果我们希望中 React 中使用 Redux,需要先安装 react-redux
3.1 react-redux
它解决了:1、引用问题(不用每次还得找store
,项目结构复杂,对后面的维护造成很多时间上的消耗) 2、更新问题(不用再自己给state
赋值更新视图了。原理其实是我们上会说到redeux
的subscribe()
方法会监听数据变化,然后处理组件的更新问题,而不是调用组件的render
)
3.1.1 安装
npm i -S redux react-redux
// ./store/reducer/user.js
let user = {
id: 0,
username: ''
};
export default (state = user, action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/reducer/users.js
let users = [{
id: 1,
username: 'zs',
password: '123'
},
{
id: 2,
username: 'ls',
password: '123'
},
{
id: 3,
username: 'ww',
password: '123'
},
{
id: 4,
username: 'xm',
password: '123'
}];
export default (state = users, action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/reducer/items.js
let items = [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
];
export default (state = items, action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/reducer/cart.js
export default (state = [], action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/index.js
import {createStore, combineReducers} from 'redux';
import user from './reducer/user';
import users from './reducer/users';
import items from './reducer/items';
import cart from './reducer/cart';
let reducers = combineReducers({
user,
users,
items,
cart
});
const store = createStore(reducers);
export default store;
3.1.2 Provider 组件
想在 React 中使用 Redux ,还需要通过 react-redux 提供的 Provider 容器组件把 store 注入到应用中
// index.js
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
3.1.3 connect 方法
有了 connect 方法,我们不需要通过 props 一层层的进行传递, 类似路由中的 withRouter ,我们只需要在用到 store 的组件中,通过 react-redux 提供的 connect 方法,把 store 注入到组件的 props 中就可以使用了
import {connect} from 'react-redux';
class Main extends React.Component {
render() {
console.log(this.props);
}
}
export default connect()(Main);
默认情况下,connect 会自动注入 dispatch 方法
3.1.3.1 注入 state 到 props
export default connect( state => {
return {
items: state.items
}
} )(Main);
connect 方法的第一个参数是一个函数
- 该函数的第一个参数就是 store 中的 state :
store.getState()
- 该函数的返回值将被解构赋值给 props :
this.props.items
3.1.3.2 example02
引入React-Redux
3.1.3.2.1 example02-1
Provider
组件是容器组件,我们将App
包进去,会注入一些东西,并接收参数。
我们这里它会将store
通过props
的方式传递给Provider
组件,它的内部会接收一个store
,它会把这个store
转发给App
组件(通过props
转发),最终App
的props
接收store
。当其触发更新的时候,它会把新的内容重新传递给App
,即将store
转化了props
,因此store
更新后,组件内部自动刷新。
React-Redux01\app\src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux'; // 类似高阶或容器组件
import store from "./store";
console.log(store.getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
React-Redux01\app\src\components\List.js
import React from 'react';
import Item from "./Item";
import store from "../store";
export default class List extends React.Component {
render() {
return(
<div>
<Item />
</div>
);
}
}
React-Redux01\app\src\components\Item.js
import React from 'react';
import {connect} from 'react-redux';
class Item extends React.Component {
constructor(props) {
super(props);
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
// let val = this.el.value;
// // console.log(val);
//
// store.dispatch({
// type: 'ADD_ITEM',
// payload: {
// name: val
// }
// });
//
// this.el.value = '';
//
// // 如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是 props的变化
// console.log(store.getState());
//
// this.setState(store.getState())
}
render() {
console.log(this.props);
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{/*{*/}
{/* items.map(item => {*/}
{/* return(*/}
{/* <li key={item.id}>{item.name}</li>*/}
{/* )*/}
{/* })*/}
{/*}*/}
</ul>
</div>
);
}
}
export default Item;
我们可以对比一下:打印什么都没有
props
没有东西,需要经过connect
包装:
connect
工厂函数,调用以后会返回一个包装组件(高阶组件)
export default connect()(Item);
dispatch
就是store
里面的派发函数
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.02-1
Branch:branch2commit description:v0.02-1-example02-1(connect工厂函数返回一个包装组件)
tag:v0.02-1
3.1.3.2.2 example02-2
connect 方法的第一个参数是一个函数
- 该函数的第一个参数就是 store 中的 state :
store.getState()
- 该函数的返回值将被解构赋值给 props :
this.props.items
state
就是仓库的state
,该函数的返回值是一个对象,该对象就被解构赋值给 props
React-Redux01\app\src\components\Item.js
import React from 'react';
import {connect} from 'react-redux';
class Item extends React.Component {
constructor(props) {
super(props);
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
let val = this.el.value;
this.props.dispatch({
type: 'ADD_ITEM',
payload: {
name: val
}
});
this.el.value = '';
}
render() {
console.log(this.props);
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
this.props.items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default connect( (state) => {
// state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
return {
items: state.items
};
} )(Item);
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.02-2
Branch:branch2commit description:v0.02-2-example02-2(connect工厂函数实现仓库数据实时更新视图)
tag:v0.02-2
还有一些细节地方,需要我们看文档:
我们看参数名称很容易知道其功能,第一个参数mapStateToProps
:将仓库中的State
映射到组件的props
,第二个参数mapDispatchToProps
:Dispatch
方法映射到组件的props
上
4. Redux DevTools extension
Redux
开发工具扩展:
为了能够更加方便的对 redux 数据进行观测和管理,我们可以使用 Redux DevTools extension 这个浏览器扩展插件
https://github.com/zalmoxisus/redux-devtools-extension
const store = createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
打开浏览器开发者工具面板 -> redux
直接在谷歌商店中安装即可。
我们也可把项目下载下来,自己编译生成:
用git 克隆项目:
然后手动编译:
先用npm
把它的依赖都装上:
我们可以看看package.json
查看这里的命令
如:“build:extension” :源代码进行编译,会自动生成一个扩展
{
"version": "2.17.1",
"name": "remotedev-redux-devtools-extension",
"description": "Redux Developer Tools for debugging application state changes.",
"scripts": {
"start": "gulp",
"build:extension": "rimraf build/extension && cross-env BABEL_ENV=production gulp build:extension",
"build:firefox": "cross-env BABEL_ENV=production gulp build:firefox",
"build:examples": "babel-node examples/buildAll.js",
"precompress:extension": "npm run lint && npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron",
"precompress:firefox": "npm run lint && npm run build:firefox && npm run test:app",
"compress:extension": "gulp compress:extension",
"compress:firefox": "gulp compress:firefox",
"docs:clean": "rimraf _book",
"docs:prepare": "gitbook install",
"docs:build": "npm run docs:prepare && gitbook build",
"docs:watch": "npm run docs:prepare && gitbook serve",
"docs:publish": "npm run docs:clean && npm run docs:build && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:zalmoxisus/redux-devtools-extension gh-pages --force",
"clean": "rimraf build/ && rimraf dev/",
"lint": "eslint .",
"test:app": "cross-env BABEL_ENV=test mocha --require test/app/setup.js --recursive test/app",
"test:chrome": "gulp test:chrome",
"test:electron": "gulp test:electron && rimraf test/electron/tmp",
"test": "npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron"
},
直接用npm start build:extension 启动报了很多错,不太容易解决,如果想解决可仔细分析一下错因。
可在百度搜别人下载好的去下载安装(小迪跳墙弄得)。
装好以后需要在代码中添加:
其实这个插件会自动在window
下追加一个方法,这个方法其实就是调用该插件。
src/store/index.js
/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers} from 'redux';
import users from './reducer/users';
import items from './reducer/items';
const store = createStore(
combineReducers({
users,
items
}),
// 调用插件
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
@@INIT
这个其实就是action
动作,每次执行的action
动作在这里都会有记录。
看看仓库中的State:
也可选择用多种结构去查看:
比较操作过程中的差异性的记录
添加数据就产生action,可以查看数据的变化
也可过滤查询:
之前讲过reducer
的一个纯函数概念,不修改传入参数,我们在这里就可详细看到整个数据变化的过程,非常的方便。
可回放每一步的action
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.02-3
Branch:branch2commit description:v0.02-3-example02-3(引入redux)
tag:v0.02-3
5. 异步 action
许多时候,我们的数据是需要和后端进行通信的,而且中开发模式下,很容易就会出现跨域请求的问题,好在 create-react-app 中内置了一个基于 node 的后端代理服务,我们只需要少量的配置就可以实现跨域
5.1 package.json 配置
相对比较的简单的后端 URL 接口,我们可以直接中 package.json 文件中进行配置
// 后端接口
http://localhost:7777/api/items
// http://localhost:3000
{
...
//
"proxy": "http://localhost:7777"
}
axios({
url: '/api/items'
});
5.2 example03
异步action
5.2.1 example03-1
刚刚我们所做的所有操作都是基于同步式修改的,我们没有去和后端交互也没有做其他的处理。但更多时候需要与后端或者其他东西做交互,有可能有异步处理的需求。针对异步处理,会不会产生一些问题呢?我们搭一下环境,配上后端(nodejs koa
)。
npm init -y
npm i koa koa-router
server1\app.js
const Koa = require('koa');
const KoaRouter = require('koa-router');
const app = new Koa();
const router = new KoaRouter();
router.get('/', async ctx => {
ctx.body = 'hello';
});
app.use( router.routes() );
app.listen(7777);
node .\app.js

让后端存数据:
const Koa = require('koa');
const KoaRouter = require('koa-router');
const app = new Koa();
const router = new KoaRouter();
let items = [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
];
router.get('/', async ctx => {
ctx.body = 'hello';
});
router.get('/api/items', async ctx => {
ctx.body = items;
});
app.use( router.routes() );
app.listen(7777);
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-1
Branch:branch2commit description:v0.03-1-example03-1(引入后端nodejs)
tag:v0.03-1
5.2.2 example03-2
组件挂载完毕后,我们在componentDidMount中发送数据请求,我们安装axios:
npm i axios
React-Redux01\app\src\components\Item.js
import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[]
}
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
}
async componentDidMount() {
let rs = await axios({
url:'http://localhost:7777/api/items'
})
console.log(rs);
}
render() {
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
this.props.items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default connect( (state) => {
// state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
return {
items: state.items
};
} )(Item);
报错并警告,这个时候浏览器发现此时的请求是跨域的,给拦截了。可通过cors
解决或者通过(前端)代理的方式。
用前端代理方式,其实我们每次运行react,它也帮我们在本地开启了一个服务(http://localhost:3000/
),
其实是脚手架工具会帮助我们启一个本地服务,除此之外还进行了代理请求,当我们发送http://localhost:7777/items
请求的时候,肯定会跨域,受到同源策略限制,后端是没有这种同源策略限制,
后端就算跨域也是可以正常完成的。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-2
Branch:branch2commit description:v0.03-2-example03-2(异步action——axios请求跨域问题)
tag:v0.03-2
5.2.3 example03-3
React-Redux01\app\src\components\Item.js
async componentDidMount() {
let rs = await axios({
url: '/api/items'
});
console.log(rs);
}
这样填写就相当于把请求发送到了当前服务器,这个时候不会产生跨源,但是本地服务器没有写这个路由。因此我们需要做一些配置,如果当前请求满足某种规则,就让其帮忙访问真实的地址。即:
/api/items => http://localhost:3000/api/items => http://localhost:7777/api/items
/api/items
=> http://localhost:3000/api/items
(转发)=>http://localhost:7777/api/items
这种转发,称之为正向代理
,不过还有反向代理
它俩都有三个共同的角色:
请求者 代理者 实际目标
请求者
通常是浏览器 代理者
http://localhost:3000 实际目标
http://localhost:7777
正向代理:
请求者&代理者在同一个域(
被服务端
) 目标在另一个服务器(服务端
)
反向代理:
请求者是一个域(
被服务端
) 代理&目标是另外一组(服务端
)
其实vue、react
本地开发大部分都属于正向代理。
如果代码编译完后丢到服务器上,这个时候必然涉及跨域,假如前端代码通过server1
访问,而后端代码通过server2
访问,(通常开发的时候,浏览器也与代理者同在一台服务器上,一般开发都正向代理,而发布后,浏览器就不在一起了,就是反向代理了)浏览器和它们是分开的,这个时候就是反向代理模式了。
相对比较的简单的后端 URL 接口,我们可以直接中 package.json 文件中进行配置。
React-Redux01\app\package.json
{
"name": "app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"axios": "^0.19.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-scripts": "3.4.1",
"redux": "^4.0.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:7777"
}
现在没有跨域错误,而是报404
这个时候需要重启服务,注意修改配置文件后,需要重启服务的!
重启React服务
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-3
Branch:branch2commit description:v0.03-3-example03-3(异步action——解决简单axios请求跨域问题)
tag:v0.03-3
5.2.4 example03-4
以上只是针对简单的情况下直接替换。
假如服务器端的接口变了,则前端的代理请求也需要发生变化。
\server1\app.js
const Koa = require('koa');
const KoaRouter = require('koa-router');
const app = new Koa();
const router = new KoaRouter();
let items = [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
];
router.get('/', async ctx => {
ctx.body = 'hello';
});
router.get('/items', async ctx => {
ctx.body = items;
});
app.use( router.routes() );
app.listen(7777);
src/components/Item.js
async componentDidMount() {
let rs = await axios({
// url: '/api/items'
url: '/items'
});
console.log(rs);
}
虽然打印无误,但是很容易产生另外一种问题。这里我们发送了请求,但是可能在做react项目的时候,你可能会用到react-router(路由),我们做了前端路由配置,而前端路由里也有‘/items’
,很容易造成本地代理服务器,它分不清楚这是前端请求还是后端请求,这样就产生了冲突。
因此一般使用一个特殊前缀表示(如:‘/api/items’
),我这个应用请求的是哪一台服务器,而不是我本地的。如果有多个服务器多个接口,可能就会用‘/api1/items’
、‘/api2/items’
、‘/apiX/items’
等来代表不同服务器的接口地址来进行转发,因此这种模式下在package.json中写“proxy”
的方法就适用不了了。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-4
Branch:branch2commit description:v0.03-4-example03-4(异步action——前端代理服务器url需要特殊前缀)
tag:v0.03-4
5.3 ./src/setupProxy.js 配置
针对相对复杂的情况,(proxy
:代理转发和重写)可以有更多的配置
需要在本地安装一个中间件
npm i -S http-proxy-middleware
我们如果发现有问题最好去官网看看demo
// javascript
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
app.use('/api', createProxyMiddleware({ target: 'http://www.example.org', changeOrigin: true }));
app.listen(3000);
// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
5.3.1 example04
src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use('/api', createProxyMiddleware({
target: 'http://localhost:7777/',
// 路径重写,去掉“/api”
pathRewrite: {
'^/api': ''
} }));
// (存在多个服务器)如果想配置多个再调用ues方法即可
}
配置好别忘了重启本地服务
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.04
Branch:branch2commit description:v0.04-example04(异步action——复杂的代理转发http-proxy-middleware)
tag:v0.04
5.4 example05
需求:请求完数据更新到store
React-Redux01\app\src\store\reducer\items.js
let items = [
];
let maxId = 6; // 当前的id
export default (state = items, action) => {
switch (action.type) {
// case 'ADD_ITEM':
// return [...state, {
// id: ++maxId,
// name: action.payload.name,
// price: 10000
// }];
case 'UPDATE_ITEMS':
// 拿到的就是更新的全部数据,一次性返回
return action.payload.items;
default:
return state;
}
}
React-Redux01\app\src\components\Item.js
import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[]
}
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
}
async componentDidMount() {
let rs = await axios({
// url:'/api/items'
url: '/items'
})
// 更新到store
this.props.dispatch({
type: 'UPDATE_ITEMS',
payload: {
items: rs.data
}
});
}
render() {
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
this.props.items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default connect( (state) => {
// state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
return {
items: state.items
};
} )(Item);
看上去似乎没有任何问题。但是有没有考虑过一个问题,我们如果想把这块代码封装起来,怎么做呢?我们这里的dispatch
只能处理同步的,即只能传入一个数据进去,它也没有通过dispatch
发送请求。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.05
Branch:branch2commit description:v0.05-example05(异步action——请求完数据更新到store)
tag:v0.05
let rs = await axios({
url: '/api/items'
});
this.props.dispatch({
type: 'UPDATE_ITEMS',
payload: {
items: rs.data
}
});
希望this.props.dispatch
接收一个异步函数,直接提交就行了,把这个过程封装起来,但它本身只接收对象,不接收函数。
我们用Middleware中间件
来实现,叫做redux-thunk
。
6. Middleware
默认情况下,dispatch 是同步的,我们需要用到一些中间件来处理
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd(action.type)
return result
}
6.1 redux.applyMiddleware
通过 applyMiddleware 方法,我们可以给 store 注册多个中间件
注意:devTools 的使用需要修改一下配置
npm i -D redux-devtools-extension
...
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore(
reducres,
composeWithDevTools(
applyMiddleware( logger )
)
)
7. redux-thunk
作用:这是一个把同步 dispatch 变成异步 dispatch 的中间件
7.1 安装
npm i -S redux-thunk
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import user from './reducer/user';
import items from './reducer/items';
import cart from './reducer/cart';
let reducers = combineReducers({
user,
items,
cart
});
const store = createStore(
reducers,
composeWithDevTools(applyMiddleware(
thunk
))
);
7.2 example06
实现this.props.dispatch
接收一个异步函数,直接提交就行了,把这个过程封装起来,但它本身只接收对象,不接收函数。
7.2.1 example06-1
import thunk from 'redux-thunk'
,把它引入到仓库中,然后用中间件把这个dispatch改了,它里面有一个方法applyMiddleware
,它的作用就是中间件,把我们引入的thunk
扔进去,对dispatch
方法进行包装,去改变dispatch
的行为。先不调用中间件对dispatch
方法进行包装,先注释看看效果。
React-Redux01\app\src\store\index.js
/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
import users from './reducer/users';
import items from './reducer/items';
const store = createStore(
combineReducers({
users,
items
}),
// 对 dispatch 方法进行包装
// applyMiddleware(thunk)
// 调用插件
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
React-Redux01\app\src\components\Item.js
import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[]
}
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
}
async componentDidMount() {
// let rs = await axios({
// // url:'/api/items'
// url: '/items'
// })
// // 更新到store
// this.props.dispatch({
// type: 'UPDATE_ITEMS',
// payload: {
// items: rs.data
// }
// });
this.props.dispatch( function () {
});
}
render() {
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
this.props.items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default connect( (state) => {
// state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
return {
items: state.items
};
} )(Item);
this.props.dispatch默认只能接收一个对象(action动作),不能接收函数的。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-1
Branch:branch2commit description:v0.06-1-example06-1(把同步dispatch 变成异步 dispatch 的中间件,注释包装函数)
tag:v0.06-1
打开注释,调用中间件对dispatch
方法进行包装,就不报错了。
7.2.2 example06-2
注释调用中间件对dispatch
方法进行包装,完善接收的函数,传递函数对象。
dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的。
React-Redux01\app\src\components\Item.js
async componentDidMount() {
// let rs = await axios({
// // url:'/api/items'
// url: '/items'
// })
// // 更新到store
// this.props.dispatch({
// type: 'UPDATE_ITEMS',
// payload: {
// items: rs.data
// }
// });
// 这个函数只能是同步的
function updateAction() {
return {
type: 'UPDATE_ITEMS',
payload: {
items: []
}
}
}
// dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的
this.props.dispatch( updateAction() );
}
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-2
Branch:branch2commit description:v0.06-2-example06-2(把同步dispatch 变成异步 dispatch 的中间件,注释包装函数,传递函数对象)
tag:v0.06-2
7.2.3 example06-3
注释调用中间件对dispatch
方法进行包装,完善接收的函数,传递函数对象。
dispath
最终是接受一个action
对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的。我们传递异步函数试试。
React-Redux01\app\src\components\Item.js
async componentDidMount() {
// let rs = await axios({
// // url:'/api/items'
// url: '/items'
// })
// // 更新到store
// this.props.dispatch({
// type: 'UPDATE_ITEMS',
// payload: {
// items: rs.data
// }
// });
// 这个函数只能是同步的
function updateAction() {
// return {
// type: 'UPDATE_ITEMS',
// payload: {
// items: []
// }
// }
return new Promise((resolve) => {
setTimeout(() => {
resolve({
type: 'UPDATE_ITEMS',
payload: {
items: []
}
});
}, 1000);
})
}
// dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的
// dispatch 会被修改,这里的 dispatch 并不是真正的原来的 dispatch 函数,
this.props.dispatch( updateAction() );
}
updateAction()
不是立马执行的,立刻产生一个对象(1s
后才产生action
对象),这里的定时器是一个异步任务,this.props.dispatch
它不会接受异步任务的,它必须要求立马返回一个对象给它。这是原生的dispatch
的一种行为,这个时候如果把Ajax封装进来的话,这个时候必然就会产生问题了。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-3
Branch:branch2commit description:v0.06-3-example06-3(把同步dispatch 变成异步 dispatch 的中间件,注释包装函数,传递异步函数试试)
tag:v0.06-3
7.2.4 example06-4
实际上是希望的代码是这样的:
Ajax
请求(异步)发送完毕后,再返回一个action
对象。
async componentDidMount() {
// let rs = await axios({
// // url:'/api/items'
// url: '/items'
// })
// // 更新到store
// this.props.dispatch({
// type: 'UPDATE_ITEMS',
// payload: {
// items: rs.data
// }
// });
// 这个函数只能是同步的
// async function updateAction(dispatch) {
async function updateAction() {
let rs = await axios({
url: '/api/items'
});
return {
type: 'UPDATE_ITEMS',
payload: {
items: []
}
}
}
// dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的
this.props.dispatch( updateAction() );
}
默认情况下只能处理同步的,我把注释打开,调用中间件对dispatch
方法进行包装。
React-Redux01\app\src\store\index.js
/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
import users from './reducer/users';
import items from './reducer/items';
const store = createStore(
combineReducers({
users,
items
}),
// 对 dispatch 方法进行包装
applyMiddleware(thunk)
// 调用插件
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
但是还是报错
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-4
Branch:branch2commit description:v0.06-4-example06-4(把同步dispatch 变成异步 dispatch 的中间件,打开包装函数,传递异步函数,报错)
tag:v0.06-4
7.2.5 example06-5
我暂时找不到问题,我们去查查官网
包装以后,this.props.dispatch
它已经不是原生的dispatch了,它会自动地把真正地dispatch
传到函数参数里。dispatch
参数直接往里传递即可。
React-Redux01\app\src\components\Item.js
import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[]
}
this.addNewItem = this.addNewItem.bind(this);
}
addNewItem() {
}
componentDidMount(dispatch) {
// dispatch参数才是真正原生的
async function updateAction(dispatch) {
// return {
// type: 'UPDATE_ITEMS',
// payload: {
// items: []
// }
// }
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve({
// type: 'UPDATE_ITEMS',
// payload: {
// items: []
// }
// });
// }, 1000);
// })
let rs = await axios({
// url: '/api/items',
url: '/api/items'
});
dispatch({
type: 'UPDATE_ITEMS',
payload: {
items: rs.data
}
});
// return {
// type: 'UPDATE_ITEMS',
// payload: {
// items: []
// }
// }
}
// dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的
// dispatch 会被修改,这里的 dispatch 并不是真正的原来的 dispatch 函数
this.props.dispatch( updateAction );
}
render() {
return(
<div>
<input type="text" ref={el => {
this.el = el;
}}/> <button onClick={this.addNewItem}>添加</button>
<ul>
{
this.props.items.map(item => {
return(
<li key={item.id}>{item.name}</li>
)
})
}
</ul>
</div>
);
}
}
export default connect( (state) => {
// state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
return {
items: state.items
};
} )(Item);
src/store/index.js
/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers, applyMiddleware} from 'redux';
import users from './reducer/users';
import items from './reducer/items';
import thunk from 'redux-thunk';
const store = createStore(
combineReducers({
users,
items
}),
// 对 dispatch 方法进行包装
applyMiddleware(thunk),
// 调用插件
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
但是现在的redux的浏览器扩展插件用不了,会报错。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-5
Branch:branch2commit description:v0.06-5-example06-5(实现同步dispatch 变成异步 dispatch 的中间件,redux插件报错)
tag:v0.06-5
7.2.6 example06-6
只能换一种方式,用代码的方式去装这个扩展插件。
npm i redux-devtools-extension
之后在仓库中引用,并再加一层包装
React-Redux01\app\src\store\index.js
/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
import users from './reducer/users';
import items from './reducer/items';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(
combineReducers({
users,
items
}),
// 对 dispatch 方法进行包装
// applyMiddleware(thunk),
// 调用插件
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
这就大功告成了!
原先的dispatch
函数只能接收同步函数,但现在就可以传异步函数了,异步函数执行完毕之后,再执行dispatch
了。这就相当于把请求action
动作都封装起来了,可以在任意地方调用了。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-6
Branch:branch2commit description:v0.06-6-example06-6(实现同步dispatch 变成异步 dispatch 的中间件,实现)
tag:v0.06-6
7.3 小结原理—example07
我们大致研究一下原理:
7.3.1 example07-1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="./redux.min.js"></script>
<script>
let users = [
{
id: 1,
username: 'zs',
age: 35
},
{
id: 2,
username: 'ls',
age: 30
}
];
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, action.payload];
default:
return state;
}
}
let store = Redux.createStore( reducer, users );
let unsubscribe = store.subscribe(function() {
console.log(store.getState());
});
// console.log(store);
// console.log( store.getState() );
function add(payload) {
return {
type: 'ADD',
payload
}
}
store.dispatch(add({
id: 3,
username: 'xm',
age: 30
}));
// console.log( store.getState() );
store.dispatch(add({
id: 3,
username: 'xh',
age: 30
}));
// console.log( store.getState() );
</script>
</body>
</html>
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.07-1
Branch:branch2commit description:v0.07-1-example07-1(通过add方法添加数据的,通过store.dispatch来调用add函数)
tag:v0.07-1
这里是通过add
方法添加数据的,通过store.dispatch
来调用add
函数,传入想要数据进来,但是这个add
方法,只能是同步的。
那怎样处理异步函数呢?
7.3.2 example07-2
let users = [
{
id: 1,
username: 'zs',
age: 35
},
{
id: 2,
username: 'ls',
age: 30
}
];
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, action.payload];
default:
return state;
}
}
let store = Redux.createStore( reducer, users );
function add(payload) {
return new Promise(resolve => {
setTimeout(() => {
resolve( {
type: 'ADD',
payload
})
}, 1000);
})
}
store.dispatch( add('abc') );
console.log( store.getState() );
因为dispatch
不处理异步任务,会报错!我们修改代码,其实就是中间件的原理!
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.07-2
Branch:branch2commit description:v0.07-2-example07-2(通过add方法添加数据的,通过store.dispatch来调用异步add函数,报错)
tag:v0.07-2
7.3.3 example07-3
我们把原来dispatch
先存起来,再重写,再去调用,就是我们重写后的的dispatch
函数了。处理完then
后再调用原生dispatch
。
let users = [
{
id: 1,
username: 'zs',
age: 35
},
{
id: 2,
username: 'ls',
age: 30
}
];
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, action.payload];
default:
return state;
}
}
let store = Redux.createStore( reducer, users );
function add(payload) {
return new Promise(resolve => {
setTimeout(() => {
resolve( {
type: 'ADD',
payload
})
}, 1000);
})
}
// 先将原有的store.dispatch存起来,再重写!
let dispatch = store.dispatch;
// 把它重写
store.dispatch = function(reducer) {
reducer.then( val => {
console.log(val);
// 处理完then后再调用原生dispatch
dispatch(val);
} );
};
store.dispatch( add('abc') );
console.log( store.getState() );
console.log(1);
这样就大功告成了,这里把执行异步的过程封装成了一个方法。把这个方法直接传给Middleware
里去,它会自动注入dispatch
函数进来,实际重写了。
参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.07-3
Branch:branch2commit description:v0.07-3-example07-3(通过add方法添加数据的,通过store.dispatch来调用异步add函数,实现)
tag:v0.07-3
7.3.4 伪代码原生原理
let users = [
{
id: 1,
username: 'zs',
age: 35
},
{
id: 2,
username: 'ls',
age: 30
}
];
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, action.payload];
default:
return state;
}
}
function add(payload) {
return new Promise(resolve => {
setTimeout(() => {
resolve( {
type: 'ADD',
payload
})
}, 1000);
})
}
// 先将原有的store.dispatch存起来,再重写!
// let dispatch = store.dispatch;
// 把它重写
// store.dispatch = function(reducer) {
// reducer.then( val => {
// console.log(val);
// // 处理完then后再调用原生dispatch
// dispatch(val);
// } );
// };
// 函数式
function fn1(store) {
// 这个函数自动传入一个参数,next 下一个中间件的调用
return function (next) {
// 这个函数才是最终被改写的dispatch函数
return function (action) {
}
}
}
// applyMiddleware 自动给fn1传入的第一个参数是store,它必须返回一个函数
let store = Redux.createStore( reducer, applyMiddleware( fn1 ) );
store.dispatch( fn1('abc') );
console.log( store.getState() );
8. Redux 中间件
8.1 Redux 中间件的作用
Reudx
中的中间件是通过对 dispatch
进行的二次封装,对 action
发起之后,reducer
调用之前的行为进行扩展,我们可以通过中间件来进行:日志记录
、发送报告
、调用异步接口
……
8.2 从日志记录需求开始
需求:再每次 dispatch
的时候打印修改之前和之后的 state
8.2.1 准备
let initState = {
users: []
}
function reducers(state=initState, {type, payload}) {
switch(type) {
case 'user/add':
return Object.assign(state, {
users: [...state.users, payload]
});
}
return state;
}
let store = Redux.createStore(
reducers
);
8.2.2 最直接最原始的做法
console.log(store.getState());
store.dispatch({
type: 'user/add',
payload: 'zs'
});
console.log(store.getState());
8.2.3 封装 logger
function logger(store, action) {
console.log(store.getState());
store.dispatch(action)
console.log(store.getState());
}
logger(store, {
type: 'user/add',
payload: 'zs'
});
8.2.4 封装 dispatch
let next = store.dispatch;
store.dispatch = function logger(action) {
console.log(store.getState());
next(action);
console.log(store.getState());
}
store.dispatch({
type: 'user/add',
payload: 'zs'
});
8.3 添加错误报告发送
需求:每次 dispatch
把提交的数据进行持久化
let next = store.dispatch;
store.dispatch = function storage(action) {
next(action);
let s = localStorage
}
store.dispatch({
type: 'user/add',
payload: 'zs'
});
8.4 封装合并多个中间件
- 把每个独立的中间件封装成独立的函数
- 每个中间件都需要获取一次
dispatch
function logger(store) {
let next = store.dispatch;
return function loggerDispatch(action) {
console.log('before: ', store.getState());
next(action);
console.log('after: ', store.getState());
}
}
function storage(store) {
let next = store.dispatch;
return function storageDispatch(action) {
next(action);
localStorage.setItem('store', JSON.stringify(store.getState()));
console.log('持久存储完成');
}
}
store.dispatch = logger(store);
store.dispatch = storage(store);
store.dispatch({
type: 'user/add',
payload: 'zs'
});
8.5 方法一个方法统一处理中间件函数的注册
封装一个 applyMiddleware
方法
function applyMiddleware(store, middlewares) {
middlewares.reverse()
middlewares.forEach(middleware => {
store.dispatch = middleware(store)
})
}
applyMiddleware(store, [logger, storage])
8.6 抽取 next
function applyMiddleware(store, middlewares) {
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware => {
dispatch = middleware(store)(dispatch)
})
store.dispatch = dispatch;
}
function logger(store) {
return function(next) {
return function loggerDispatch(action) {
console.log('before: ', store.getState());
next(action);
console.log('after: ', store.getState());
}
}
}
function storage(store) {
return function(next) {
return function storageDispatch(action) {
next(action);
localStorage.setItem('store', JSON.stringify(store.getState()));
console.log('持久存储');
}
}
}
8.7 ES6
改写
let logger = store => next => action => {
console.log('before: ', store.getState());
next(action);
console.log('after: ', store.getState());
}
let storage = store => next => action => {
next(action);
localStorage.setItem('store', JSON.stringify(store.getState()));
console.log('持久存储');
}
8.8 使用 Redux.applyMiddleware
方法
Redux
内置了一个 applyMiddleware
方法用来注册中间件
let store = Redux.createStore(
reducers,
Redux.applyMiddleware(logger, storage)
);
8.9 异步的问题
仔细观察我们发现,默认的 dispatch
是没有对异步的任务进行处理的,且该方法默认情况下接收的是一个 action
对象
8.9.1 redux-thunk
的原理
通过中间件重新封装 dispatch
方法,使它能够接收一个异步任务函数
let thunk = store => next => action => {
console.log('thunk');
new Promise( (resolve, reject) => {
action(next);
} )
}
store.dispatch( dispatch => {
setTimeout(() => {
dispatch({
type: 'user/add',
payload: 'zs'
});
}, 1000);
} )
(后续待补充)