博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于zepto的组件系统搭建
阅读量:6955 次
发布时间:2019-06-27

本文共 15485 字,大约阅读时间需要 51 分钟。

前言

随着前端开发复杂度的日益提升,组件化开发应运而生,对于一个相对简单的活动页面开发如何进行组件化是本文的主要内容。

概述

下面我们看一下在zepto的基础上如何构建组件系统,首先,我们要解决第一个问题,如何引用一个组件,我们可以通过设置一个属性data-component来引用自定义的组件:

那么如何向组件中传入数据呢,我们同样也可以通过设置属性来向组件传递数据,比如传入一个id值:

那么组件之间如何进行通信呢,我们可以采用观察者模式来实现。

写一个组件

我们先来看看我们如何来写一个组件

//a.jsdefineComponent('a', function (component) {    var el = '

input-editor

'; var id = component.getProp('id');//获取参数id $(this).append(el);//视图渲染 component.setStyle('.a{color:green}');//定义样式 $(this).find('p').on('click', function () { component.emit('test', id, '2');//触发test });});

我们先看看这个组件是怎么定义的,首先调用defineComponent(先不管这个函数在哪定义的)定义一个组件a,后面那个函数是组件a的组要逻辑,这个函数传入了一个component(先不管这个是哪来的,先看它能干啥),在前面我们说过如何向组件传递数据,在组件里我们通过component.getProp('id')来获取,样式我们通过component.setStyle('.a{color:green}')来定义,组件之前的通信我们通过component.emit()来触发(在别的组件里通过component.on()来注册),看上去我们基本解决了前面关于组件的一些问题,那么这个是怎么实现的呢?

组件实现原理

我们先来看看上面那个组件我们应该如何来实现,从上面定义一个组件来看有两个地方是比较关键的,一个是defineComponent是怎么实现的,一个就是component是什么。

我们先来看看defineComponent是怎么实现的,很显然defineComponent必须定义为全局的(要不然a.js就无法使用了,而且必须在加载a.js之前定义defineComponent),我们来看看defineComponent的代码

//component.js  var component = new Component();  window.defineComponent = function (name, fn) {        component.components[name] = {            init: function () {                //设置currentComponent为当前组件                currentComponent = this;                fn.call(this, component);                component.init(this);            }        };    }

这里我们可以看到定义了一个类Componentcomponent是它的一个实例,defineComponent就是在component.components注册一个组件,这里的关键是Component类,我们来看看Component是怎么定义的

//component.js  /**     * Component类     * @constructor     */    function Component() {        this.components = {};//所有的组件        this.events = {};//注册的事件        this.loadStyle = {};        this.init('body');//初始化    }    var currentComponent = null;//当前的组件    /**     * 类的初始化函数     * @param container 初始化的范围,默认情况下是body     */    Component.prototype.init = function (container) {        var self = this;        container = container || 'body';        $(container).find('[data-component]').each(function () {            self.initComponent(this);        });    };    /**     *  初始化单个组件     * @param context 当前组件     */    Component.prototype.initComponent = function (context) {        var self = this;        var componentName = $(context).attr('data-component');        if (this.components[componentName]) {            this.components[componentName].init.call(context);        } else {            _loadScript('http://' + document.domain + ':5000/dist/components/' + componentName + '.js', function () {                self.components[componentName].init.call(context);                //设置样式,同一个组件只设置一次                if (!self.loadStyle[componentName] && self.components[componentName].style) {                    $('head').append('');                    self.loadStyle[componentName] = true;                }            });        }    };    /**     * 设置样式     * @param style 样式     */    Component.prototype.setStyle = function (style) {        //获取当前组件的名称,currentComponent就是当前组件        var currentComponentName = $(currentComponent).attr('data-component');        var component = this.components[currentComponentName];        if (component && !component.style) {            component.style = style;        }    };    /**     * 获取组件参数     * @param prop 参数名     * @returns {*|jQuery}     */    Component.prototype.getProp = function (prop) {        var currentComponentNme = $(currentComponent).attr('data-component');        if ($(currentComponent).attr('data-' + prop)) {            return $(currentComponent).attr('data-' + prop)        } else {            //属性不存在时报错            throw Error('the attribute data-' + prop + ' of ' + currentComponentNme + ' is undefined or empty')        }    };    /**     * 注册事件     * @param name 事件名     * @param fn 事件函数     */    Component.prototype.on = function (name, fn) {        this.events[name] = this.events[name] ? this.events[name] : [];        this.events[name].push(fn);    };    /**     * 触发事件     */    Component.prototype.emit = function () {        var args = [].slice.apply(arguments);        var eventName = args[0];        var params = args.slice(1);        if(this.events[eventName]){            this.events[eventName].map(function (fn) {                fn.apply(null, params);            });        }else{            //事件不存在时报错            throw Error('the event ' + eventName + ' is undefined')        }    };    /**     * 动态加载组价     * @param url 组件路径     * @param callback 回调函数     * @private     */    function _loadScript(url, callback) {        var script = document.createElement("script");        script.type = "text/javascript";        if (typeof(callback) != "undefined") {            if (script.readyState) {                script.onreadystatechange = function () {                    if (script.readyState == "loaded" || script.readyState == "complete") {                        script.onreadystatechange = null;                        callback();                        $(script).remove();                    }                };            } else {                script.onload = function () {                    callback();                    $(script).remove();                };            }        }        script.src = url;        $('body').append(script);    }

