Vuex和Redux都使用的Flux設計模式精簡版實現

做者:殷榮檜@騰訊

目錄:

1. 爲何須要Flux設計模式

2. Flux設計模式是怎麼實現的

本文對應Github地址,若是以爲文章還能夠,但願您送上寶貴的Star

  先來看一下最終結果,省得你以爲太複雜,跑了。怎麼樣就一個添加,一個刪除,是否是很簡單,那就繼續往下看吧。css

  老外搞個新東西就喜歡給其取個Cool的名字,什麼Flux,Redux,Meteor。原本英語就不是太好的中國人一看就跑路了,What?老子Javascirpt還沒學好,你又來這這些。名字都看不懂,還學啥。紛紛感慨:老子學不動了,不要再更新了。其實吧,老外就是把公司的我的檔案管理流程應用到了前端數據管理流程中了,而後取了嚇跑了不少人的名字Flux而已,不信我給你實現一個簡單版的Flux架構(代碼中對照我的檔案管理流程講解Flux流程),結合下面這張圖你再看看是否是。html

1.爲何須要Flux設計模式前端

(1)刀耕火種的年代react

  最初由於前端就是幾個頁面和幾個js文件,前端對於網站中的全局變量(全局變量就是在哪一個頁面均可以被訪問和修改的變量,如網站的標題)基本上用git

window.title = 'XXX'
複製代碼

  就能夠了。剛上手項目,你全局搜一下window這個關鍵字也差很少對項目中的全局變量都有個印象了。而後有一天,線上網站中標題出錯了(好比:‘愛牛網’ 變成了 ‘屠牛網’),老闆一看嚇的不輕,讓你趕忙看看。你就全局搜一下window.title看一下,原來在D頁面title被一個新來的員工改錯了(新員工在測試環境下改代碼改着玩的,忘刪了)。而後你把它修改過來就能夠了。github

(2)MVC時代設計模式

  前端發展到中期,應用的場景開始增多,須要一個框架來下降代碼的耦合了,Backbone相似的MVC框架出現。經過各個Model來存放數據,包括全局的數據。這個時候全局變量能夠經過View(頁面)來修改Model中的數據,再反饋到其餘的View中去,作到數據同步。這個時期基本上是作一個博客網站,管理系統的體量。數組

(3) 前端大爆炸時代架構

  前端發展到如今,所應用的場景就愈來愈多了。有個同窗小扎(馬克·扎克伯格)在大學裏想作個叫Facebook的社交應用,就讓前端的朋友幫忙作一作頁面。前端同窗很快用MVC模式搞定了Facebook前端架構。可是令小扎同窗沒想到的是,這個叫Facebook的應用火了,功能不停的迭代,增長。前端到同窗表示天天加班加點累到只能看着本身手中小扎剛發的1000萬美刀的股票罵罵娘。這個時候的前端體量急劇膨脹,上百個View(頁面)出現,對應上百個Model(存放前端數據的專用層),常常會出現多個頁面對同一個Model數據進行操做,好比有個全局數據剛添加到Model,又來一個頁面通知這個全局變量要刪除,這個時候其餘頁面還不知道這個全局變量已經刪除了,表示要修改這個數據,再來個頁面又要獲取這個數據,這個時候就致使前端的數據管理異常的混亂,任何一個頁面都有權操做全局變量。你能想象一個公司的員工檔案信息中心(相似全局變量)如何任何員工(頁面)均可以過來隨意的增刪改查,你信不信沒過幾天,你再去檔案庫查看你的檔案,你的學歷會變成幼兒園。這個檔案信息中心的數據將會一塌糊塗。這就是Facebook有一斷時間老是會出現你有未讀消息提示,可是你點進去查看卻沒有任何消息的問題。小扎同窗由於這個問題被無數的美國網友罵 What the hell.小扎那叫個火大啊,把前端的主管叫過來一頓痛罵(把美國網友送給他的What the hell都送給了這位主管),讓其一個星期內解決此問題(以上罵娘故事純屬虛構,)。這個時候Flux就出現了。這就是爲何須要Flux設計模式。app

2.Flux設計模式是怎麼實現的

  咱們直接上代碼,在代碼中作了很是詳細的比喻,再對照文章開始的圖,相信你必定能看懂。

(1) Action實現部分

