Redux、React-Redux 及Redux中间件
一、Redux、React-Redux 分别是什么,有什么联系?
①、Redux 是 JavaScript 状态容器,提供可预测化的状态管理(保证程序行为一致性且易于测试)。由Flux演变而来。
Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
②、React-Redux则是Redux提供的React绑定库,(有点类似Vuex专门服务Vue)需要另外安装依赖。
二、Redux
1) 三大原则
单一数据源,state只读,使用纯函数来执行修改是Redux的三大原则。
- 单一数据源指的是,整个应用的state被存储在唯一的一个store中。
- state只读指的是,唯一改变state的方法就是触发action。
- 使用纯函数来执行修改指的是,编写纯函数reducers来描述action如何改变state。
那什么是reducer?
reducer是一个纯函数,接收旧的state及action,返回新的state.
而reducer为什么必须是一个纯函数?
因为redux的核心是提供可预测化的状态管理,即无论何时特定的action触发的行为永远保持一致。如果是非纯函数则无法保证可预测性。
保证reducer是一个纯函数需要满足如下条件:
- 不得修改传入的参数
- 不得调用非纯函数,如Date.now(),Math.random()等
- 不得执行有副作用的操作,如API请求和路由跳转。
API请求该如何执行?
上面说到,reducer中不能执行api请求操作,那应该如何做呢?有以下两个方法
- 在dispatch方法之前执行api请求。先进行api请求,收到结果后再dispatch不同的action
- 选用redux-thunk,redux-saga,redux-promise等中间件,使得dispatch函数中可以直接执行异步操作。
2) 安装及使用
直接上代码,边看边说吧
npm install --save redux
store.js
import { createStore,combineReducers} from 'redux'
// 初始化reducer
const caclReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case 'ADD':
return state + payload
case 'MINUS':
return state - payload
default:
return state;
}
}
// 初始化reducer
const todoReducers = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
complete:false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {complete:true})
}
return todo
})
default:
return state
}
}
// 使用combineReducers组合多个reducer
let reducers = combineReducers({ caclReducer, todoReducers })
// 创建store
const store = createStore(reducers)
export default store
视图 index.js
import React, { PureComponent } from 'react';
import store from '../store';
class Demo01 extends PureComponent {
constructor(props) {
super(props);
this.state = {
todoContent: ""
};
}
componentDidMount() {
// 视图subscribe订阅
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
// 取消订阅
if (this.unsubscribe) {
this.unsubscribe();
}
}
// dispatch提交更新
add = () => {
store.dispatch({ type: 'ADD', payload: 200 });
};
minus = () => {
store.dispatch({ type: 'MINUS', payload: 100 });
};
getInputVal = e => {
this.setState({
todoContent:e.target.value
})
};
addTodo = () => {
const { todoContent} = this.state
store.dispatch({ type: 'ADD_TODO', text: todoContent })
this.setState({todoContent:""})
}
markTodo (index) {
store.dispatch({type:'COMPLETE_TODO',index})
}
render() {
return (
<div>
<h3>redux示例</h3>
{/* getState()获取store中状态值 */}
<div>当前数值:{store.getState().caclReducer}</div>
<button onClick={this.add}>+200</button>
<button onClick={this.minus}>-100</button>
<hr />
<h3>todo list</h3>
<input type="text" onChange={this.getInputVal} value={this.state.todoContent}/>
<button onClick={this.addTodo}>添加</button>
<ul>
{
store.getState().todoReducers.map((item, index) => {
return !item.complete?<li key={index}>
<span>{item.text}</span>
<button onClick={() => { this.markTodo(index) }}>标记</button>
</li>:undefined
})
}
</ul>
</div>
);
}
}
export default Demo01;
3) redux使用要点总结
- 通过createStore创建唯一store。
- store里的reducer初始化state,并定义state修改规则
- combineReducers可以连接多个reducer
- 通过dispatch action来提交对数据的修改
- action提交到reducer函数里,根据传入的action type,返回新的state
- 通过store.getState()获取状态
- 视图通过subscribe订阅,更新state到视图层
4)遗留问题:每次都要重新调用render、store.getState()。太不方便啦!!为此,react-redux应运而生。这个部分,稍后第四部分重点讲。
我们先来聊聊redux中间件。
三、Redux中间件
1)为什么要使用中间件?
Redux只是个纯粹的状态管理器,默认只支持同步。
如果需要实现异步任务,如延时,网络请求等,就需要中间件的支持。
2)什么是redux中间件?
中间件是一个函数,对store.dispacth方法进行了升级。在发出Action和执行Reducer两步之间,添加了新的功能。
若传递的action是一个对象,dispatch直接传给store
若传递的action是一个函数,dispatch通过中间件自动执行函数,依据函数的执行情况判断是否直接传递给store。
3)如何使用中间件?
下面例举redux-thunk,redux-logger,redux-promise这三个常用中间件的使用方法
(还有个redux-saga中间件,我另外开个专题讲)
1)安装
npm install --save redux-thunk
npm install --save redux-logger
npm install --save redux-promise
2)store.js
// 1、引入applyMiddleware
import {createStore,applyMiddleware,combineReducers} from 'redux'
// 2、引入相关中间件
import thunk from 'redux-thunk' // 异步解决方案
import logger from 'redux-logger' // 打印日志
import promise from 'redux-promise' // 处理promise
// 初始化reducer
const caclReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case 'ADD':
return state + payload
case 'MINUS':
return state - payload
default:
return state;
}
}
const todoReducers = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
complete:false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {complete:true})
}
return todo
})
default:
return state
}
}
// 组合reducer
let reducers = combineReducers({ caclReducer, todoReducers })
// 3、创建store,第一个参数是reducers,第二个参数是中间件
const store = createStore(
reducers,
applyMiddleware(thunk,promise,logger)
)
export default store
3)view.js
import React, { PureComponent } from 'react';
import store from '../store/index1';
class Demo01 extends PureComponent {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
// dispatch提交更新
add = () => {
store.dispatch({ type: 'ADD', payload: 200 });
};
minus = () => {
store.dispatch({ type: 'MINUS', payload: 100 });
};
// 4、模拟提交异步操作
asyncAdd = () => {
store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({ type: 'ADD', payload: 300 });
}, 2000);
});
};
// 5、模拟promise
handlePromise = () => {
store.dispatch(
Promise.resolve({
type: 'MINUS',
payload:5
})
)
}
render() {
return (
<div>
<h3>redux示例</h3>
{/* getState()获取store中状态值 */}
<div>当前数值:{store.getState().caclReducer}</div>
<button onClick={this.add}>+200</button>
<button onClick={this.minus}>-100</button>
<button onClick={this.asyncAdd}>异步操作</button>
<button onClick={this.handlePromise}>处理promise</button>
</div>
);
}
}
export default Demo01;
四、React-Redux
前面说到,单纯靠redux实现步骤过于繁琐。redux作者给react量身定做了一套React-Redux。
1)安装
npm install --save react-redux
2)使用
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux'
import store from './store/index'
ReactDOM.render(
<React.StrictMode>
{/*1、使用Provider为后代组件提供store*/}
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
store.js
import { createStore } from 'redux'
const caclReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case 'ADD':
return state + payload
case 'MINUS':
return state - payload
default:
return state;
}
}
// 创建store,传入reducer
const store = createStore(caclReducer)
export default store
view.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
// connect是一个高阶组件,用于连接React组件和Redux store.
// 返回一个新的已与Redux store连接的组件类
class ReactReduxClassView extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render () {
const { num,add,minus} = this.props
return (
<div>
<h3>React-redux Class组件示例 Caculator</h3>
<p>当前数值是:{num}</p>
<button onClick={add}>+200</button>
<button onClick={minus}>-100</button>
</div>
)
}
}
// 将需要的state的节点注入到与此视图数据相关的组件(props)上
const mapStateToProps = state => {
return {
num:state
}
}
// 将需要绑定的响应事件注入到组件上(props上)
const mapDispatchToProps = {
add: () => {
return {type:'ADD',payload:200}
},
minus: () => {
return {type:'MINUS',payload:100}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxClassView)
上面的写法还是有点复杂,有个更高级的装饰器可以使用,我们来看下
1)用create-react-app创建的项目,先npm run eject后,在package.json里配置以下babel 选项
同时,在项目根目录下新建 jsconfig.json,添加图2内容,以消除警告
"babel": {
"presets": [ "react-app" ],
"plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ]
}
{
"compilerOptions": {
"experimentalDecorators": true
}
}
2)改写view.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
// 第一个参数就是mapStateToProps,第二个参数是mapDispatchToProps(可以是object,也可以是function)
@connect(
(({ cacl }) => ({ num: cacl })),
{
add: () => ({
type:'ADD',payload:200
}),
minus: () => ({
type:'MINUS',payload:100
})
}
)
class ReactReduxClassView extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render () {
const { num,add,minus} = this.props
return (
<div>
<h3>React-redux Class组件示例 Caculator</h3>
<p>当前数值是:{num}</p>
<button onClick={add}>+200</button>
<button onClick={minus}>-100</button>
</div>
)
}
}
export default ReactReduxClassView
上面connect第二个参数也可以是一个function,也可以改成下面写法
@connect(
(({ cacl }) => ({ num: cacl })),
dispatch => {
const add = () => dispatch({ type: 'ADD', payload: 200 })
const minus = () => dispatch({type:'MINUS',payload:100})
return {dispatch,add,minus}
}
)
但是方法多了,return要一个个添加也显得很冗余
import {bindActionCreators } from 'redux'
// 第一个参数就是mapStateToProps,第二个参数是mapDispatchToProps(object | function)
@connect(
(({ cacl }) => ({ num: cacl })),
dispatch => {
let creators = {
add: () => ({
type:'ADD',payload:200
}),
minus: () => ({
type:'MINUS',payload:100
})
}
creators = bindActionCreators(creators,dispatch)
return {dispatch,...creators}
}
)
(我怎么觉得还是object返回最优呢)
随着React 16.8版本增加了hooks,React-Redux也“与时俱进”添加了新的hooks,看下hooks又是怎么写的。
import React from 'react'
import { useSelector,useDispatch} from 'react-redux'
const ReactReduxHookView = (props) => {
const num = useSelector(state => state.cacl)
const dispatch = useDispatch()
const add = () => {
dispatch({type:'ADD',payload:200})
}
const minus = () => {
dispatch({type:'MIMUS',payload:100})
}
return <div>
<h3>React-Redux hook</h3>
<p>当前数值是{num}</p>
<button onClick={add}>+200</button>
<button onClick={minus}>-100</button>
</div>
}
export default ReactReduxHookView
hooks真的是很简洁了
总结下React-Redux的使用要点
- Provider、Connect两个核心api。其中Provider用于为后代组件提供store。connect则为组件提供数据和变更方法。正常情况下,根组件嵌套在<Provider>中才能使用connect方法。
- connect是一个高阶组件,用于连接React组件和Redux store,返回一个已与redux建立连接的组件类。
- connect的第一个参数mapStateToProps 是一个函数,用于建立组件和store.state的映射关系。
- connect的第二个参数mapDispatchToProps可以是object,也可以是function,用于建立组件和store.dispatch的映射关系。
- 函数组件中则可以使用useSelector,和useDispatch这两个hooks.
五、Redux数据流
view-->action-->reducer-->store-->view
view -> action -> middleware -> reducer -> store
- 首先,用户通过View发出Action,发出方式就用到了dispatch方法
- 如果使用了中间件,可以在action发起之后,到达reducer之前执行一些副作用操作,例如api请求。
- 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
- State—旦有变化,Store就会调用监听函数,来更新View
六、Redux,React-Redux实现原理及源码解读
(待补充)