用少量程式碼實現redux核心部分及其演化歷史

殷榮檜發表於2019-01-09

作者:殷榮檜@騰訊

本文原始碼地址

本文Github地址,歡迎star

  因為redux是基於flux模式的,所以如果你想對flux有所瞭解,可以檢視我的這篇文章Vuex和Redux都使用的Flux設計模式精簡版實現

  先來看一下,完成文章標題所說的,需要完成哪些任務:(看到下面這個list,先不要被嚇跑,因為每個任務平均實現程式碼都沒有25行,只是為了分的細一點)

TODO LIST(計劃列表)

1.redux中reducer的實現

2.redux中action的實現

3.redux中store的實現

3.5 先不使用redux,直接用react實現一個主題定製頁面的開發

  3.5.1 使用props傳參完成父子元件共享同一個全域性變數(演示全域性變數的原始層層傳遞共享資料)
    
  3.5.2 使用react提供的Context上下文完成父子元件全域性變數的共享(演示全域性變數使用react context 跨越元件共享資料) 

4.結合自己實現的redux實現一個主題定製頁面的開發

5.redux中的connect的實現。(結合react中的context實現)

6.結合connect react把主題定製頁面再實現一遍

7.redux中新增mapStateToProps

8.redux中新增mapDispatchToProps

9.結合mapStateToProps,mapDispatchToProps 主題定製頁面

10.redux中的provider的實現

11.使用provider重構程式碼

12.使用真正的react-redux替代進來,看專案是否正常執行。程式碼整理,provider中頁面重新整理相關的移動到connect,語法檢查,錯誤檢查,新增註釋,合併commit
複製程式碼

  接下來我們試著一個一個去實現。當然這其中包括了為什麼需要redux的歷史演化的過程,總的用了十幾個commit來完成了這篇文章,基本上每個commit實現TODO list中的一個計劃(在這牆裂推薦你使用source tree這個工具來檢視各個commit都幹了啥,修改了啥,對閱讀原始碼很有幫助)。在完成一個完整的功能後都會使用完整的demo進行演示,基本上可以覆蓋redux的進化歷程了。最後真正實現redux的部分連100行程式碼都不到,所以你千萬不要被redux的名頭嚇跑。最後再不要臉一下,如果你覺得文章寫得還行,歡迎您在我的Github中送上star.

用少量程式碼實現redux核心部分及其演化歷史

接下來,就照著專案提交的commit一個一個的講:

(1)commit1:完成TODO list規劃 這個commit主要是完成了實現這篇文章所要完成的任務。也就是上面陳列的TODO list.

(2)commit2:完成reducer模組的功能 沒有使用任何的構建工具,直接開啟index.html就可以執行。需要在Google 除錯工具的控制檯中檢視結果,介面上並無內容。這個commit主要就是實現了一個reducer,並在index.html中呼叫了reducer.js,主要是除錯了一下reducer是否可以正常的執行。reducer實現的程式碼部分如下。

var reducer = (state = initialState, action) => {
    switch(action.type) {
        case CREATE_NOTE: {
            let currentId = state.nextNoteId;
            return {
                nextNoteId: currentId + 1,
                notes: {
                    ...state.notes,
                    [currentId]: action.content
                },
            };
        }
        case UPDATE_NOTE: {
            let {id, content} = action;
            return {
                ...state,
                notes: {
                    ...state.notes,
                    [id]: content
                }
            };
        }
        default:
            return state;
    }
}
複製程式碼

除錯成功的結果如下:

用少量程式碼實現redux核心部分及其演化歷史

(3)commit3-commit4:redux中action的實現

這兩個commit主要實現了redux中action的定義。同樣在index.html中引入了actions.js來對所寫的action進行測試。

const CREATE_NOTE = "CREATE_NOTE";
const UPDATE_NOTE = "UPDATE_NOTE";

// 新增一條備忘錄
let create_action = {
    type: CREATE_NOTE,
    content: '明天下午要開會'
};
// 更新一條備忘錄
let update_action = {
    type: UPDATE_NOTE,
    id: 1,
    content: '好像記錯了,是明天上午要開會'
};
複製程式碼

下圖為測試成功控制檯的結果。 用少量程式碼實現redux核心部分及其演化歷史

(4)commit5-commit6:完成store的實現,基本實現redux功能

function createStore(reducer) {
    let state = undefined;
    const subscribers = [];
    let store = {
        getState: () => state,
        dispatch: (action) => {
            state = reducer(state, action);
            subscribers.forEach(handler => handler());
        },
        subscribe: (handler) => {
            // handler就要比如備忘錄有更新就傳送通知到對應的人之類的
            subscribers.push(handler);
            return () => {
                let index = subscribers.indexOf(handler);
                if (index >= 0) {
                    // 防止搜尋不到index為-1時,把subscribers最後一個刪除了
                    subscribers.splice(0, index);
                }
            }
        }
    }
    store.dispatch(init_action); // 初始化一下備忘錄
    return store;
}
複製程式碼

基本實現redux後,來測試一下自己寫的redux是否正常執行。以下為正常執行的結果:

用少量程式碼實現redux核心部分及其演化歷史

