在react中,组件与组件进行数据交互时,咱们能够用this.props在不一样组件之间传递数据,可是当开发一个大型应用时,用this.props将数据一层一层的传下去来进行组件之间的通讯就会变得很是的麻烦,这个时候,redux应运而生,redux就是用来解决开发庞大应用时的数据交互问题。react
可是,redux并不适用与全部场景,若是组件之间没有大量数据交互,组件不须要共享状态,或者应用比较简单的话,redux的加入反而会增长代码的复杂性。多交互,组件须要共享状态,才是redux的适用场景。redux
state是redux保存数据的地方,而且state只能有一个。有的地方讲store是保存数据的地方,可是我更倾向于将store理解成一个装着获取state(getState)、操做state(dispatch)和监听state(subscribe)的方法的对象。具体什么是store,且听下文分解。数组
redux规定,一个state对应一个view,知道state就知道view,知道view就知道state,state驱动着页面(view),这点跟react的思想也是同样的,即:数据驱动页面。闭包
咱们是不能直接操做state的,咱们只能接触到view,action就是操做view后,view发出的通知,告诉state要改变了,具体怎么改变取决于action的内容。ide
action是一个对象,它必需要含有一个type属性,用来区别不一样类型的action,下面是一个action的例子:函数
reducer是一个函数,它接受2个参数,一个是state,一个是action,而后根据action的内容对state做出修改,并返回一个新的state,下面是一个例子:ui
注意:参数state有一个默认值,这个默认值会做为state的初始值(等下会解释为何).this
reducer必须是一个纯函数,所谓纯函数就是,相同的输入一定获得相同的输出,就像高中数学的函数同样,一个x只能对应一个y,若是一个x对应2个y那么就不能称之为函数。spa
reducer函数不用直接使用,它会在dispatch函数中调用。翻译
讲了这么多,终于要讲store了
store跟state同样,只能有一个,咱们要引入redux中的createStore来建立store,以下:
import { createStore } from 'redux';
const store = createStore(fnc);
复制代码
注意:createStore接受一个函数做为参数
下面我引用了阮一峰老师的一篇文章中的一段:
首先,咱们能够看到state的声明,就是一普普统统的变量,再看getState函数,功能很简单,就是直接返回state。
接下来是dispatch,它接受一个action做为参数,而后在内部执行reducer函数,返回一个新的state,咱们先不看下面的 listeners.forEach(listener => listener()); reducer函数是经过createStore函数的参数传递进来的,而后在dispatch内部调用,dispatch是createStore函数的返回对象中的三个函数之一,为何不直接将reducer暴露出去而是选择将其间接在dispatch中调用而后将dispatch暴露出去呢,个人理解是:reducer要接受state做为参数,若是直接将reducer暴露出去的话,那么state势必也要暴露出去,这样的话redux的稳定性就会大幅下降,这种方法是不可取的,因此redux的做者选择间接调用reducer,而不是直接将其暴露出去。 dispatch的意思是:派遣,迅速执行,dispatch函数顾名思义,就是获得一个action指令,当即处理。
上面我讲了,reducer函数的默认值会做为state的初始值,为何呢?能够看到,createStore函数在return以前,调用了一次dispatch,传了一个空对象,咱们进入函数内部来分析一下此次调用的过程,此次咱们依然忽略 listeners.forEach(listener => listener()); 这个语句,以下:
(1)state声明时并无赋值,这个时候它是undefined。
(2) action为{},它并无type值,这个时候reducer函数的调用至关于: reducer(undefined。 ,{}),形参state接受到了一个undefined,这个时候它就会使用默认值,也就是defaultState。
(3)由于action是空对象,没有type值,那么switch匹配不到相应的case,进入到default,返回原state对象,也就是defaultState。这就是为何reducer函数的默认值会做为state的初始值。
最后是subscribe函数,在createStore函数开头除了声明了state,还声明了一个listeners数组,subscribe函数接受一个listener,listener是一个函数,它会被push到listeners数组里面,上面被我忽略了2次的 listeners.forEach(listener => listener()); 终于要拿出来了,这个语句的做用就是将listeners数组里面的每个函数,也就是listener都执行一次。那为何这句话要放在dispatch中执行呢?显然,listener的意思是监听者,监听谁?固然是state,而只有dispatch才能引发state的改变,因此要将这个语句放在dispatch函数里面,每次dispatch后state会变化,同时执行全部listener函数。
subscribe翻译过来是订阅,咱们订阅了state,每次state改变了,都会让咱们收到消息并做出回应(执行全部listener函数),就像咱们订阅B站UP主同样,UP主每次发布新视频,咱们都能获得消息,而后本身选择看或者不看。
这里尚未结束,咱们能够看到subscribe函数还返回了一个函数,这个函数执行后会将当前listener在listeners数组中移除掉,这个动做就是unsubscribe,也就是取消订阅。取消订阅后,state再更新,就不会执行相应的操做了。
来看个例子:
从上面那张图来看,能够发现,createStore的核心原理就是JS的闭包,将state在闭包中声明,而后将获取、修改、监听state的三个函数放在一个对象里面暴露出去,这个对象就是store。将store对象传到不一样的地方,就能在不一样的地方共享同一个state。这就是redux想给咱们的东西了。
能够看到,redux的流程是单向的。
开发大型项目的时候,一个state多是巨大的,若是只用一个reducer来处理state的话,那么这个reducer将是极其复杂的,这样的话,是否是能够经过一种方法,将reducer拆分红多个小reducer,负责state不一样的reducer区域呢?
Redux 提供了一个combineReducers方法,咱们从redux中引入它。以下:
import { combineReducers } from 'redux';
复制代码
它怎么用呢?下面是一个例子:
let rootReducer = combineReducers({
todos:todosReducer,
counter:counterReducer,
})
复制代码
combineReducers函数接受一个对象,对象里面是一些小的reducer函数,返回值是一个大的reducer,顾名思义,就是将reducer combine起来。
combineReducers函数使用了一些小技巧,让咱们能够分开写reducer函数,以应对大型项目的庞大的state,可是它的基本原理仍是不变的,它依然是一个reducer函数,操做起来跟普通的reducer函数也是如出一辙的,须要接受2个参数,一个是state,一个是action,而且返回一个新的state。
下面来说一下它的原理,依然引用一下阮一峰老师的的内容。(他讲的真的是太好了)
执行combineReducers函数后,返回的是一个这样的函数,我把它赋给rootReducer,并从上面那幅图中分离出来,以便分析,以下:
咱们来分析一下它的返回值,我把它从图中分离出来,以便分析。
注意,不要觉得传入一个rootReducer传入一个action只会在对应的小reducer的switch中判断,事实是:它会在全部的小reducer中判断,这也是为何,我说combineReducers返回的大reducer跟普通的reducer没有什么区别的缘由之一。
还有一个缘由,那就是小reducer函数中的state参数的默认值会看成对应的state区域的初始值,由于将大reducer函数最终仍是要看成createStore函数的参数,而后createStore函数内部会执行一次dispatch,给state初始化(能够看上面store部分dispatch的解析),在这个过程当中,全部的小reducer都会被执行一次,并给各个对应的state区域初始化,最后就完成了state的初始化。来看个例子:
综上所述,combineReducers函数让咱们能够将reducer拆开来写,可是最后的reducer跟普通的reducer没有任何区别。
上面咱们已经生成了store,接下来的问题就是:
- 怎么在react项目的各个地方都能拿到store。
- 拿到以后怎么对store进行操做。
这里我要讲一个别人写好的库:react-redux,这个库使用起来比直接使用redux方便,可是它有一些本身的规范,咱们要按照它的规范来使用。
Provider组件让store组件在任何位置都能被取到,它的原理是react的context上下文对象,这里不作解释。
那Provider组件怎么使用呢?很简单,先将它引入:
import { Provider } from 'react-redux'
复制代码
而后将根组件用Provider包起来,像这样:
render(
<Provider store={store}> <App /> </Provider>,
document.getElementById('root')
)
复制代码
不必定要包着App组件,你也能够:
class App extends React.Component {
render(){
return (
<Provider store={store}> <div className="App"> <Header /> <Content /> </div> </Provider>
);
}
}
复制代码
反正要记住一点,只有被Provider包起来的组件,才能够拿到store,因此咱们尽可能包住整个react项目的根组件。
别忘了那句store={store},这是将store传下去的要点,通常咱们用createStore生成了store,而后将store赋值给同名变量store,这样就能保证Provider里面的组件能拿到store了。
咱们使用了Provider组件将store"全局化"以后,如今就要开始使用store了,怎么使用呢?直接store.getState(),或者store.dispatch()吗?确定没这么简单,直接这样用react会给你报错的。
react-redux这个库提供了connect方法来创建组件与store之间的通讯。要使用connect,首先咱们要引入它:
import { connect } from 'react-redux'
复制代码
而后咱们要用connect将要输出的组件包起来,也就是说,咱们不能直接export组件了,如今要export的是通过connect包装事后的组件,咱们来看一下二者的区别:
假如咱们要输出一个Header组件,大体是这样:
class Header extents Component{
//.....
//...
}
export default Header
复制代码
使用了connect后,要这样:
class Header extents Component{
//.....
//...
}
export default connect()(Header)
复制代码
看到区别了吗,上面讲了咱们react-redux这个库有本身的规则,这就是它的规则之一,你这个组件想要用store,就必需要先用connect将其包起来,或者理解为将其与store链接起来。connect就是一张通行证,想要用store是吧,先给我用connect包起来,否则不让你用。
上面咱们看到了store与组件是如何联系起来的,connect发挥了它的做用,可是,仅仅联系起来是不够的,咱们尚未定义操做逻辑,即:如何对state进行操做?事实上上面的connect函数咱们没有写完,它是有2个参数的:mapStateToProps和mapDispatchToProps。它的完整写法应该是:
export default connect(mapStateToProps,mapDispatchToProps)(Header)
复制代码
这个函数的名字取得很是的语义化,将state映射到props,它的写法是这样的:
class Header extents Component{
render(){
let lists = this.props.lists;
return(
//....
//....
)
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Header)
复制代码
关于mapStateToProps,咱们要知道的是:
(1) 咱们能够根据本身的须要随便return什么,mapStateToProps的功能就是将state映射到props上面,至于怎么映射,取决于咱们本身,若是咱们这样写:
const mapStateToProps = (state) => {
return {
state : state,
lists : state.lists,
userName : state.userName,
}
}
复制代码
那么props上就会有state、lists、userName:
class Header extents Component{
render(){
let {state , lists , userName} = this.props;
return(
//....
//....
)
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Header)
复制代码
(2)mapStateToProps会订阅store,一旦state发生改变,mapStateToProps会自动执行,从新计算UI组件的参数,触发UI组件的从新渲染,这跟react的响应式的特色相同。
这个函数跟mapStateToProps是同样的,不过此次映射的是dispatch,以前映射的是state,让咱们来直接看看它是怎么写的吧:
mapDispatchToProps定义了UI组件怎么发出action。
mapDispatchToProps不必定要写成函数,它也能够写成对象的形式,这里不作深究,知道就行了。
(1) 咱们从state里面取到了lists,而后在render函数里面将它用map函数转化成一个li数组,再放在return里面将其渲染出来。
(2)咱们定义了一个input标签,它是一个受控组件,它收到this.state的控制(注意不要搞混了这几个state)
(3)咱们定义了一个button,给它添加了一个onClick函数,在它的onClick函数中,调用了myOnClick函数,它会调用dispatch函数对state进行修改,react-redux监听到了state的变化后,再次执行mapStateToProps,从新计算lists的值,并引发UI组件的从新渲染。这个时候,ul列表里面会多一条li。
上面讲了react-redux的一些规则,它还有一些规则,UI组件与容器组件就是其中之一。
简单的来讲UI组件就是咱们本身写的组件,好比上面的Header,它负责UI的呈现,而容器组件负责数据管理和逻辑,它不用本身写,connect函数帮咱们生成了它。
最后export出去的,就是一个UI组件和容器组件的结合体。它包含着咱们本身写的负责视觉层(view)的Header,以及负责与store对接、进行数据交互、状态管理的容器组件。