我们先了解一下大概的流程

大致的流程就是上面这张流程图了,我们所有的组件都是注册在component.components里,事件都是在component.events里面。
我们回头看一下组件components里头的init方法

//component.js  var component = new Component();  window.defineComponent = function (name, fn) {        component.components[name] = {            init: function () {                //设置currentComponent为当前组件                currentComponent = this;                fn.call(this, component);                component.init(this);            }        };    }

首先,将this赋给currentComponent,这个在哪里会用到呢?在个getProp和setStyle这两个方法里都用到了

//component.js        /**     * 设置样式     * @param style 样式     */    Component.prototype.setStyle = function (style) {        console.log(currentComponent);        //获取当前组件的名称,currentComponent就是当前组件        var currentComponentName = $(currentComponent).attr('data-component');        var component = this.components[currentComponentName];        if (component && !component.style) {            component.style = style;        }    };    /**     * 获取组件参数     * @param prop 参数名     * @returns {*|jQuery}     */    Component.prototype.getProp = function (prop) {        return $(currentComponent).attr('data-' + prop)    };

到这里大家可能会对this比较疑惑,这个this到底是什么,我们可以先看在那个地方调用了组件的init方法

//component.js        /**     *  初始化单个组件     * @param componentName 组件名     * @param context 当前组件     */    Component.prototype.initComponent = function (componentName, context) {        var self = this;        if (this.components[componentName]) {            this.components[componentName].init.call(context);        } else {            _loadScript('http://' + document.domain + ':5000/components/' + componentName + '.js', function () {                self.components[componentName].init.call(context);                //设置样式,同一个组件只设置一次                if (!self.loadStyle[componentName] && self.components[componentName].style) {                    $('head').append('');                    self.loadStyle[componentName] = true;                }            });        }    };

就是在单个组件初始化的调用了init方法,这里有call改变了init的this,使得this=context,那么这个context又是啥呢

//component.js       /**     * 类的初始化函数     * @param container 初始化的范围,默认情况下是body     */    Component.prototype.init = function (container) {        var self = this;        container = container || 'body';        $(container).find('[data-component]').each(function () {            var componentName = $(this).attr('data-component');            console.log(this);            self.initComponent(componentName, this);        });    };

context其实就是遍历的每一个组件,到这里我们回过头来看看我们是怎么定义一个组件

//b.jsdefineComponent('b', function (component) {    var el = '

text-editor

'; $(this).append(el); component.on('test', function (a, b) { console.log(a + b); }); var style = '.text-editor{color:red}'; component.setStyle(style)});

我们知道this就是组件本身也就是下面这个