/**
 *(2)Action人事辦事(分設:增長檔案辦事窗口,刪除檔案辦事窗口...)大廳生成器
 *公司員工的檔案信息屬於重要信息,非HR工做人員不
 *可以直接找檔案信息中心的人私自修改本身或他人檔案,
 *須要到人事大廳窗口辦理,由人事大廳交給人事主管(dispatcher)
 *再由HR人事辦事大廳
 */
let Action = function Action() {
    return {
        create: function(initData) { // 新建員工辦事窗口
            // 辦事大廳登記此項事物後由人事主管(dispatcher)通知手下注冊過的員工去幹(staff_1好像就會幹)
            dispatcher.dispatch({ type: 'create', item: initData });
        },
        add: function(item) { // 添加員工辦事窗口
            // 辦事大廳登記此項事物後由人事主管(dispatcher)通知手下注冊過的員工去幹(staff_1好像就會幹)
            dispatcher.dispatch({ type: 'add', item });
        },
        remove: function(item) { // 刪除員工辦事窗口
            // 辦事大廳登記此項事物後由人事主管(dispatcher)通知手下注冊過的員工去幹(staff_1好像就會幹)
            dispatcher.dispatch({ type: 'remove', item });
        }
    }
};
複製代碼

(2)Dispatcher 實現部分

/**
 * (1)主管生成器
 * 生成各個部門的主管(各類主管,包括hr主管,人事檔案主管等)
 */
let Dispatcher = function Dispatcher() {
    let _cid = 0; // callbackId
    let _callbacks = [];
    return {
        register: function(callback) {
            _callbacks[_cid] = callback;
            return _cid++;
        },
        unregister: function(_cid) {
            // 從回調數組中刪除當前的一項
            _callbacks.splice(_cid, _cid);
        },
        dispatch: function(payload) {
            // 通知全部註冊的用戶執行回調方法
            _callbacks.forEach(callback => callback(payload));
        }
    }
};	
複製代碼

(3)Store實現部分

/**
 * (3)檔案信息中心生成器
 * 全部員工的信息都存放在檔案信息中心
 */
let Store = function Store() {
    let _itemList = []; // 員工檔案信息存放櫃
    let _emit = new Flux.Dispatcher(); // 生成檔案信息中心主管(姓名_emit)
    // 一個小員工名爲staff_1到人事主管dispatcher這注冊,這樣dispatcher主管就有一個員工啦
    dispatcher.register(function staff_1(payload) {
        // 如下是這個小員工的簡歷,代表他能夠幹增長,刪除,更新等hr員工該乾的活
        switch (payload.type) {
            case 'create': // 建立初始員工(創始人那一批,相似於阿里巴巴的18羅漢之類的)
                _itemList = [...payload.item];
                break;
            case 'add': // 添加一個新的員工檔案
                _itemList.push(payload.item);
                break;
            case 'remove': // 刪除一個離職員工的檔案
                _itemList = _itemList.filter(item => item.id != payload.item.id);
                break;
            default: // 其餘操做
                break;
        }
        _emit.dispatch(); // 檔案信息中心主管發出通知,讓手下注冊的員工們開始幹活了
    });

    return {
        // 獲取員工全部的檔案信息
        getList: function() {
            return _itemList;
        },
        // 有新的員工到檔案信息中心主管(_emit)這注冊報到,這樣就有人給主管幹活啦
        addEmiter: function(callback) {
            return _emit.register(callback);
        },
        // 乾的不爽,從檔案信息中心主管這離職了
        removeEmiter: function(callbackId) {
            _emit.unregister(callbackId);
        }
    }
}
複製代碼

(4)最後爲頁面中的實現部分

// ==========如下爲辦事大廳顯示屏顯示狀況控制==============
var store = new Flux.Store(); // 生成一個名爲store的檔案信息中心
var action = new Flux.Action(); // 生成一個名爲action的人事辦事大廳
// 初始化加載的備忘錄列表
var initStaffs = [
    { id: 0, name: '馬雲' },
    { id: 1, name: '王健林' },
    { id: 2, name: '褚時健' },
];

// 從新把檔案信息中心的人事檔案渲染到辦事大廳大屏幕上
var reRender = function reRender() {
    appEle = document.querySelector('#app');
    ulEle = document.querySelector('#list');
    let allEleArr = [];
    this.store.getList().forEach((item) => {
        allEleArr.push(`<li>${item.name}  <span id="${item.id}" class="remove" onclick="remove(${item.id})">刪除</span></li>`);
    });
    let allStr = allEleArr.join('');
    ulEle.innerHTML = allStr;
}

