­

前端框架擼起來——組件和路由

框架只有一個html文件,html中只有一個id是app的div,如何點擊一個按鈕或者菜單來顯示對應的頁面呢?最初大家都是通過拼接html字元串,然後再綁定,這樣寫很不優雅,當系統功能模組龐大時,這樣下來難以維護。如何實現模組化以及寫出優雅的程式碼,接下來就是組件和路由的事情。

組件(Component)

組件是龐大系統的一個個小的零件,組件可以進行嵌套。系統有多個頁面構成,頁面有多個部件組成,頁面和部件都可以稱之為組件,他們都有共同的屬性和方法。本框架我們約定組件有render、mounted、destroy三個方法。

1)組件的定義

function TestPage() {
    //這裡寫組件的私有變數、共有屬性和方法、私有方法
    var component1 = new Component1();//私有變數component1
    var timer;//計時器
}

2)呈現方法(render)

//這裡是呈現TestPage組件的方法
//dom是根節點app,也可以是其他頁面中的節點
this.render = function(dom) {
    $('<div>').html('Component1').appendTo(dom);//呈現一個div
    component1.render(dom);//呈現嵌套組件component1
}

3)掛載方法(mounted)

//這裡是載入組件的後端介面數據
this.mounted = function() {
    component1.loadData();
    timer = setInterval(function() {...}, 1000);
}

4)銷毀方法(destroy)

//這裡是銷毀組件的資源,例如一個setInterval的對象
this.destroy = function() {
    clearInterval(timer);
}

5)組件完整程式碼

function TestPage() {
    var component1 = new Component1();
    var timer;
    
    this.render = function(dom) {
        $('<div>').html('Component1').appendTo(dom);
        component1.render(dom);
    }
    
    this.mounted = function() {
        component1.loadData();
        timer = setInterval(function() {...}, 1000);
    }
    
    this.destroy = function() {
        clearInterval(timer);
    }
}

路由(Router)

路由是不同組件之前的轉換器,起到組件自由切換的作用。路由可以進行嵌套,即頁面是最頂級的組件,渲染在根節點下面,頁面內部區塊也可以呈現不同的組件。本框架路由只提供兩個方法,即導航和回退,其實路由可以擴展更多的方法,如根據name或者模板來路由,這裡暫不實現。本框架暫不支援瀏覽器地址路由,有興趣的同學可以自己實現。

1)路由的定義

//elem是路由的節點對象
//option是路由的配置選項
function Router(elem, option) {
    //這裡寫路由的私有變數、共有屬性和方法、私有方法
    var _current = {};//存儲當前路由對象
}

2)導航方法(route)

//路由到指定的組件
//item為路由對象,必須包含component屬性
this.route = function(item) {
    //呈現前的驗證,例如登錄驗證
    if (!_option.before(item))
        return;
    //銷毀當前組件
    _destroyComponent();
    //設置當前組件
    _setCurrent(item);
    //執行組件
    var component = item.component;
    if (component) {
        _renderComponent(component);
        _mountComponent(item, component);
    }
}

3)回退方法(back)

//回退到當前路由的上一個路由
this.back = function() {
    _this.route(_current.previous);
}

4)路由完整程式碼

function Router(elem, option) {
    //fields
    var _option = option || {},
        _elem = elem,
        _current = {},
        _this = this;

    //methods
    this.route = function (item) {
        if (!_option.before(item))
            return;

        _destroyComponent();
        _setCurrent(item);

        var component = item.component;
        if (component) {
            _renderComponent(component);
            _mountComponent(item, component);
        }
    }

    this.back = function () {
        _this.route(_current.previous);
    }
    
    //private
    function _destroyComponent() {
        var currComp = _current.component;
        currComp && currComp.destroy && currComp.destroy();
    }
    
    function _setCurrent(item) {
        if (!item.previous) {
            item.previous = _current; //存儲上一個路由
        }
        _current = item;
    }
    
    function _renderComponent(component) {
        if (typeof component === 'string') {
            _elem.html(component);//字元串組件
        } else {
            _elem.html('');//清空節點
            component.render(_elem);//呈現組件
        }
    }
    
    function _mountComponent(item, component) {
        setTimeout(function () {
            _option.after && _option.after(item);//呈現後回調公共邏輯
            component.mounted && component.mounted();//調用後台數據
        }, 10);//延時執行,等dom呈現完成後
    }
}

下一章我們實現框架根組件App。