前端框架撸起来——组件和路由

框架只有一个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。