不管,在这篇文章之前,我默认你已经了解了一些react的基础知识。还有就是我也是自己刚开始学习redux而已,了解的并不深,所以后续欢迎大家补充。
那不管怎样。先来了解下redux为啥出现。
react
中数据是单向流动的,这意味着数据都是从一个方向流动下去,这样的好处也就是容易定位和纠错。一般而言数据都是从父元素流向子元素。通过props,从最顶层的父组件,一层层向下传递。
那么这里就出现了一个麻烦点,深层次的组件之间通讯困难,也就是说父组件要向他的孙子的孙子的。。。。孙子组件传递数据将是一件很麻烦的事,所以出现了redux.
redux
是一个状态管理容器,提供了整个应用中的唯一数据源store,这个数据源随处都可以被访问到,不需要依靠父子组件的传递。
这时有人可能也会说,这不就跟react中的context差不多么。【context作用:某个组件往自己的context里面放了某些状态,这个组件下面的所有子组件都可以直接访问这个状态而不需要通过中间件的传递。但是一个组件的context只有他的子组件能够访问。】
但是context
有一个不好的地方,一个组件设置了一个context,就相当于为其子组件设置了一个全局变量,从而导致了context里面的数据能够被随意修改,那么每个组件若都修改context,则会导致程序运行的不可预料,显然这不是我们想要的。
好了。扯回来继续讲redux,主题还是不能忘的。
先放张大图,建立起redux和react-redux之间的联系
从上图看出,redux有三大核心:Store, Action, Reducer
Store
: 是一个对象,整个应用的数据的存储的地方。
Action
: 是个对象,必须包含type这个属性。
Reducer
: 是一个函数,接受两个参数: 要修改的数据state和action对象。通常根据action.type来决定采用的操作,并对state进行修改,然后返回新的state。
举例:
在这个例子中,name属性是可选的,由action携带,最后传给reducer。这些可选的参数,被称为payload(载荷)。
reducer
接收一个修改前的state和一个action,然后通过判断action.type的方式来进行不同的操作。return语句的结果就是拼接一个新的state,达到更新state的目的。
记住state是一个对象,如果直接修改state是会对其他引用state的地方产生影响,所以,一般都是采用拼接的方式返回一个新的state。从而保证了reducer是一个纯函数的要求。
Redux核心之间的关系
action和reducer之间的关系是:我们触发action –> reducer来处理。且由上图可知,store通过dispatch方法触发action,reducer方法接受action对象作为参数,返回一个新的state对象。所以可以概括为:
store —> dispatch —> action <—reducer
Store
我们可以通过redux提供的createStore这个方法来创建一个Store,他接受对state进行处理的reducer作为参数。
store有三个方法:
getState:
用来获取store里面存储的数据[store是存储整个应用的数据,那么state是某个时间点的数据]
dispatch
: store里面的数据是不能直接进行修改的,只能通过触发action来进行修改,而dispatch就是用来触发action的
subscribe
: 每次数据变化后,我们都希望能在视图上看到相应的更改。那么我们就需要一个方法来自动调用render来进行更新视图。这个地方采用的是订阅者模式,使用subscribe方法进行相应的操作。
来个简易版的createStore
代码(核心差不多,不是源码),帮助我们来理解这三个函数
function createStore(reducer) {
let state = null
const listeners = [];
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = reducer(state,action) //覆盖原有的对象
listeners.forEach((listener) => listener())
}
dispatch({}) //初始化state
return { getState, dispatch, subscribe }
}
Reducer
主要是进行对state的操作。一般而言我们会对不同的数据通过编写不同的reducer来进行不同的操作。然后通过combineRecuder这个API把这个reducer集合到一个根reducer中。
//对name进行处理
export const changeName = (state={},action) => {
const {type} = action;
switch(type){
case CHANGE_NAME:
return {
...state,
{
name: action.name
}
}
default:
return state
}
}
//对picture进行处理
export const change_pic = (state={},action) => {
const {type} = action;
switch(type){
case CHANGE_PIC:
return {
...state,
{
picture: b.jpg
}
}
default:
return state
}
}
//生成root reducer
export default combineReducer({
name: change_name,
picture: change_pic
})
react-redux
redux
是独立的应用状态管理工具,是可以独立在react之外的。如果我们需要在react中运用它,就需要我们手动订阅store的状态变化,来对我们dereact组件进行更新。react-redux这个工具就是帮我们实现了这个功能。我们只需要对store进行处理,react组件就会有相应的变化。
connect[这一部分若不好理解可以先看下面的provider后再返回来看]
作用:连接React和Redux store。connect其实是一个高阶组件,传入一个组件,返回一个新的组件。
connect
接受mapStateToProps,mapDispatchProps作为参数。
mapStateToProps(state, ownProps)
mapStateToProps
会接受store.getState()的结果作为参数,也就是说传进去的state是整个应用的state,然后返回一个对象,这个对象是根据state生成的。
通过mapStateToProps,组件将会监听Redux store的变化,只要redux store发生改变,mapStoreToProps函数就会被调用。第二个参数ownProps为传递到组件的props,只要组件接收到新的props,mapStoreToProps也会被调用。
export const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
render() {
const {store} = this.context;
let stateProps = mapStateToProps(store.getState())
//把store.getState通过props的方式传递到组件中
return <WrappedComponent {...stateProps} />
}
}
return Connect
}
mapDispatchPoros
:接受dispatch作为参数,返回一个对象,这个对象内容会被connect当做是props参数传递给被包装的组件。可以在返回的对象内部定义一些函数,这个函数会用到dispatch来触发特定的action.
调整connect后
export connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount() {
const {store} = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps() {
const {store} = this.context;
let stateProps = mapStateToProps ? mapStateToProps(store.getState(),this.props) : {}
let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch,this.props) : {}
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render() {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
connect调用方法:
const connectApp = connect(mapStateToProps, mapDispatchToProps)(App);
通过connect,他对app组件进行进一步的包装,原结构就变成了
<Provider store = {store}>
<Connect>
<App />
</Connect>
< /Provider>
加了个Connect组件,将store和app关联了起来,也就是将store和connect关联了起来。
Provider
在使用connect中,mapStateToProps中我们使用了store的state,那么这个store是从哪里来的。
provider就是用来解决这个问题的。provider这个react组件就是为了将store挂载在Context中,然后我们写的真正的app里就可以获得store了,并使它的子孙在调用connect方法时,都能获取到store。
<Provider store = {store}>
<App />
< /Provider>
上关键源码帮助理解:
export default class provider extends Component {
//关键部分:将this.store加到了context里,子组件就可以通过context直接拿到store,不需要一级一级props传递下去
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props,context)
this.store = props.store
}
render() {
return Children.only(this.props.children)
}
....//省略代码
}
Provider.propTypes = {
//要求store对象里面的3个必须是function
store: storeShape.isRequired,
//要求我们形如<Provider store={store}><App /></Provider>,<App>必须是react element,还要求必须是单一element,
//这也是render本身限制的,要求children.Only()
children: PropTypes.element.isRequird
}
//和getChildContext一样,必须得加
//就像访问context属性是需要通过contextType指定可访问的元素一样,getChildContext指定的传递给子组件的属性需要先通过childContextTypes来指定,否则报错
Provider.childContextTypes ={
store: storeShape.isRequired
}
总结
dispatch
函数:专门负责数据的修改,所有对数据进行的操作,都必须通过dispatch函数,他接受一个action对象,action对象里必须有一个type字段来声明我们想要干什么。dispatch在switch里会识别这个type对象,能够识别出来的操作才会执行对这个state的修改。
reducer
函数:描述应用程序状态会根据action发生什么变化