var remove = function remove(id) {
    // 整理全離職員工的信息
    let delItem = '';
    this.store.getList().forEach(item => {
        if (item.id === id) {
            delItem = item;
        }
    });
    // 到人事大廳「刪除」辦事窗口辦理
    this.action.remove(delItem);
}
var add = function add(event) {
    // 本身整理新員工的檔案信息
    let itemValue = document.querySelector('#thingInput').value;
    let currentList = this.store.getList();
    let lastId = currentList.length ? currentList[currentList.length - 1].id : 0;
    let item = {
            id: lastId + 1,
            name: itemValue
        }
        // 整理好了到人事大廳「添加」辦事窗口辦理新增員工業務
    this.action.add(item);
}

setTimeout(() => {
    // 將初始員工的信息到人事大廳新建辦事窗口辦理,以便存放到檔案信息中心的檔案櫃中
    action.create(initStaffs);
    //  檔案信息中心主管這來了一個新員工註冊了,他的本領都在 reRender 函數中說明了
    store.addEmiter(reRender);
    // 代碼加載完畢後,點亮辦事大廳的全部員工檔案信息展現大屏幕
    reRender();
}, 0);
複製代碼

把全部的代碼結合起來也就是下面這樣:

=====================js.js文件=====================
/**
 * author: JackieYin
 * time: 2018-12-8 16:35
 * title: Flux設計模式的簡單實現
 * goal: flux設計的初衷是讓你取數據好取,但要改變
 * 數據須要通過flux的一系列操做才行,這樣可
 * 以保障數據不會被隨意篡改
 */

// Flux對象中存放Flux架構
let Flux = function() {
    /**
     * (1)主管生成器
     * 生成各個部門的主管(各類主管,包括hr主管,人事檔案主管等)
     */
    let Dispatcher = function Dispatcher() {
        let _cid = 0; // callbackId
        let _callbacks = [];
        return {
            register: function(callback) {
                _callbacks[_cid] = callback;
                return _cid++;
            },
            unregister: function(_cid) {
                // 從回調數組中刪除當前的一項
                _callbacks.splice(_cid, _cid);
            },
            dispatch: function(payload) {
                // 通知全部註冊的用戶執行回調方法
                _callbacks.forEach(callback => callback(payload));
            }
        }
    };
    var dispatcher = new Dispatcher(); // // 生成一個HR人事主管(主管姓名:dipatcher)(主管員工的增刪改查)
    /**
     *(2)Action人事辦事(分設:增長檔案辦事窗口,刪除檔案辦事窗口...)大廳生成器
     *公司員工的檔案信息屬於重要信息,非HR工做人員不
     *可以直接找檔案信息中心的人私自修改本身或他人檔案,
     *須要到人事大廳窗口辦理,由人事大廳交給人事主管(dispatcher)
     *再由HR人事辦事大廳
     */
    let Action = function Action() {
        return {
            create: function(initData) { // 新建員工辦事窗口
                // 辦事大廳登記此項事物後由人事主管(dispatcher)通知手下注冊過的員工去幹(staff_1好像就會幹)
                dispatcher.dispatch({ type: 'create', item: initData });
            },
            add: function(item) { // 添加員工辦事窗口
                // 辦事大廳登記此項事物後由人事主管(dispatcher)通知手下注冊過的員工去幹(staff_1好像就會幹)
                dispatcher.dispatch({ type: 'add', item });
            },
            remove: function(item) { // 刪除員工辦事窗口
                // 辦事大廳登記此項事物後由人事主管(dispatcher)通知手下注冊過的員工去幹(staff_1好像就會幹)
                dispatcher.dispatch({ type: 'remove', item });
            }
        }
    };
    /**
     * (3)檔案信息中心生成器
     * 全部員工的信息都存放在檔案信息中心
     */
    let Store = function Store() {
        let _itemList = []; // 員工檔案信息存放櫃
        let _emit = new Flux.Dispatcher(); // 生成檔案信息中心主管(姓名_emit)
        // 一個小員工名爲staff_1到人事主管dispatcher這注冊,這樣dispatcher主管就有一個員工啦
        dispatcher.register(function staff_1(payload) {
            // 如下是這個小員工的簡歷,代表他能夠幹增長,刪除,更新等hr員工該乾的活
            switch (payload.type) {
                case 'create': // 建立初始員工(創始人那一批,相似於阿里巴巴的18羅漢之類的)
                    _itemList = [...payload.item];
                    break;
                case 'add': // 添加一個新的員工檔案
                    _itemList.push(payload.item);
                    break;
                case 'remove': // 刪除一個離職員工的檔案
                    _itemList = _itemList.filter(item => item.id != payload.item.id);
                    break;
                default: // 其餘操做
                    break;
            }
            _emit.dispatch(); // 檔案信息中心主管發出通知,讓手下注冊的員工們開始幹活了
        });

        return {
            // 獲取員工全部的檔案信息
            getList: function() {
                return _itemList;
            },
            // 有新的員工到檔案信息中心主管(_emit)這注冊報到,這樣就有人給主管幹活啦
            addEmiter: function(callback) {
                return _emit.register(callback);
            },
            // 乾的不爽,從檔案信息中心主管這離職了
            removeEmiter: function(callbackId) {
                _emit.unregister(callbackId);
            }
        }
    }
    return {
        Dispatcher,
        Action,
        Store,
    }
}();

