zoukankan      html  css  js  c++  java
  • React 中的 Redux 详解:

    Redux 安装指令是:

    >  yarn add  redux  react-redux

    Redux 中的核心是:

    store 是应用的状态管理中心,保存着是应用的状态(state),当收到状态的更新时,会触发视觉组件进行更新。

    container 是视觉组件的容器,负责把传入的状态变量渲染成视觉组件,在浏览器显示出来。

    reducer 是动作(action)的处理中心, 负责处理各种动作并产生新的状态(state),返回给store。

    Redux中的工作流程是:

    1. 使用函数createStore创建store数据点
    2. 创建Reducer。它要改变的组件,它获取state和action,生成新的state
    3. subscribe监听每次修改情况
    4. dispatch执行,reducer(currentState,action)处理当前dispatch后的传入的action.type并返回给currentState处理后的state,通过currentListeners.forEach(v=>v())执行监听函数,并最后返回当前 action状态

    实现Redux:

     getState实现

    1 export const createStore = () => {    
    2     let currentState = {}       // 公共状态    
    3     function getState() {       // getter        
    4         return currentState    
    5     }    
    6     function dispatch() {}      // setter    
    7     function subscribe() {}     // 发布订阅    
    8     return { getState, dispatch, subscribe }
    9 }

    dispatch实现:

    >

    但是dispatch()的实现我们得思考一下,经过上面的分析,我们的目标是有条件地、具名地修改store的数据,那么我们要如何实现这两点呢?我们已经知道,在使用dispatch的时候,我们会给dispatch()传入一个action对象,这个对象包括我们要修改的state以及这个操作的名字(actionType),根据type的不同,store会修改对应的state。我们这里也沿用这种设计:
    >
     1 export const createStore = () => {    
     2     let currentState = {}    
     3     function getState() {        
     4         return currentState    
     5     }    
     6     function dispatch(action) {        
     7         switch (action.type) {            
     8             case 'plus':            
     9             currentState = {                 
    10                 ...state,                 
    11                 count: currentState.count + 1            
    12             }        
    13         }    
    14     }    
    15     function subscribe() {}    
    16     return { getState, subscribe, dispatch }
    17 }

    >

    我们把对actionType的判断写在了dispatch中,这样显得很臃肿,也很笨拙,于是我们想到把这部分修改state的规则抽离出来放到外面,这就是我们熟悉的reducer。我们修改一下代码,让reducer从外部传入:

    >

     1 import { reducer } from './reducer'
     2 export const createStore = (reducer) => {    
     3     let currentState = {}     
     4     function getState() {        
     5         return currentState    
     6     }    
     7     function dispatch(action) {         
     8         currentState = reducer(currentState, action)  
     9     }    
    10     function subscribe() {}    
    11     return { getState, dispatch, subscribe }
    12 }

    >

    然后我们创建一个reducer.js文件,写我们的reducer

     >

     1 //reducer.js
     2 const initialState = {    
     3     count: 0
     4 }
     5 export function reducer(state = initialState, action) {    
     6     switch(action.type) {      
     7         case 'plus':        
     8         return {            
     9             ...state,                    
    10             count: state.count + 1        
    11         }      
    12         case 'subtract':        
    13         return {            
    14             ...state,            
    15             count: state.count - 1        
    16         }      
    17         default:        
    18         return initialState    
    19     }
    20 }

     >

    代码写到这里,我们可以验证一下getStatedispatch

    >

     1 //store.js
     2 import { reducer } from './reducer'
     3 export const createStore = (reducer) => {    
     4     let currentState = {}        
     5     function getState() {                
     6         return currentState        
     7     }        
     8     function dispatch(action) {                
     9         currentState = reducer(currentState, action)  
    10     }        
    11     function subscribe() {}        
    12     return { getState, subscribe, dispatch }
    13 }
    14 
    15 const store = createStore(reducer)  //创建store
    16 store.dispatch({ type: 'plus' })    //执行加法操作,给count加1
    17 console.log(store.getState())       //获取state

    >

    运行代码,我们会发现,打印得到的state是:{ count: NaN },这是由于store里初始数据为空,state.count + 1实际上是underfind+1,输出了NaN,所以我们得先进行store数据初始化,我们在执行dispatch({ type: 'plus' })之前先进行一次初始化的dispatch,这个dispatch的actionType可以随便填,只要不和已有的type重复,让reducer里的switch能走到default去初始化store就行了:
    >
     1 import { reducer } from './reducer'
     2 export const createStore = (reducer) => {        
     3     let currentState = {}        
     4     function getState() {                
     5         return currentState        
     6     }        
     7     function dispatch(action) {                
     8         currentState = reducer(currentState, action)        
     9     }        
    10     function subscribe() {}    
    11     dispatch({ type: '@@REDUX_INIT' })  //初始化store数据        
    12     return { getState, subscribe, dispatch }
    13 }
    14 
    15 const store = createStore(reducer)      //创建store
    16 store.dispatch({ type: 'plus' })        //执行加法操作,给count加1
    17 console.log(store.getState())           //获取state

    >

    运行代码,我们就能打印到的正确的state:{ count: 1 }

    >

    subscribe实现

     >

    尽管我们已经能够存取公用state,但store的变化并不会直接引起视图的更新,我们需要监听store的变化,这里我们应用一个设计模式——观察者模式,观察者模式被广泛运用于监听事件实现(有些地方写的是发布订阅模式,但我个人认为这里称为观察者模式更准确,有关观察者和发布订阅的区别,讨论有很多,读者可以搜一下)

    所谓观察者模式,概念也很简单:观察者监听被观察者的变化,被观察者发生改变时,通知所有的观察者。那么我们如何实现这种监听-通知的功能呢,为了照顾还不熟悉观察者模式实现的同学,我们先跳出redux,写一段简单的观察者模式实现代码:

    >

     1 //观察者
     2 class Observer {    
     3     constructor (fn) {      
     4         this.update = fn    
     5     }
     6 }
     7 //被观察者
     8 class Subject {    
     9     constructor() {        
    10         this.observers = []          //观察者队列    
    11     }    
    12     addObserver(observer) {          
    13         this.observers.push(observer)//往观察者队列添加观察者    
    14     }    
    15     notify() {                       //通知所有观察者,实际上是把观察者的update()都执行了一遍       
    16         this.observers.forEach(observer => {        
    17             observer.update()            //依次取出观察者,并执行观察者的update方法        
    18         })    
    19     }
    20 }
    21 
    22 var subject = new Subject()       //被观察者
    23 const update = () => {console.log('被观察者发出通知')}  //收到广播时要执行的方法
    24 var ob1 = new Observer(update)    //观察者1
    25 var ob2 = new Observer(update)    //观察者2
    26 subject.addObserver(ob1)          //观察者1订阅subject的通知
    27 subject.addObserver(ob2)          //观察者2订阅subject的通知
    28 subject.notify()                  //发出广播,执行所有观察者的update方法

    >

    解释一下上面的代码:观察者对象有一个update方法(收到通知后要执行的方法),我们想要在被观察者发出通知后,执行该方法;被观察者拥有addObservernotify方法,addObserver用于收集观察者,其实就是将观察者们的update方法加入一个队列,而当notify被执行的时候,就从队列中取出所有观察者的update方法并执行,这样就实现了通知的功能。我们redux的监听-通知功能也将按照这种实现思路来实现subscribe:

    有了上面观察者模式的例子,subscribe的实现应该很好理解,这里把dispatch和notify做了合并,我们每次dispatch,都进行广播,通知组件store的状态发生了变更。

    >

     1 import { reducer } from './reducer'
     2 export const createStore = (reducer) => {        
     3     let currentState = {}        
     4     let observers = []             //观察者队列        
     5     function getState() {                
     6         return currentState        
     7     }        
     8     function dispatch(action) {                
     9         currentState = reducer(currentState, action)                
    10         observers.forEach(fn => fn())        
    11     }        
    12     function subscribe(fn) {                
    13         observers.push(fn)        
    14     }        
    15     dispatch({ type: '@@REDUX_INIT' })  //初始化store数据        
    16     return { getState, subscribe, dispatch }
    17 }

    >

    我们来试一下这个subscribe(这里就不创建组件再引入store再subscribe了,直接在store.js中模拟一下两个组件使用subscribe订阅store变化):

    >

     1 import { reducer } from './reducer'
     2 export const createStore = (reducer) => {        
     3     let currentState = {}        
     4     let observers = []             //观察者队列        
     5     function getState() {                
     6         return currentState        
     7     }        
     8     function dispatch(action) {                
     9         currentState = reducer(currentState, action)                
    10         observers.forEach(fn => fn())        
    11     }        
    12     function subscribe(fn) {                
    13         observers.push(fn)        
    14     }            
    15     dispatch({ type: '@@REDUX_INIT' })  //初始化store数据        
    16     return { getState, subscribe, dispatch }
    17 }
    18 
    19 const store = createStore(reducer)       //创建store
    20 store.subscribe(() => { console.log('组件1收到store的通知') })
    21 store.subscribe(() => { console.log('组件2收到store的通知') })
    22 store.dispatch({ type: 'plus' })         //执行dispatch,触发store的通知

    >

    控制台成功输出store.subscribe()传入的回调的执行结果:

    >

    >

    到这里,一个简单的redux就已经完成,在redux真正的源码中还加入了入参校验等细节,但总体思路和上面的基本相同。

    我们已经可以在组件里引入store进行状态的存取以及订阅store变化,数一下,正好十行代码(`∀´)Ψ。但是我们看一眼右边的进度条,就会发现事情并不简单,篇幅到这里才过了三分之一。尽管说我们已经实现了redux,但coder们并不满足于此,我们在使用store时,需要在每个组件中引入store,然后getState,然后dispatch,还有subscribe,代码比较冗余,我们需要合并一些重复操作,而其中一种简化合并的方案,就是我们熟悉的react-redux

    >

     react-redux的实现:

    ----------------------------------------------------------------------------------------

    >

    上文我们说到,一个组件如果想从store存取公用状态,需要进行四步操作:import引入store、getState获取状态、dispatch修改状态、subscribe订阅更新,代码相对冗余,我们想要合并一些重复的操作,而react-redux就提供了一种合并操作的方案:react-redux提供Providerconnect两个API,Provider将store放进this.context里,省去了import这一步,connect将getState、dispatch合并进了this.props,并自动订阅更新,简化了另外三步,下面我们来看一下如何实现这两个API:

     >

    Provider实现:

    >

    我们先从比较简单的Provider开始实现,Provider是一个组件,接收store并放进全局的context对象,至于为什么要放进context,后面我们实现connect的时候就会明白。下面我们创建Provider组件,并把store放进context里
    >


     1 import React from 'react'
     2 import PropTypes from 'prop-types'
     3 export class Provider extends React.Component {  
     4     // 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法  
     5     static childContextTypes = {    
     6         store: PropTypes.object  
     7     } 
     8 
     9     // 实现getChildContext方法,返回context对象,也是固定写法  
    10     getChildContext() {    
    11         return { store: this.store }  
    12     }  
    13 
    14     constructor(props, context) {    
    15         super(props, context)    
    16         this.store = props.store  
    17     }  
    18 
    19     // 渲染被Provider包裹的组件  
    20     render() {    
    21         return this.props.children  
    22     }
    23 }

    >

    完成Provider后,我们就能在组件中通过this.context.store这样的形式取到store,不需要再单独import store。

    >

    connect实现:

    >

    下面我们来思考一下如何实现connect,我们先回顾一下connect的使用方法:

    我们已经知道,connect接收mapStateToProps、mapDispatchToProps两个方法,然后返回一个高阶函数,这个高阶函数接收一个组件,返回一个高阶组件(其实就是给传入的组件增加一些属性和功能)connect根据传入的map,将state和dispatch(action)挂载子组件的props上,我们直接放出connect的实现代码,寥寥几行,并不复杂:

    >

     1 export function connect(mapStateToProps, mapDispatchToProps) {    
     2     return function(Component) {      
     3         class Connect extends React.Component {        
     4             componentDidMount() {          
     5                 //从context获取store并订阅更新          
     6                 this.context.store.subscribe(this.handleStoreChange.bind(this));        
     7             }       
     8             handleStoreChange() {          
     9                 // 触发更新          
    10                 // 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新          
    11                 this.forceUpdate()        
    12             }        
    13             render() {          
    14                 return (            
    15                     <Component              
    16                         // 传入该组件的props,需要由connect这个高阶组件原样传回原组件              
    17                         { ...this.props }              
    18                         // 根据mapStateToProps把state挂到this.props上              
    19                         { ...mapStateToProps(this.context.store.getState()) }               
    20                         // 根据mapDispatchToProps把dispatch(action)挂到this.props上              
    21                         { ...mapDispatchToProps(this.context.store.dispatch) }                 
    22                     />              
    23                 )        
    24             }      
    25         }      
    26         //接收context的固定写法      
    27         Connect.contextTypes = {        
    28             store: PropTypes.object      
    29         }      
    30         return Connect    
    31     }
    32 }

    >

    写完了connect的代码,我们有两点需要解释一下:

    1. Provider的意义:我们审视一下connect的代码,其实context不过是给connect提供了获取store的途径,我们在connect中直接import store完全可以取代context。那么Provider存在的意义是什么,其实笔者也想过一阵子,后来才想起...上面这个connect是自己写的,当然可以直接import store,但react-redux的connect是封装的,对外只提供api,所以需要让Provider传入store。

    2. connect中的装饰器模式:回顾一下connect的调用方式:connect(mapStateToProps, mapDispatchToProps)(App)其实connect完全可以把App跟着mapStateToProps一起传进去,看似没必要return一个函数再传入App,为什么react-redux要这样设计,react-redux作为一个被广泛使用的模块,其设计肯定有它的深意。

    其实connect这种设计,是装饰器模式的实现,所谓装饰器模式,简单地说就是对类的一个包装,动态地拓展类的功能。connect以及React中的高阶组件(HoC)都是这一模式的实现

    >
     
     1 //普通connect使用
     2 class App extends React.Component{
     3     render(){
     4         return <div>hello</div>
     5     }
     6 }
     7 function mapStateToProps(state){
     8     return state.main
     9 }
    10 function mapDispatchToProps(dispatch){
    11     return bindActionCreators(action,dispatch)
    12 }
    13 export default connect(mapStateToProps,mapDispatchToProps)(App)
     1 //使用装饰器简化
     2 @connect(
     3   state=>state.main,
     4   dispatch=>bindActionCreators(action,dispatch)
     5 )
     6 class App extends React.Component{
     7     render(){
     8         return <div>hello</div>
     9     }
    10 }

    >

    写完了react-redux,我们可以写个demo来测试一下:使用create-react-app创建一个项目,删掉无用的文件,并创建store.js、reducer.js、react-redux.js来分别写我们redux和react-redux的代码,index.js是项目的入口文件,在App.js中我们简单的写一个计数器,点击按钮就派发一个dispatch,让store中的count加一,页面上显示这个count。最后文件目录和代码如下:
    >
     
     
     1 // store.js
     2 export const createStore = (reducer) => {    
     3     let currentState = {}    
     4     let observers = []             //观察者队列    
     5     function getState() {        
     6         return currentState    
     7     }    
     8     function dispatch(action) {        
     9         currentState = reducer(currentState, action)       
    10         observers.forEach(fn => fn())    
    11     }    
    12     function subscribe(fn) {        
    13         observers.push(fn)    
    14     }    
    15     dispatch({ type: '@@REDUX_INIT' }) //初始化store数据    
    16     return { getState, subscribe, dispatch }
    17 }
     1 //reducer.js
     2 const initialState = {    
     3     count: 0
     4 }
     5 
     6 export function reducer(state = initialState, action) {    
     7     switch(action.type) {      
     8         case 'plus':        
     9         return {            
    10             ...state,            
    11             count: state.count + 1        
    12         }      
    13         case 'subtract':        
    14         return {            
    15             ...state,            
    16             count: state.count - 1        
    17         }      
    18         default:        
    19         return initialState    
    20     }
    21 }
     
     1 //react-redux.js
     2 import React from 'react'
     3 import PropTypes from 'prop-types'
     4 export class Provider extends React.Component {  
     5     // 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法  
     6     static childContextTypes = {    
     7         store: PropTypes.object  
     8     }  
     9 
    10     // 实现getChildContext方法,返回context对象,也是固定写法  
    11     getChildContext() {    
    12         return { store: this.store }  
    13     }  
    14 
    15     constructor(props, context) {    
    16         super(props, context)    
    17         this.store = props.store  
    18     }  
    19 
    20     // 渲染被Provider包裹的组件  
    21     render() {    
    22         return this.props.children  
    23     }
    24 }
    25 
    26 export function connect(mapStateToProps, mapDispatchToProps) {    
    27     return function(Component) {      
    28     class Connect extends React.Component {        
    29         componentDidMount() {          //从context获取store并订阅更新          
    30             this.context.store.subscribe(this.handleStoreChange.bind(this));        
    31         }        
    32         handleStoreChange() {          
    33             // 触发更新          
    34             // 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新          
    35             this.forceUpdate()        
    36         }        
    37         render() {          
    38             return (            
    39                 <Component              
    40                     // 传入该组件的props,需要由connect这个高阶组件原样传回原组件              
    41                     { ...this.props }              
    42                     // 根据mapStateToProps把state挂到this.props上              
    43                     { ...mapStateToProps(this.context.store.getState()) }               
    44                     // 根据mapDispatchToProps把dispatch(action)挂到this.props上              
    45                     { ...mapDispatchToProps(this.context.store.dispatch) }             
    46                 />          
    47             )        
    48         }      
    49     }      
    50 
    51     //接收context的固定写法      
    52     Connect.contextTypes = {        
    53         store: PropTypes.object      
    54     }      
    55     return Connect    
    56     }
    57 }  
     1 //index.js
     2 import React from 'react'
     3 import ReactDOM from 'react-dom'
     4 import App from './App'
     5 import { Provider } from './react-redux'
     6 import { createStore } from './store'
     7 import { reducer } from './reducer'
     8 
     9 ReactDOM.render(   
    10     <Provider store={createStore(reducer)}>        
    11         <App />    
    12     </Provider>,     
    13     document.getElementById('root')
    14 );
     
     
     1 //App.js
     2 import React from 'react'
     3 import { connect } from './react-redux'
     4 
     5 const addCountAction = {  
     6     type: 'plus'
     7 }
     8 
     9 const mapStateToProps = state => {  
    10     return {      
    11         count: state.count  
    12     }
    13 }
    14 
    15 const mapDispatchToProps = dispatch => {  
    16     return {      
    17         addCount: () => {          
    18             dispatch(addCountAction)      
    19         }  
    20     }
    21 }
    22 
    23 class App extends React.Component {  
    24     render() {    
    25         return (      
    26             <div className="App">        
    27                 { this.props.count }        
    28                 <button onClick={ () => this.props.addCount() }>增加</button>      
    29             </div>    
    30         );  
    31     }
    32 }
    33 
    34 export default connect(mapStateToProps, mapDispatchToProps)(App)
    运行项目,点击增加按钮,能够正确的计数,OK大成功,我们整个redux、react-redux的流程就走通了
     

  • 相关阅读:
    ASP.NET MVC5+ 路由特性
    老李分享:大数据,数据库,数据仓库之间是什么关系
    老李分享:DBA
    老李提问:开源家族你认识几个
    米尔格伦连锁信实验
    老李分享:六度分隔理论
    三者关系
    老李分享:JDK,JRE,JVM区别与联系
    Linux简介与厂商版本下
    Linux简介与厂商版本上
  • 原文地址:https://www.cnblogs.com/yjzs/p/13337417.html
Copyright © 2011-2022 走看看