#自擼一個面向對象風格的redux

擼一個面向對象風格的redux

redux使用感覺

各類優勢就不說了,有兩點以爲不爽的地方:html

  1. 示例工程,文件夾太散。具體而言action,reduce,store,三者的對應關係很固定,分在三個文件夾下,實在太散。react

  2. redux的源碼使用了大量的函數式的pattern。本人不是很習慣(好像暴露了-_——!)git

因此,我就擼了一個面向對象風格的簡易版,代碼數少到不行,代碼在github上,(還附帶了react-reduxconnect的簡易版,不過方面的代碼註釋掉了,由於這是爲了配合react的功能,而redux的設計有獨立於react的初衷。github

在本人的一個demo項目中,使用下來,感受還行。demo地址 樣式上主要適配手機,pc上也能看,另外控制檯有列表頁加載數據的log,以下圖。demo的代碼暫時不開放,須要整理。
screen for consoleexpress

store的簡單實現(無中間件功能,後面會有的)

actionreduce必需要使用者提供.同redux同樣,要求每次reduce返回全新的state對象redux

dispatch的功能相對固定,另外提供一個subscribe方法,用來註冊監聽器,dispatch時,通知全部監聽者。api

根據這些要求,寫一個BaseStore。用戶須要繼承BaseStore,提供reduce函數react-router

export default class BaseStore{

    listeners=[]

    dispatch(action){
        this.state = this.reduce(action);
        this.listeners.forEach(listen=>{
            listen(action,this.getState())
        })
    }

    subscribe(listener){
        var index = this.listeners.length
        this.listeners.push(listener);

        return (index)=>{
            return ()=>{
                this.listeners.splice(index);
            }
        }(index)
    }


    reduce(action){
        throw new Error('subClass fo BaseStore should implement reducer function')
    }

    getState(){
        return this.state;
    }

}

使用的時候,只需繼承BaseStore,提供reduce函數就行app

import Immutable from 'immutable';
import {BaseStore} from 'zlux'

const ActionTypes={
    ADD:'ADD',
    DELETE_BY_ID:'DELETE_BY_ID',
    UPDATE:'UPDATE'
}

export default class SimpleStore extends BaseStore{
    __className ='PostListStore'

    state = new Immutable.List()

    reduce(action){
        if(action.type === ActionTypes.ADD){
            return state.push(action.payLoad)
        }
        if(action.type === ActionTypes.DELETE_BY_ID){
            let id = action.payLoad.id;
            return state.filter(item=>{return item.id !==id})
        }
        if(action.type == ActionTypes.UPDATE){
            var id = action.payLoad.id;
            var index = state.findIndex(item=>{return item.id == id});
            //if index == -1, 這裏不考慮update時,沒有相應item的狀況
            return state.set(index,action.payLoad)
        }

        return state; //注意:默認返回原state
    }
    
    //提供方便外接調用的方法
    add(payLoad){
        this.dispatch({
            type:ActionTypes.ADD,
            payLoad
        })
    }
    
    deleteById(payLoad){
        this.dispatch({
            type:ActionTypes.DELETE_BY_ID,
            payLoad
        })
    }
    
    update(payLoad){
        this.dispatch({
            type:ActionTypes.UPDATE,
            payLoad
        })
    }
}

而後像這樣使用:dom

var ss = new SimpleStore()
   ss.add({id:1,content:'hello'})
   ss.update({id:1,content:'world'})
   ss.deleteById({id:1})

每次調用dispatch,ss.getState()都會返回最新的state。由於使用了immutable的list,確保每次的state都是全新的。

中間件

關於redux的中間件的前因後果,官方文檔已經說得不能在詳細,文檔地址

這裏實現中間件,故意作的和express中的用法很像。以下:

simpleStore.use(
    function(next,action,store){
          console.log('before')
          next()
          console.log('after')
    },
    function(next){
        if(isLogin){
            next()
        }
        else{
            goToLogin();
        }
    }
)

稍微有些區別,

  1. 須要用中間件,要一次性的傳個use函數,屢次使用use,後面的會覆蓋前面的。

  2. 中間件函數中,參數只有next是必須的。actionstore 都是自動注入的。須要用就寫上,不須要用,就不用管。

  3. 暫時沒使用error first的模式。我的認爲state中徹底能夠體現錯誤信息。

    中間件實現的核心代碼以下:

    use(...fns){
        this.middlewareFns = fns;
        var _this =this;
        this.wrappedDispatch= this.middlewareFns.reduceRight((a,b)=>{
            return ()=>{
                b(a,_this.__curAction,_this)
            }
        },this.__dispatch)//__dispatch是原始的dispatch實現。
    }
    
    //改寫上面的dispatch實現。
    dispatch(action){
        this.__curAction = action
        this.wrappedDispatch();
    }

簡易connect

redux的實現都是不依賴於react,只要合適,任何環境下都能使用。爲了更好的和react配合使用,redux官方還提供了 react-redux.

react-redux裏的connect,幫助Redux store(由Provider傳入的合併過的store)的中狀態、方法和container進行綁定。

react-redux要求整個應用只有一個redux store,是由多個單純store(使用單純store來區分redux store)是合併而成。container能夠對應多個單純store。

使用者(也就是你)選取Redux store中的須要的statedispatch, 交由connect去綁定到react組件的props中。
指定了哪些 Store/State 屬性被映射到 React Componentprops,這個過程被稱爲 selector.
若是很是在乎性能,避免沒必要要的計算,還須要經過reselect這樣的庫。

而我這裏要求單純的store和container是一對多的關係。若是一個container須要多個store,那麼經過拆分container,而不是合併store。
這樣就要求container是最小的頁面構成單位,應該作到原子化。
這樣,container是否須要經過setState()來render組件,只要比較對應單純store的state,是否是同一個(還記得嗎,任何的狀態改變,都返回全新的state,因此這個判斷很是快)
只因此這樣要求,是由於好實現(找了那麼多借口,最後暴露了-_——!)。固然我好實現,使用者就得多寫點代碼,可是結構上,我我的以爲更清晰點。

不過由於我還沒寫個特別大型的項目,不知道拆分container而不是合併store,是否是能知足複雜應用的開發。

import {Component} from 'react';
import getStoreShape from './getStoreShape.js'

export default (WrappedContainer,store) => {
    return class extends Component{

        state={}

        constructor(props,context){
            super(props,context)

            this.state.props = store.getState();
            
            this.unsubscribe = store.subscribe(()=>{
                //直接判斷state的引用是否變化,shouldComponentUpdate都不須要了
                if(this.state.props == store.getState()){return ;}
                //這裏的props隨便取的名字,沒有意義
                //setState只是用於通知從新渲染。
                this.setState({
                    props:store.getState()
                })
            })
        }

        componentWillUnmount(){
            this.unsubscribe();
        }

        //context機制,TODO test case
        static childContextTypes={
            store:getStoreShape
        }

        getChildContext() {
            return { store: this.store };
        }

        getWrappedInstance() {
            return this.refs.wrappedInstance;
        }

        render(){
            return(
                <WrappedContainer ref='wrappedInstance' {...this.props} store={store} />
            )
        }
    }
}

總體是,就是一個 high order component

constructor裏,註冊了對store的監聽。這是一個單純store,直接比較state是否變化,就知道要不要重新render。

使用的話,貼點代碼:

//postListStore 是已經實例化的單純store,能夠被多個container公用。
const PostListElement = enhanceWithStore(PostListContainer,postListStore)
const PostDetailElement = enhanceWithStore(PostDetailContainer,postDetailStore)
//...
//使用react-router
React.render(
    (
        <Router>
            <Route path="/" component={App}>
                <IndexRoute  component={PostListElement}
                             onEnter={()=>{ utils.Scroll.restoreScroll('PostList') }}
                             onLeave={()=>{ utils.Scroll.saveScroll('PostList') }} />

                <Route path="posts/:postId" component={PostDetailElement} />
            </Route>
        </Router>
    ),
    document.getElementById('mount-dom')
)

其餘

核心的功能就這些。
真正serious的實際項目,你們仍是用用redux吧,配套齊全。自擼的項目,本身先踩踩坑~。

相關文章
相關標籤/搜索