// ==========如下爲辦事大廳顯示屏顯示狀況控制==============
var store = new Flux.Store(); // 生成一個名爲store的檔案信息中心
var action = new Flux.Action(); // 生成一個名爲action的人事辦事大廳
// 初始化加載的備忘錄列表
var initStaffs = [
    { id: 0, name: '馬雲' },
    { id: 1, name: '王健林' },
    { id: 2, name: '褚時健' },
];

// 從新把檔案信息中心的人事檔案渲染到辦事大廳大屏幕上
var reRender = function reRender() {
    appEle = document.querySelector('#app');
    ulEle = document.querySelector('#list');
    let allEleArr = [];
    this.store.getList().forEach((item) => {
        allEleArr.push(`<li>${item.name}  <span id="${item.id}" class="remove" onclick="remove(${item.id})">刪除</span></li>`);
    });
    let allStr = allEleArr.join('');
    ulEle.innerHTML = allStr;
}

var remove = function remove(id) {
    // 整理全離職員工的信息
    let delItem = '';
    this.store.getList().forEach(item => {
        if (item.id === id) {
            delItem = item;
        }
    });
    // 到人事大廳「刪除」辦事窗口辦理
    this.action.remove(delItem);
}
var add = function add(event) {
    // 本身整理新員工的檔案信息
    let itemValue = document.querySelector('#thingInput').value;
    let currentList = this.store.getList();
    let lastId = currentList.length ? currentList[currentList.length - 1].id : 0;
    let item = {
            id: lastId + 1,
            name: itemValue
        }
        // 整理好了到人事大廳「添加」辦事窗口辦理新增員工業務
    this.action.add(item);
}

setTimeout(() => {
    // 將初始員工的信息到人事大廳新建辦事窗口辦理,以便存放到檔案信息中心的檔案櫃中
    action.create(initStaffs);
    //  檔案信息中心主管這來了一個新員工註冊了,他的本領都在 reRender 函數中說明了
    store.addEmiter(reRender);
    // 代碼加載完畢後,點亮辦事大廳的全部員工檔案信息展現大屏幕
    reRender();
}, 0);

==================index.html 文件==================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="./css.css">
    <title>Document</title>
</head>
<body>
    <header id="title">
        <h1>Flux樣例演示系統</h1>
    </header>
    <div id="app">
        <input type="text" id="thingInput" placeholder="請輸入新的員工名稱"><span onclick="add()" class="add">添加</span>
        <ul id="list"></ul>
    </div>
</body>
<script src="./js.js"></script>

</html>
複製代碼

  最後我仍是牆裂建議你把個人代碼clone下來,直接打開其中的index.html就能運行。而後再仔細看看,若是發現有寫的不對的地方反手就是一個Issue提給我。最後仍是那就不要臉的話,若是你覺的講的還能夠,Please送上你寶貴的Star。   
   部門正在招新,爲騰訊企業產品部,隸屬CSGI事業羣。福利很多,薪水很高,就等你來。有興趣請猛戳下方兩個連接。       www.lagou.com/jobs/521039… www.zhipin.com/job_detail/…

參考文章:

Flux架構小白入門筆記

Flux from Scratch

Dissection of Flux architecture or how to write your own

A cartoon guide to Flux

相關文章
相關標籤/搜索