这个组件通过component.on注册了一个test事件,在前面我们知道test事件是在a组件触发的,到这里我们就把整个组件系统框架开发完成了,下面就是一个个去增加组件就好了,整个的代码如下:

//component.js(function () {    /**     * Component类     * @constructor     */    function Component() {        this.components = {};//所有的组件        this.events = {};//注册的事件        this.loadStyle = {};        this.init('body');//初始化    }    var currentComponent = null;//当前的组件    /**     * 类的初始化函数     * @param container 初始化的范围,默认情况下是body     */    Component.prototype.init = function (container) {        var self = this;        container = container || 'body';        $(container).find('[data-component]').each(function () {            self.initComponent(this);        });    };    /**     *  初始化单个组件     * @param context 当前组件     */    Component.prototype.initComponent = function (context) {        var self = this;        var componentName = $(context).attr('data-component');        if (this.components[componentName]) {            this.components[componentName].init.call(context);        } else {            _loadScript('http://' + document.domain + ':5000/dist/components/' + componentName + '.js', function () {                self.components[componentName].init.call(context);                //设置样式,同一个组件只设置一次                if (!self.loadStyle[componentName] && self.components[componentName].style) {                    $('head').append('');                    self.loadStyle[componentName] = true;                }            });        }    };    /**     * 设置样式     * @param style 样式     */    Component.prototype.setStyle = function (style) {        //获取当前组件的名称,currentComponent就是当前组件        var currentComponentName = $(currentComponent).attr('data-component');        var component = this.components[currentComponentName];        if (component && !component.style) {            component.style = style;        }    };    /**     * 获取组件参数     * @param prop 参数名     * @returns {*|jQuery}     */    Component.prototype.getProp = function (prop) {        var currentComponentNme = $(currentComponent).attr('data-component');        if ($(currentComponent).attr('data-' + prop)) {            return $(currentComponent).attr('data-' + prop)        } else {            //属性不存在时报错            throw Error('the attribute data-' + prop + ' of ' + currentComponentNme + ' is undefined or empty')        }    };    /**     * 注册事件     * @param name 事件名     * @param fn 事件函数     */    Component.prototype.on = function (name, fn) {        this.events[name] = this.events[name] ? this.events[name] : [];        this.events[name].push(fn);    };    /**     * 触发事件     */    Component.prototype.emit = function () {        var args = [].slice.apply(arguments);        var eventName = args[0];        var params = args.slice(1);        if(this.events[eventName]){            this.events[eventName].map(function (fn) {                fn.apply(null, params);            });        }else{            //事件不存在时报错            throw Error('the event ' + eventName + ' is undefined')        }    };    /**     * 动态加载组价     * @param url 组件路径     * @param callback 回调函数     * @private     */    function _loadScript(url, callback) {        var script = document.createElement("script");        script.type = "text/javascript";        if (typeof(callback) != "undefined") {            if (script.readyState) {                script.onreadystatechange = function () {                    if (script.readyState == "loaded" || script.readyState == "complete") {                        script.onreadystatechange = null;                        callback();                        $(script).remove();                    }                };            } else {                script.onload = function () {                    callback();                    $(script).remove();                };            }        }        script.src = url;        $('body').append(script);    }    var component = new Component();    window.defineComponent = function (name, fn) {        component.components[name] = {            init: function () {                //设置currentComponent为当前组件                currentComponent = this;                fn.call(this, component);                component.init(this);            }        };    }})();

工程化

上面搭建的组件系统有个不好的地方,就是我们定义的htmlstyle都是字符串,对于一些大的组件来说,htmlstyle都是非常长的,这样的话调试就会很困难,因此,我们需要对组件系统进行工程化,最终目标是htmljscss可以分开开发,现有的工程化工具比较多,你可以用gulp或者node自己写一个工具,这里介绍一下如何使用node来实现组件系统的工程化。

我们先来看看目录结构
我们首先要获取到编译前组件的路径

