react之redux基本用法

在react中,组件与组件进行数据交互时,咱们能够用this.props在不一样组件之间传递数据,可是当开发一个大型应用时,用this.props将数据一层一层的传下去来进行组件之间的通讯就会变得很是的麻烦,这个时候,redux应运而生,redux就是用来解决开发庞大应用时的数据交互问题。react

可是,redux并不适用与全部场景,若是组件之间没有大量数据交互,组件不须要共享状态,或者应用比较简单的话,redux的加入反而会增长代码的复杂性。多交互,组件须要共享状态,才是redux的适用场景。redux

redux的几个基本概念

一. state

state是redux保存数据的地方,而且state只能有一个。有的地方讲store是保存数据的地方,可是我更倾向于将store理解成一个装着获取state(getState)、操做state(dispatch)和监听state(subscribe)的方法的对象。具体什么是store,且听下文分解。数组

redux规定,一个state对应一个view,知道state就知道view,知道view就知道state,state驱动着页面(view),这点跟react的思想也是同样的,即:数据驱动页面。闭包

二. action

咱们是不能直接操做state的,咱们只能接触到view,action就是操做view后,view发出的通知,告诉state要改变了,具体怎么改变取决于action的内容。ide

action是一个对象,它必需要含有一个type属性,用来区别不一样类型的action,下面是一个action的例子:函数

其余的属性能够根据本身的须要来定义。

三. reducer

reducer是一个函数,它接受2个参数,一个是state,一个是action,而后根据action的内容对state做出修改,并返回一个新的state,下面是一个例子:ui

注意:参数state有一个默认值,这个默认值会做为state的初始值(等下会解释为何).this

reducer必须是一个纯函数,所谓纯函数就是,相同的输入一定获得相同的输出,就像高中数学的函数同样,一个x只能对应一个y,若是一个x对应2个y那么就不能称之为函数。spa

reducer函数不用直接使用,它会在dispatch函数中调用。翻译

四. store

讲了这么多,终于要讲store了

store跟state同样,只能有一个,咱们要引入redux中的createStore来建立store,以下:

import { createStore } from 'redux';
const store = createStore(fnc);
复制代码

注意:createStore接受一个函数做为参数

下面我引用了阮一峰老师的一篇文章中的一段:

上面是一个createStore函数的简单实现。且听我娓娓道来。

  1. 首先,咱们能够看到state的声明,就是一普普统统的变量,再看getState函数,功能很简单,就是直接返回state。

  2. 接下来是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的初始值。

  3. 最后是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再更新,就不会执行相应的操做了。

    来看个例子:

subscribe接受了一个匿名函数,而后返回了一个值,赋给了unsubscribe。接下来每次执行dispatch,都会在控制台输出state。

咱们执行了unsubscribe函数,接下来每次执行dispatch函数以后,控制台都不会输出state了。

从上面那张图来看,能够发现,createStore的核心原理就是JS的闭包,将state在闭包中声明,而后将获取、修改、监听state的三个函数放在一个对象里面暴露出去,这个对象就是store。将store对象传到不一样的地方,就能在不一样的地方共享同一个state。这就是redux想给咱们的东西了。

五. redux的流程总结

  1. state决定了view
  2. 操做view发出action
  3. reducer函数接受action后修改state 这里有张图:

能够看到,redux的流程是单向的。

六. combineReducers

开发大型项目的时候,一个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的简单实现,它有一个reducers参数,跟上面讲的同样,这是一个对象,里面包含着全部小的reducer。且听我娓娓道来。

执行combineReducers函数后,返回的是一个这样的函数,我把它赋给rootReducer,并从上面那幅图中分离出来,以便分析,以下:

rootReducer即是执行combineReducers函数后返回的函数了。就像上面讲的,它依然是一个reducer,有一个state参数,一个action参数,而且返回一个新的state。

咱们来分析一下它的返回值,我把它从图中分离出来,以便分析。

如上图,它是这样一个对象,含义就是:给reducers对象中的全部小reducer(reducers[key])传入对应部分的state(state[key])和action而后执行,返回值放入nextState对象的对应部分(nextState[key]),最后将这个nextState对象返回。这个nextState对象就是我所说的新的state了,只不过是这种方式看起来更复杂一点而已。

注意,不要觉得传入一个rootReducer传入一个action只会在对应的小reducer的switch中判断,事实是:它会在全部的小reducer中判断,这也是为何,我说combineReducers返回的大reducer跟普通的reducer没有什么区别的缘由之一。