(5)commit7:使用props傳參完成父子元件通訊,完成主題切換 使用react的專案初始化工具,詳情參考react專案初始化工具,完成專案的初始化工作,並使用最原始的方式進行父子元件的傳參,使用了props進行傳參。使用這種方式的弊端就是,如果要從父向兒子,孫子,重孫....一直傳到祖宗十九代的時候,每個子元件都要寫向下傳遞的程式碼,非常的冗餘和難以維護。在專案中才使用了幾層程式碼都能感覺到程式碼的冗餘和難以維護。(注意:專案中的Index.js需要修改為index.js才可以在my-app目錄下使用npm run start執行,我的鍋,不要意思,之後的commit都要改成index.js才可以正常執行)

 class Content extends Component {
  render() {
    return (
      <div>
          <span 
            color={this.props.color} 
            style={{color: this.props.color}}>
              主內容區域
          </span>
          <br/>
          <ChildContent 
            switchColor={this.props.switchColor}
            color={this.props.color} 
            ></ChildContent>
      </div>
    );
  }
}
複製程式碼

以下是完成的主題切換的效果:

用少量程式碼實現redux核心部分及其演化歷史

(6)commit8:完成react提供的Context上下文完成父子元件全域性變數的共享 為了避免程式碼的冗餘。可以考慮使用react中提供的全域性context來進行完成。這樣就可以避免層層使用props來進行傳參。在任何一行使用contextTypes就可以完成引用全域性變數。但是這樣寫程式碼的弊端依然很明顯。程式碼中依然存在冗餘。但維護性相對於使用props較好。以下是使用contextTypes在元件中引用屬性的方法。

class Title extends Component {
    static contextTypes = {
        color: PropTypes.string
    }
    render() {
        return (
            <div style={{color: this.context.color}}>我是文章的標題</div>
        );
    }
}
複製程式碼

(7)commit9-commit10:結合自己實現的redux實現一個主題定製頁面的開發

到這就可以結合自己開發的redux實現一個簡單的主題定製頁面的開發。主要在紅色主題與藍色主題之間切換。來測試自己所寫的redux是否可以正常工作。主要實現的結果和上面相同。通過在my-app/的目錄下執行npm run start就可成功啟動頁面。

(8)commit11-commit13:redux中connect的實現

connect實現部分的程式碼如下:

let connect = (WrappedComponent) => {
    class connect extends Component {
        static contextTypes = {
            store: PropTypes.object,
        }
        componentWillMount() {
            this.store = this.context.store;
        }
        render() {
            return <WrappedComponent store = {this.store}></WrappedComponent>   
        }
    }
    return connect;
}
複製程式碼

(9)commit14:redux中mapStateToProps的實現

mapStateToProps實現的程式碼如下:

let mapStateToProps = (state) => {
    return {
        themeColor: state.color,
    }
}
複製程式碼

(10)commit15:redux中mapDispatchToProps的實現並結合自己實現的mapStateToProps和mapDispatchToProps更新主題修改小應用

let mapDispatchToProps = (dispatch) => {
    return {
        changeThemeColor: function(color) {
            dispatch({type: UPDATE_THEME, color})
        } 
    }
}
複製程式碼

(11)commit16:redux中provider的實現,並使用其更新主題修改小程式

class Provider extends Component {
    static propTypes = {
        store: PropTypes.object,
    }
    static childContextTypes = { // 定義父子元件共享的變數
        store: PropTypes.object,
    }
    getChildContext () {
        return {
          store: this.props.store
        }
    }
    componentWillMount() {
        this.props.store.subscribe(() => this.updateComponent());
    }
    updateComponent() {
        // 每次store資料更新後重新渲染一下頁面
        this.setState({color: this.props.store.getState().color});
    }
    render() {
        return this.props.children;
    }
}
複製程式碼

(12)commit17-commit18:完成redux的簡單仿寫,並將專案中引用自己所寫redux部分全部改成react中的redux,好看程式碼是否可行。

-import createStore from './redux/store';
+import {createStore} from 'redux';
-import Provider from './redux/provider';
+import {Provider} from 'react-redux';
複製程式碼

通過上述的替換之後,切換主題的小應用工作正常(需要使用命令npm run dev,手賤,把start改成了dev),如下圖所示。可見所仿寫的redux基本成功,當然其中精細部分與原版有所差別,但是整體的原理上基本相似。所以到這基本上就瞭解了redux的工作原理,以後再也不要對redux的語法死記硬背了,因為你都已經寫過一遍。

用少量程式碼實現redux核心部分及其演化歷史

本文原始碼地址

本文Github地址,歡迎star

  部門正在招新,為騰訊企業產品部,隸屬CSGI事業群。福利不少,薪水很高,就等你來。有興趣請猛戳下方兩個連結。    www.lagou.com/jobs/521039… www.zhipin.com/job_detail/…

(如果發現文章中程式碼存在問題,請批評指正並留言,我會更新到文章以及程式碼中去,謝謝)

參考資料

code-cartoons.com/a-cartoon-i… cartoon intro to redux) zapier.com/engineering…(build yourself a redux) www.sohamkamani.com/blog/2017/0… (React-redux "connect" explained) codesandbox.io/s/github/re…(provider and connect example) huziketang.mangojuice.top/books/react… (React.js 的 context) huziketang.mangojuice.top/books/react… (動手實現 Redux(四):共享結構的物件提高效能,為什麼需要使用...(spread operator)來提供效能) juejin.im/post/5a90e0…(聊一聊我對 React Context 的理解以及應用)

相關文章