//get-path.jsvar glob = require('glob');exports.getEntries = function (globPath) {    var entries = {};    /**     * 读取src目录,并进行路径裁剪     */    glob.sync(globPath).forEach(function (entry) {        var tmp = entry.split('/');        tmp.shift();        tmp.pop();        var pathname = tmp.join('/'); // 获取前两个元素        entries[pathname] = entry;    });    return entries;};

然后根据路径分别读取index.js,index.html,index.css

//read-file.jsvar readline = require('readline');var fs = require('fs');exports.readFile = function (file, fn) {    console.log(file);    var fRead = fs.createReadStream(file);    var objReadline = readline.createInterface({        input: fRead    });    function trim(str) {        return str.replace(/(^\s*)|(\s*$)|(\/\/(.*))|(\/\*(.*)\*\/)/g, "");    }    var fileStr = '';    objReadline.on('line', function (line) {        fileStr += trim(line);    });    objReadline.on('close', function () {        fn(fileStr)    });};//get-component.jsvar fs = require('fs');var os = require('os');var getPaths = require('./get-path.js');var routesPath = getPaths.getEntries('./src/components/**/index.js');var readFile = require('./read-file');for (var i in routesPath) {    (function (i) {        var outFile = i.replace('src', 'dist');        readFile.readFile(i + '/index.js', function (fileStr) {            var js = fileStr;            readFile.readFile(i + '/index.html', function (fileStr) {                js = js.replace('', fileStr);                readFile.readFile(i + '/index.css', function (fileStr) {                    js = js.replace('

index.htmlindex.css转化成字符串插入到index.js中,我们看看index.js

// a/index.jsdefineComponent('a', function (component) {    var el = '';    var id = component.getProp('id');//获取参数id    $(this).append(el);//视图渲染    var style = '

<html><style>替换成之前index.htmlindex.css转化的字符串,最后对componets文件夹下面的文件进行监控

//component-watch.jsvar exec = require('child_process').exec;var chokidar = require('chokidar');console.log('开始监听组件...');chokidar.watch('./src/components/**/**').on('change', function (path) {    console.log(dateFormat(new Date(), 'yyyy-M-d h:m:s') + ':' + path + '变化了...');    exec('node get-component.js', function (err, out, code) {        console.log(dateFormat(new Date(), 'yyyy-M-d h:m:s') + ':' + '编译完成...');    });});//时间格式化function dateFormat(date, fmt) {    var o = {        "M+": date.getMonth() + 1, //月份        "d+": date.getDate(), //日        "h+": date.getHours(), //小时        "m+": date.getMinutes(), //分        "s+": date.getSeconds(), //秒        "q+": Math.floor((date.getMonth() + 3) / 3), //季度        "S": date.getMilliseconds() //毫秒    };    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));    for (var k in o)        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));    return fmt;}

到这里组件系统的工程化就完成了。

具体代码在

大家有兴趣的话可以关注一下我的

转载地址:http://butil.baihongyu.com/

你可能感兴趣的文章
Go 1.12发布:改进了运行时性能以及模块支持
查看>>
元数据驱动设计 —— 设计一套用于API数据检索的灵活引擎
查看>>
腾讯云携手朋迈推出“综合能源服务平台” 实现能源资源“智慧化”运营
查看>>
eBay宣布发布全新的购买和销售APIs
查看>>
微服务接口限流的设计与思考(附GitHub框架源码)
查看>>
Underscore 整体架构浅析
查看>>
SegmentFault 技术周刊 Vol.12 - Web 安全指南(上)
查看>>
HTML5中的表单
查看>>
vue初探-简易留言板
查看>>
完美解决safari、微信浏览器下拉回弹效果。
查看>>
我在那日界线上奔跑之JS---链表
查看>>
Ueditor 1.4.3.3 PHP图片上传路径修改
查看>>
matalb学习3-各种求解
查看>>
Hyper-V创建虚拟机
查看>>
实现小小的fullpage
查看>>
常见设计模式的定义,应用场景和方法
查看>>
ES6——生成器
查看>>
多地图架构设计
查看>>
剪切板粘贴上传图片功能的javascript实现
查看>>
Friday Q&A 2015-12-11:Swift 中的弱引用
查看>>