还有一个缘由,那就是小reducer函数中的state参数的默认值会看成对应的state区域的初始值,由于将大reducer函数最终仍是要看成createStore函数的参数,而后createStore函数内部会执行一次dispatch,给state初始化(能够看上面store部分dispatch的解析),在这个过程当中,全部的小reducer都会被执行一次,并给各个对应的state区域初始化,最后就完成了state的初始化。来看个例子:

我写了3个reducer函数而后将其放到一个对象里面做为combineReducers函数的参数,请注意这3个小reducer的state参数的默认值,组合完后咱们来输出一下state:

能够看到,就如上面所说,小reducer函数中的state参数的默认值会看成对应的state区域的初始值,一个reducer对应state对象的一个属性。 state对象中的属性的属性名与combineReducers函数参数中的几个属性的属性名相同,他们是一一对应的(能够看上面combineReducers函数的简单实现的分析)。这样一来,咱们就完成了分区域写reducer,而不用写在一块儿。

综上所述,combineReducers函数让咱们能够将reducer拆开来写,可是最后的reducer跟普通的reducer没有任何区别。

redux的使用

上面咱们已经生成了store,接下来的问题就是:

  1. 怎么在react项目的各个地方都能拿到store。
  2. 拿到以后怎么对store进行操做。

这里我要讲一个别人写好的库:react-redux,这个库使用起来比直接使用redux方便,可是它有一些本身的规范,咱们要按照它的规范来使用。

一. Provider组件

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了。

二. connect

咱们使用了Provider组件将store"全局化"以后,如今就要开始使用store了,怎么使用呢?直接store.getState(),或者store.dispatch()吗?确定没这么简单,直接这样用react会给你报错的。

2.1 connect的使用

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包起来,否则不让你用。

2.2 connect函数的参数

上面咱们看到了store与组件是如何联系起来的,connect发挥了它的做用,可是,仅仅联系起来是不够的,咱们尚未定义操做逻辑,即:如何对state进行操做?事实上上面的connect函数咱们没有写完,它是有2个参数的:mapStateToPropsmapDispatchToProps。它的完整写法应该是:

export default connect(mapStateToProps,mapDispatchToProps)(Header)
复制代码

2.3 mapStateToProps

这个函数的名字取得很是的语义化,将state映射到props,它的写法是这样的:

react-redux会将state做为参数传给它,而后这个函数会返回一个对象,这个对象的属性会被放到当前组件的props上面,而后咱们就能够拿到咱们想要的属性了,以上面的那张图为例,咱们能够这样使用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的响应式的特色相同。

2.4 mapDispatchToProps

这个函数跟mapStateToProps是同样的,不过此次映射的是dispatch,以前映射的是state,让咱们来直接看看它是怎么写的吧:

mapStateToProps拿到的是state,而mapDispatchToProps拿到的是dispatch,它跟mapStateToProps同样,依然须要返回一个对象,对象上面的每一个属性,都会被放到(映射)props上面,听起来是否是跟mapStateToProps如出一辙!区别就在于mapDispatchToProps获得的是函数,返回的对象里面的属性也是函数,这些函数能够调用dispatch以修改state,在组件里面咱们又能够调用这些函数,这样就完成了dispatch到props的映射。

mapDispatchToProps定义了UI组件怎么发出action。

mapDispatchToProps不必定要写成函数,它也能够写成对象的形式,这里不作深究,知道就行了。

2.5 让咱们来一睹全貌

分析:

(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。

2.6 UI组件与容器组件

上面讲了react-redux的一些规则,它还有一些规则,UI组件与容器组件就是其中之一。

简单的来讲UI组件就是咱们本身写的组件,好比上面的Header,它负责UI的呈现,而容器组件负责数据管理和逻辑,它不用本身写,connect函数帮咱们生成了它。

最后export出去的,就是一个UI组件和容器组件的结合体。它包含着咱们本身写的负责视觉层(view)的Header,以及负责与store对接、进行数据交互、状态管理的容器组件。

小结

一. 生成store

  1. reducer函数接受2个参数:state和action,返回新的state。
  2. createStore(reducer)生成store。
  3. store.dispatch(action)修改state。
  4. store.getState()获取state。
  5. store.subscribe()监听state。

二. store"全局化"

  1. Provider包住根组件。
  2. 原理:context上下文对象。

三. 组件与store进行交互

  1. 将组件用connect包一层。
  2. mapStateToProps将state映射到props。
  3. mapDispatchToProps将dispatch映射到props。
  4. UI组件与容器组件。
相关文章
相关标签/搜索