文章目录
前言
一、redux产生原因
在我们利用react时,每个组件经常要通过状态提升、或者context来使用全局的state。那么state就会被任意修改,变得不好管理,所以我们提出了redux思想:
只通过特定的动作对state进行预定修改、有专门的管理中心,各组件通过管理中心“交流”
引入redux前
引入redux后
二、redux定义
redux是一种思想,(一种开发方式)本质是一种系统架构,它有三个特点:
1.单向数据流
2.单store
3.不修改、只返回状态
react-redux:结合了redux的redux,特点:
1.将redux的state分发到props
2.提供整体容器Provider
三、redux实现
我们知道redux是思想,而react刚好需要这种思想,从这里我们将一步一步的实现redux,并在最后封装出一个完全脱离react的redux模型。
1.初步
在上文中我们提到,需要一个状态管理中心,通过该状态管理中心去改变状态,那我们先写一个dispatch函数用于管理状态,其余部分依旧采用react形式,
代码如下(示例):
//简单定义一个render函数
var render = function () {
document.getElementById('title').innerHTML = state.title
document.getElementById('content').innerHTML = state.content
}
//初始化state
var state = {
title: '这是一个标题',
content: '这是内容'
}
//更新状态
var setState = function (newState) {
state = {
...state,
...newState
}
render()
}
render()
// 实现一个分发函数,只接受预定动作,对state进行预定修改
var dispatch = function (action) {
switch (action.type) {
case 'CHANGE_TITLE':
setState({
title: action.newTitle
})
break
default:
break
}
}
// 在switch中根据type确定接下来动作,传入利用setState
dispatch(
{
type: 'CHANGE_TITLE',
newTitle: 'this is a new title'
}
)
这里我们可以看到,现在只有满足条件的action才可以修改state
2.脱离setState、状态改变后不局限于render
初步实现中,我们还是很react的声明了setState方法,在这里我们直接将该方法删除,在dispatch中直接实现(数组的结构),此外我们在上述代码中是在setState方法运行后调用render,在这里我们不想将state更新后操作局限于render,所以我们借用subscribe函数,去将我们所有想执行的操作存入一个数组中,在dispatch的‘setState’方法后遍历该数组。
代码如下(示例):
var render=function()
{
document.getElementById('title').innerHTML=state.title
document.getElementById('content').innerHTML=state.content
}
var state={
title:'这是一个标题',
content:'这是内容'
}
// 实现一个分发函数,只接受预定动作,对state进行预定修改
var dispatch=function(action)
{
switch(action.type)
{
case 'CHANGE_TITLE':
state={
...state,
title:action.newTitle
}
break
default:
break
}
listeners.forEach(listener=>listener())
}
// 需要在state改变之后执行的所有函数都放在此处
var listeners=[]
// 把需要修改的函数都放到里面
var subscribe=function(listener)
{
listeners.push(listener)
}
subscribe(render)
subscribe(function()
{
console.log(state)
})
// 在switch中根据type确定接下来动作,传入利用setState
dispatch(
{
type:'CHANGE_TITLE',
newTitle:'this is a demo2 title'
}
)
3.初步生成createStore方法
我们将刚才的一系列除(render)封装到createStore中,因为最后我们要实现的是调用createStore就返回一个store,而我们的createStore可以分为三个部分:①state②dispatch()③subscribe,那我们直接return这三部分即可在外面调用这三个方法(值得注意的是我们要重新声明一个getState来获取当前的state),并且render方法总是将该state作为参数渲染页面
代码如下(示例):
var render=function(state)
{
document.getElementById('title').innerHTML=state.title
document.getElementById('content').innerHTML=state.content
}
// 调用此方法就返回一个store
var createStore=function()
{
var state={
title:'这是一个标题',
content:'这是内容'
}
var getState=function()
{
return state
}
// 实现一个分发函数,只接受预定动作,对state进行预定修改
var dispatch=function(action)
{
switch(action.type)
{
case 'CHANGE_TITLE':
state={
...state,
title:action.newTitle
}
break
default:
break
}
listeners.forEach(listener=>listener())
}
// 需要在state改变之后执行的所有函数都放在此处
var listeners=[]
// 把需要修改的函数都放到里面
var subscribe=function(listener)
{
listeners.push(listener)
}
return{
subscribe,
dispatch,
getState
}
}
var store=createStore()
store. subscribe(function()
{
render(store.getState())
})
store.subscribe(function()
{
console.log(store.getState())
})
// 在switch中根据type确定接下来动作,传入利用setState
store.dispatch(
{
type:'CHANGE_TITLE',
newTitle:'this demo2 title'
}
)
// 初始化
render(store.getState())
</script>
4.终级实现
经过上述的一系列操作,我们的redux已经颇为纯净,但还是不够,所以我们要继续向外抽离。不难发现,当下的dispatch所接收的action以及对象的state被我们写死在函数内部,这一定不是我们想要的,所以我们首先要做的第一步正是将dispatch抽离出来,这里我们可以定义它为一个函数,在函数中接收预定的action,对state进行预定的处理,而我在文章首部已经提及,redux是只返回不修改,那么我们就可以定义state变量去接受我们存在于createStore外部的action所返回的state值,而定义在外面的这个函数所要完成的功能就是通过接受action修改state。至此逻辑很清晰了,那我再来总结一下该部分。我们的外部函数(称为appReducer函数)功能为接收action,修改state后返回它,而为了初始化state,我们也要增添一个state参数,以保证在无state时有一个初始state可以返回;而在dispatch(即我们的管理中心)用state去接收我们的返回值,并调用方法数组遍历。
代码实现
var render = function (state) {
document.getElementById('title').innerHTML = state.title
document.getElementById('content').innerHTML = state.content
}
// 自定义action的过程
var appReducer = function (state, action) {
// 默认state
if (!state) {
return {
title: '初始化',
content: '初始content'
}
}
// 根据action返回state
switch (action.type) {
case 'CHANGE_TITLE':
return {
...state,
title: action.newTitle
}
default:
return state
}
}
// 调用此方法就返回一个store
var createStore = function (reducer) {
var state = null
var listeners = []
// 实现一个分发函数,只接受预定动作,对state进行预定修改
var dispatch = function (action) {
state = reducer(state, action) //返回新的 改变原本
listeners.forEach(listener => listener())
}
// 初始state
dispatch({})
// 需要在state改变之后执行的所有函数都放在此处
var getState = function () {
return state
}
// 把需要修改的函数都放到里面
var subscribe = function (listener) {
listeners.push(listener)
}
return {
subscribe,
dispatch,
getState
}
}
// 方法运用
var store = createStore(appReducer)
store.subscribe(function () {
render(store.getState())
})
store.subscribe(function () {
console.log(store.getState())
})
// 在switch中根据type确定接下来动作,传入利用setState
store.dispatch(
{
type: 'CHANGE_TITLE',
newTitle: 'this2 title'
}
)
// 初始页面
render(store.getState())
5.总结原理及实现
以上就是redux的原理实现,但是这些过程我们在react中已经被封装好,不难发现,我们要关心的部分主要是reducer和怎么使用store去控制listener,因为reducer书写决定state的改变。
四、react-redux
1.初步实现
至此我们已经自定义好了一个redux模型,接下来我们要来结合redux一步一步去实现react-redux
首先我们先写一个点击切换标题的demo来感受一下:
App.js
var store=createStore(reducer)
class App extends Component
{
static childContextTypes={
store :PropTypes.object
}
getChildContext()
{
return{
store:store
}
}
constructor()
{
super()
// 订阅App的setState去更新组件
store.subscribe(()=>
{
this.setState(store.getState())
})
}
changeTitle(newTitele)
{
store.dispatch({type:'CHANGE_TITELE',newTitele:newTitele})
}
render()
{
const {title}=store.getState()
console.log(title)
return(
<div>
<Title></Title>
<button onClick={()=>{this.changeTitle('新♥')}} >点击切换标题</button>
</div>
)
}
}
Title.js
class Title extends Component
{
static contextTypes=
{
store:PropTypes.object
}
constructor()
{
super()
}
render()
{
const {store}=this.context
const {title}=store.getState()
return(
<h3 onClick={()=>
{
store.dispatch({type:'CHANGE_TITELE',newTitele:'xixi'})
}}></h3>
)
}
}
我们可以注意到在App.js中,我们不在维护自己的state而是利用store去直接维护;修改state时利用dispatch去修改state;更新组件时利用subscribe去订阅setState方法(传入的参数为store.getState()获取到的state),在实现子组件继承时利用context,将store传递到store中。
2升级版本
在上部分我们已经初步引入redux到react中,但我们发现有几点问题首先是难道我们要每写一个组件就写一遍有关context的继承吗?答案当然是否,我们可以利用Connect将context过程封装,如下:
class Connect extends Component{
static contextTypes=
{
store:PropTypes.object
}
render()
{
return(
<Title store={this.context.store}></Title>
)
}
}
这样 我们的Title组件就被Connect包装了,它的props中有着store(注意:此时复用的时候引出的是Connect组件,而不是Title)
但现在好像还是每次都要写一个Connect组件去返回子组件,还是很麻烦那么怎么办呢?答案是:写一个函数,每次用调用此函数传入子组件进行包装,代码如下:
var Connect=function(WrappedComponent)
{
return(
class Connect extends Component{
static contextTypes=
{
store:PropTypes.object
}
render()
{
return(
<WrappedComponent store={this.context.store}></WrappedComponent>
)
}
}
)
}
export default Connect(Title)
这其实是react中的一个高阶组件:将某组件通过某方法进一步封装出来的组件即为高阶组件。现在我们就可以任意的封装任何子组件啦。
3进阶版本
我们的模型已经日益完善,但还是有一点,现在我们传递的都是整个store,但我们在每个子组件中应用store时并不需要store中所有的内容,那么我们可以进一步实现筛选,筛选出我们要像子组件中传递的部分内容。如果你还是不能理解我的意思,那就让我们一起来回忆在上述过程(已经明白可跳过后面解释,直接看代码),在上述过程中我们已经知道我们的store其实蕴含着许多的dispatch type类型、还有各种各样的state,但是对于某组件A来说,他可能只需要type:a 和state:a去改变自己的状态,那么仓库中的其他内容它是不需要的,如果利用上文中方法传递store我们传递的是所有,这无疑浪费,所以我们要筛选。
那么这个过程就意味着我们先利用两个函数进行筛选,筛选后的内容利用上文的connect函数去包装,这里需要利用闭包去实现:
var Connect= function(mapStateToProps,mapDispatchToProps){
return function(WrappedComponent)
{
return(
class Connect extends Component{
static contextTypes=
{
store:PropTypes.object
}
constructor()
{
super()
this.state={
//初始化所有需要更新的props
allProps:{}
}
}
componentWillMount()
{
const {store}=this.context
this._updateProps()
// 订阅更新
store.subscribe(()=>this._updateProps())
}
_updateProps()
{
var {store}=this.context
// 取出所有,进行选择 如果存在就执行,否则空
var needState=mapStateToProps?
mapStateToProps(store.getState(),this.props):{}
var needDispatch=mapDispatchToProps?
mapDispatchToProps(store.dispatch,this.props):{}
this.setState(
{
allProps:{
...needDispatch,
...needState,
...this.props
}
}
)
}
render()
{
return(
<WrappedComponent {...this.state.allProps}></WrappedComponent>
)
}
}
)
}
}
在我们实际运用中,只需要像下面那样,调用这两个函数:
var mapStateToProps=function(state,ownProps)
{
return{
title:state.title
}
}
var mapDispatchToProps=function(dispatch,ownProps)
{
return{
changeTitle:function()
{
dispatch({type:'CHANGE_TITELE',newTitle:'abcde'})
}
}
}
然后在组件中的this.props中即可获取到相关属性。
其实我们在实际开发中,需要关注的并不是content如何实现,而是关注上面这两个函数如何实现。
那么这实现这两个函数时需要注意些什么呢?
4终极版本
接下来我们来回归看一下App这个父组件,它作为根组件与其他组件有着一点不同,先看下代码:
class App extends Component
{
static childContextTypes={
store :PropTypes.object
}
getChildContext()
{
return{
store:store
}
}
constructor()
{
super()
// 订阅App的setState去更新组件
store.subscribe(()=>
{
this.setState(store.getState())
})
}
changeTitle(newTitele)
{
store.dispatch({type:'CHANGE_TITELE',newTitele:newTitele})
}
render()
{
const {title}=store.getState()
return(
<div>
<Title></Title>
{/* <button onClick={()=>{this.changeTitle('新♥')}} >点击切换标题</button> */}
</div>
)
}
}
我们可以看到它的功能为1.向子组件传递props、2.自己本身也要订阅更新、发起dispatch。那我们可以发现第二个功能也可用connect来封装,那第一个功能我们可以再向上提升一级。这样就可以实现所有的组件都是由connect函数来确定需要传递的功能。我们可以定义一个provider让它老包裹所有组件,进行传递context.
class Provider extends Component
{
static PropTypes=
{
store :PropTypes.object
,children:PropTypes.any
}
static childContextTypes={
store :PropTypes.object
}
getChildContext()
{
return{
store:this.props.store
}
}
render()
{
return(
<div>
{this.props.children}
</div>
)
}
}
5、总结
至此,我们已经实现了redux以及react-redux的源码。
五、试着运用真正的react-redux
1、安装配置react-redux环境
2、一个小demo(点击+1),初始react-redux
相信到这里你已经安装好这两个包了,简单的将他们引入到我们的js文件中,在刚才的介绍原理部分我有提到过其实我们真正需要了解并书写的是reducer和mapStateToProp、mapDispatchToProps这三个函数、其余的我们的官方已经写入到模块里,并不需要我们自己去实现。
2.1reducer实现:
我们通常另写一个reducer.js文件在这里表明我们要做的action所对应的状态修改,这个函数的实现格式和我们刚才自己写redux框架是一样的:需要初始化一个默认state,并利用switch语句表明自己要完成的action,并直接将状态返回。
var appReducer=function(state,action)
{
if(!state)
{
return{
count:0
}
}
switch(action.type)
{
case 'ADD_ONE':
return{
...state,
count:state.count+1
}
default:
return state
}
}
2.2App.js中store的创建:
我们将刚才写好的reducer函数引入进来,并利用该函数生成一个新的store
var store=createStore(appReducer)
2.3实现mapStateToProps、mapDispatchToProps
我们知道我们需要将组件经过connect的包装,才能实现分发功能以及状态改变。
var mapStateToProps=function(state,ownProps)
{
return{
count:state.count
}
}
var mapDispatchToProps=function(dispatch,ownProps)
{
return{
add_one:function()
{
//相当于为dispatch传递了一个ADD_ONE的action
dispatch({type:'ADD_ONE'})
}
}
}
(redux不修改只返回)
写好这几个函数后,我们要利用connect对App初始化,可以有自己的分发功能的高阶组件:
App=connect(mapStateToProps,mapDispatchToProps)(App)
2.4Provider封装App
但是,和我们最终是想让它成为一个可以传递store的组件,所以我们要利用Provider将它在此包装一下,这样它的子组件就都可以利用store了。
2.5总结
通过该demo可以了解到,coonnect、provider、createstore都是以实现好的框架;实际开发中我们关注的地方为reducer的实现、以及mapStateToProps、mapDispatchToProps的实现。
3、redux高级技巧
3.1
action封装
将action放到action.js中
我们如果直接在dispatch中传入对象类型的action表明我们要进行的动作很麻烦,并且容易出错,所以官方推荐通常是封装一个函数指明我们的type字段,引出,然后调用(在App和reducer中)即可
export const ADD_ONE='ADD_ONE'
export function add_one()
{
return{
type:ADD_ONE
}
}
3.2组合reducer
我们来明确一下reducer作用:接受action,产生newState,那我们要写很多的switch case这在大程序中实现是不太可能的,所以我们在设计中拆分reducer,在应用时将reducer组合到一起。
这里我们要引入一个模块:combineReducers
他可以将我们的各个action组合到一起,具体步骤如下:
新建一个reducers文件夹,里面包含index.js和各个与action有关的独立reducer,在index.js中:
import {combineReducers } from 'redux'
import reducer from './reducer'
// 这里的reducer为我们要运用得第一个reducer
export default combineReducers({reducer})
然后我们在src的ndex.js文件中,将该文件夹引入(没指明默认引入index.js文件)
然后不同的是我们在指明state的过程中,还有指明该state有关的reducer是哪一个
import appReducer from './reducers' //引入文件夹 默认引入index.js
var mapStateToProps=function(state,ownProps)
{
return{
count:state.reducer.count
}
}
4、react-redux实战案例
一、备忘录项目介绍
该项目由三部分构成:
1.输入框:输入待做的事
2.展示列表:展示所有的待做事
3.选择按钮:选择要展示的内容
好了 现在我们已经基本掌握了redux的原理,现在就要运用它去写实例从而巩固对该知识点的理解。
增加事件+改变事件状态的功能实现(在输入框中输入事件,点击提交展示到页面中)
根据功能我们来梳理一下实现的逻辑:
action:这里的第一个action为ADD_TODO 即提交一个事件,那这个action具体的功能是什么呢?就是返回一个newTodo状态到state当中。但我们为了方便后来的功能还可以为每个newTodo增加一个id属性。第二个action为TOGGLE_TODO 即改变一个事件状态,那这个action具体的功能是什么呢?就是返回一个id,表明点击的事件为哪个。
todoReducer:无疑,该reducer功能为接受ADD_TODO action和TOGGLE_TODO,我们知道reducer为返回状态那么ADD_TODO action,应该返回什么呢?答案是将state解构,和id以及newTodo放到一起。TOGGLE_TODO action:遍历newTodo数组,若数组某项id==点击id则将isDone变为!
准备工作完毕,我们正式构建Addtodo+todoList组件。
对于Addtodo组件:
mapStateToProps:该组件为一个button,不需要返回状态,直接返回一个{}。
mapDispatchToProps我们要将add_todo( id,newTodo)函数传入dispatch。
对于todoList组件:
mapStateToProps:我们要运用的为state中todoReducer的state.todoReducer(利用它来构建列表)
mapDispatchToProps我们要将toggle_todo(id)函数传入dispatch,利用该函数声明点击的事项id。
接下来渲染我们的AddTodo组件,该组件为一个表单中含有一个input和一个button按钮,在react当中我们知道input为一个受控组件,还是比较麻烦的对它处理,这里介绍一个新方法在表单的onSubmit事件当中,如果input.value!=none 则调用addTodo函数,而input我们可以在input标签当中设置ref(指标签自身)
然后渲染我们的TodoList组件,先声明来自this.props的各个变量,并为onClick事件挂载一个toggletodo函数,最后调用this.props.todo list遍历即可
并且要记得利用connect包装该组件,最后传入App中。
class AddTodo extends Component{
render()
{
const {addTodo} =this.props
return(
<div>
<form action="" onSubmit={(e)=>{
e.preventDefault()
if(this.input.value)
{
addTodo(Math.random(),this.input.value)
this.input.value=''
}
}}>
<input type="text"
ref={(node)=>{ //ref指标签本身
this.input=node
}}/>
<button type='submit' >提交</button>
</form>
{/*通过点击按钮,调用action进行dispatch */}
</div>
)
}
}
var mapStateToProps=function(){
return{}//不需要任何状态
}
var mapDispatchToProps=function(dispatch)
{
return{
addTodo: function(id,newTodo){
dispatch(add_todo( id,newTodo))
}
}
}
AddTodo = connect( mapStateToProps , mapDispatchToProps)(AddTodo)
export default AddTodo
todoList组件:
class TodoList extends Component {
render() {
const {list,toggleTodo,filter}=this.props
console.log(this.props)
var renderList=filterList(list,filter)
return (
<div>
<ul>{renderList.map((value, index) => {
return( <li
style={{textDecoration:value.isDone?'line-through':'none'}}
onClick={()=>{toggleTodo(value.id)}}
key={value.id} >
{value.todo}</li> )
})}
</ul>
</div>
);
}
}
var mapStateToProps = function (state, ownProps) {
console.log(state);
return {
list: state.todoReducer,
filter:state.filterReducer.filter
};
};
var mapDispatchToProps=function(dispatch,ownProps)
{
return{
toggleTodo:function(id)
{
dispatch(toggle_todo(id))
}
}
}
TodoList = connect(mapStateToProps,mapDispatchToProps)(TodoList);
二、项目完善(对于的事件状态展示对应得事件)
在上文我们已经成功得改变了事件的状态,但还没有将它利用并渲染,这里我们进行进一步的完善,思路为根据不同的状态将事件存入不同得数组,对应渲染即可:
老规矩先确定我们的action:
这里得action.type:SWITCH_LIST 它的功能为返回一个新的newFilter
那么在filterReducer:中实现筛选状态的动作,无可厚非,初始状态即为‘all’,
否则改变我们的filter为newFilter
然后构建我们FilterLink:由三个links组件构成,分别为‘all’、‘doing’、‘done’
class FiterLink extends Component
{
render()
{
return(
<div>
<Links filter='all' ></Links>
{' '}
<Links filter='doing' ></Links>
{' '}
<Links filter='done' ></Links>
</div>
)
}
}
所以关键在于links组件的实现:
**mapStateToProps:**不需要改变状态
mapDispatchToProps我们要将switch_list(newFilter)函数传入dispatch,利用该函数声明点击展示的是哪一项。
最后来渲染我们的Links组件,为a标签挂在onclick事件,并调用switch_list(filter)函数
class Links extends Component
{
render()
{
console.log(this.props)
const {filter,switch_list }=this.props
return(
<a href=""
onClick={(e)=>{
e.preventDefault()
switch_list(filter)}}
>{filter}</a>
)
}
}
Links =connect(function(){return{}},
function(dispatch){
return{
switch_list:function(newFilter)
{
dispatch(switch_list(newFilter))
}
}})(Links)
class FiterLink extends Component
{
render()
{
return(
<div>
<Links filter='all' ></Links>
{' '}
<Links filter='doing' ></Links>
{' '}
<Links filter='done' ></Links>
</div>
)
}
}
最后我们在todo_list组件定义filterList函数,确定渲染列表项:
var filterList=function(list,filter)
{
if(filter=='all')
{
return list
}
if(filter=='done')
{
return list.filter((todo)=>
{
return todo.isDone
})
}
if(filter=='doing')
{
return list.filter((todo)=>
{
return !todo.isDone
})
}
}
五、redux-middleware
1.定义及产生原因
类似于express的中间件,为解决异步问题产生。
2.作用
在不改变原本的dispatch的前提下,不断为dispatch增加新的功能
3.官方实现原理及实现初尝试
实例:如果我们想每次调用dispatch的时候都打印一下当前action以及状态有哪些该如何实现呢?
我们肯定是要对store.dispatch进行包装,并且应该是串联包装,那我们不用官方中间件应该怎么写呢?
var next=store.dispatch
store.dispatch=function(action)
{
console.log(action)
next(action)
console.log(store.getState())
}
可以看到我们实现的关键在于action、当前dispatch即next、和store。官方中间件的实现也是如此,原理如下:
那就试着用上面的实现方法实现一下吧~
var logger=store=>next=>action=>
{
console.log(action)
next(action)
console.log(store.getState())
}
var store=createStore(appReducer,applyMiddleware(logger))
五、redux-thunk
1.ajax异步处理
我们可以发现一个问题,当下的action执行完,总是立刻返回state,那ajax请求怎么办呢,按照现在的形式,我们无法做到等待一个ajax请求完成后,再更新状态,那么我们如何处理ajax请求?其实也很简单,就是我们将ajax请求后再执行一次dispatch(只有dispatch才会改变状态)
2.异步解决办法
①
②利用thunk中间件
我们知道目前的dispatch接受的参数都是对象类型,而我们用thunk中间件后,接受的参数便是function类型.
dispatch((dispatch,getState)=>{someAJAX})
那么我们就可以写一个函数实现:先发起一个dispatch表明要去加载数据,然后发起AJAX请求,最后再发起一个AJAX请求表明数据加载完成,然后将这个函数传给dispatch即可,例如:
var load_data=function(dispatch,getState)
// {
// dispatch({type:'LOAD_DATA'})
// var xhr=new XMLHttpRequest
// xhr.open('GET')
// xhr.send()
// xhr.onreadystatechange=()=>
// {
// if(xhr.readyState==4&&xhr.status==200)
// {
// var data=xhr.response
// dispatch({type:'DONE_LOAD',data:data})
// }
// }
// }
var store=createStore(appReducer,applyMiddleware(thunk))
至此我们已经支持了异步的AJAX,但通常情况下,都是利用闭包去升级我们的函数:
var load_data=(key)=>{
return dispatch=>
{
dispatch({type:'LOAD_DATA'})
var xhr=new XMLHttpRequest
xhr.open('GET','http://localhost:3000'+key)
xhr.send()
xhr.onreadystatechange=()=>
{
if(xhr.readyState==4&&xhr.status==200)
{
var data=xhr.response
dispatch({type:'DONE_LOAD',data:data})
}
}
}
}
3.总结redux-thunk用法
①引入redux-thunk
②在createStore中调用thunk
③使用dispatch传入函数类型action