redux简单实现

358 阅读5分钟

一、介绍

Why?

javascript需要管理state状态。

这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

React作为一个组件化开发框架,组件之间存在大量通信,有时这些通信跨越多个组件,或者多个组件之间共享一套数据,简单的父子组件间传值不能满足我们的需求,自然而然地,我们需要有一个地方存取和操作这些公共状态。

三大原则

2.1、单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

2.2、state是只读的

唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

2.3、使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers。

二、源码分析

重点:采用观察者设计模式设计state变化视图更新事件

1、存取状态createStore

export const createStore = () => {    
    let currentState = {}       // 公共状态    
    function getState() {}      // getter    
    function dispatch() {}      // setter    
    function subscribe() {}     // 发布订阅(观察者模式)    
    return { getState, dispatch, subscribe }
}
1.1、getState实现
export const createStore = () => {    
    let currentState = {}       // 公共状态    
    function getState() {       // getter        
        return currentState    
    }    
    function dispatch() {}      // setter    
    function subscribe() {}     // 发布订阅    
    return { getState, dispatch, subscribe }
}
1.2、dispatch实现

有条件地、具名地修改store的数据,我们需要给dispatch()传入一个action对象,这个对象包括我们要修改的state以及这个操作的名字(actionType)。根据type的不同,store会修改对应的state。

export const createStore = () => {    
    let currentState = {}    
    function getState() {        
        return currentState    
    }    
    function dispatch(action) {        
        switch (action.type) {            
            case 'plus':            
            currentState = {                 
                ...state,                 
                count: currentState.count + 1            
            }        
        }    
    }    
    function subscribe() {}    
    return { getState, subscribe, dispatch }
}

考虑代码格式优美, 修改state的规则抽离出来,建立我们熟悉的reducer。

import { reducer } from './reducer'
export const createStore = (reducer) => {    
    let currentState = {}     
    function getState() {        
        return currentState    
    }    
    function dispatch(action) {         
        currentState = reducer(currentState, action)  
    }    
    function subscribe() {}    
     dispatch({ type: '@@REDUX_INIT' })  //初始化store数据   
    return { getState, dispatch, subscribe }
}

从外部引入reducer.js

//reducer.js
const initialState = {    
    count: 0
}
export function reducer(state = initialState, action) {    
    switch(action.type) {      
        case 'plus':        
        return {            
            ...state,                    
            count: state.count + 1        
        }      
        case 'subtract':        
        return {            
            ...state,            
            count: state.count - 1        
        }      
        default:        
        return initialState    
    }
}
1.3、subscrib实现

已经能存取公用state,可并不会引起视图的更新,所以需要监听store的变化,这里应用了观察者设计模式实现监听事件。 第一步:创建观察者队列 第二步:将观察事件加入观察者队列 第三步:在目标发布改变时,执行观察者事件

import { reducer } from './reducer'
export const createStore = (reducer) => {        
    let currentState = {}        
    let observers = []             //观察者队列        
    function getState() {                
        return currentState        
    }        
    function dispatch(action) {                
        currentState = reducer(currentState, action)     
        // 当state发生变化时触发更新更新           
        observers.forEach(fn => fn())        
    }        
    function subscribe(fn) { 
        // 把观察者的fn触发事件加入队列               
        observers.push(fn)        
    }        
    dispatch({ type: '@@REDUX_INIT' })  //初始化store数据        
    return { getState, subscribe, dispatch }
}

可执行:
const store = createStore(reducer) //创建store 
store.subscribe(() => { console.log('组件1收到store的通知') }) 
store.subscribe(() => { console.log('组件2收到store的通知') }) 
store.dispatch({ type: 'plus' }) //执行dispatch,触发store的通知

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

三、react-redux

想从store存取公用状态,需要进行四步操作: import引入store、getState获取状态、dispatch修改状态、subscribe订阅更新 从而出现了代码相对冗余,我们想要合并一些重复的操作。

react-redux提供Provider和connect两个API: Provider将store放进this.context里,省去了import这一步 connect将getState、dispatch合并进了this.props,并自动订阅更新

2.1、 Provider

Provider是一个组件,接收store并放进全局的context对象,就能在组件中通过this.context.store这样的形式取到store,不需要再单独import store。

意义:connect是自己写的,当然可以直接import store,但react-redux的connect是封装的,对外只提供api,所以需要让Provider传入store。

2.2 connect

connect中的装饰器模式:回顾一下connect的调用方式:connect(mapStateToProps, mapDispatchToProps)(App)其实connect完全可以把App跟着mapStateToProps一起传进去,看似没必要return一个函数再传入App,为什么react-redux要这样设计,react-redux作为一个被广泛使用的模块,其设计肯定有它的深意。 其实connect这种设计,是装饰器模式的实现,所谓装饰器模式,简单地说就是对类的一个包装,动态地拓展类的功能。connect以及React中的高阶组件(HoC)都是这一模式的实现。除此之外,也有更直接的原因:这种设计能够兼容ES7的装饰器(Decorator),使得我们可以用@connect这样的方式来简化代码,有关@connect的使用可以看这篇<react-redux中connect的装饰器用法>:

export function connect(mapStateToProps, mapDispatchToProps) {    
    return function(Component) {      
    class Connect extends React.Component {        
        componentDidMount() {          //从context获取store并订阅更新          
            this.context.store.subscribe(this.handleStoreChange.bind(this));        
        }        
        handleStoreChange() {          
            // 触发更新          
            // 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新          
            this.forceUpdate()        
        }        
        render() {          
            return (            
                <Component              
                    // 传入该组件的props,需要由connect这个高阶组件原样传回原组件              
                    { ...this.props }              
                    // 根据mapStateToProps把state挂到this.props上              
                    { ...mapStateToProps(this.context.store.getState()) }               
                    // 根据mapDispatchToProps把dispatch(action)挂到this.props上              
                    { ...mapDispatchToProps(this.context.store.dispatch) }             
                />          
            )        
        }      
    }      

    //接收context的固定写法      
    Connect.contextTypes = {        
        store: PropTypes.object      
    }      
    return Connect    
    }
}  

收获

监听state变化运用了观察者设计模式,connect的封装采用了装饰器模式。

github代码地址