Omi框架學習之旅-Hello World 及原理說明

CSS HTML 軟件 技術 達人科技 2017-03-22

學什麼東西都從hello world開始, 我也不知道為啥。

恩,先上demo代碼, 然後提出問題, 之後解答問題, 最後源碼說明。

hello world - demo:

        class Hello extends Omi.Component {    // 1. Hello類先繼承Component類
 constructor(str, data) {    // 2. Hello類構造函數的參數和個數隨便傳,但是必須要有個數據對象data(其實可以是對象或者傳undefined,因為其還有第二個參數,不傳undefined的話,第二個參數就變成了第一個參數)
 super(data);    //  3. es6和es5的繼承還是有區別的,es6需要先得到父類的this實例,然後子類對其加工。Component父類其實可以接受2個參數的,這裡就先接受data數據對象作為第一個參數
 this.str = str;
 console.log(this);
 }

 style {    // 4. style方法其實是Hello類重寫了Component類的style方法(其實父類的style方法也只是空函數)
 return `
 h1 {
 color: red;
 }
 `;
 }

 clickHandle(t, evt) {    // 5. Hello類的原型方法和Component類沒關係哈
 console.log(t, evt);
 }

 render {    // 6. render方法也是Hello類重寫了Component類的render方法
 return `
 <h1 onclick="clickHandle(this, event)">aaa{{name}}</h1>
 `;
 }
        };

        var hello = new Hello('str', {name: 'hello world'});    // 7. 創建Hello類的實例
        Omi.render(hello, '#app');    // 8.讓omi渲染(生成html和局部css和把內置事件和對應的實例clickHandle函數關聯起來)

先看看omi中文文檔的說明:

組件生成的HTML最終會插入到#app中。上面的例子展示了Omi的部分特性:

  • data傳遞: new Hello(data,..)的data可以直接提供給render方法裡的模板
  • 局部CSS: h1只對render裡的h1生效,不會汙染外面的h1
  • 聲明式事件綁定: onclick調用的就是組件內的handleClick,this可以拿到當前的DOM元素,還可以拿到當前的event
  • 文檔地址:https://alloyteam.github.io/omi/website/docs-cn.html#

接下來說說這個demo的疑問和疑問的說明:

疑問1: 自己寫的類一定要繼承Omi.Component類嗎?

答:廢話,想用omi,你還不繼承別人的屬性和方法,怎麼玩下去(當然牛逼的人可以自己寫哈)。

注: Component類是核心, 源碼在component.js中,然後

Omi.Component = Component; 把Component類掛載到Omi的Component對象上去了。

疑問2: super方法中可以傳什麼數據類型的參數,和參數的個數?

答:Component構造函數可以接收2個參數。

理論上可以有這幾種傳參的方式:

傳1個參數 super/super(undefined), super({}), super('str'), super(true), super([]), super(null)

傳2個參數 super(data, 真值)

先說傳1個參數會導致啥吧:

傳 true, 'str', , null, 會使變量isReRendering則為true, {}, undefined/或者不傳參數, 變量isReRendering則為false,然而isReRendering變量是if的條件會開了2個分支,

處理不同的邏輯。

傳2個參數會怎樣啊:

第二個參數可以任意類型的值:因為會和對象{server: false, ignoreStoreData: false}合併

感受下omi的源碼:

constructor(data, option) {    // data: 數據data, option: 一般為對象
        const componentOption = Object.assign({    // 和並option
 server: false,    // 老版中的server屬性
 ignoreStoreData: false    // 忽略存儲數據
        }, option);
        this._omi_ignoreStoreData = componentOption.ignoreStoreData;    // 是否忽略存儲數據屬性_omi_ignoreStoreData
        //re render the server-side rendering html on the client-side
        const type = Object.prototype.toString.call(data);    // data的數據類型
        const isReRendering = type !== '[object Object]' && type !== '[object Undefined]';    // 是否重新渲染
        if (isReRendering) {
 this.renderTo = typeof data === "string" ? document.querySelector(data) : data
 this._hidden = this.renderTo.querySelector('.omi_scoped__hidden_data')
 this.id = this._hidden.dataset.omiId
 this.data = JSON.parse(this._hidden.value)
        } else {
 this.data = data || {};    // 給實例添加 data屬性
 this._omi_server_rendering = componentOption.server;    // _omi_server_rendering屬性
 this.id = this._omi_server_rendering ? (1000000 + Omi.getInstanceId) : Omi.getInstanceId;    // id屬性
        };
        this.refs = {};
        this.children = ;    // 每個實例的孩子
        this.childrenData = ;    // 每個實例孩子的數據
        this.HTML = null;    // 實例中render返回的字符串
        this._addedItems = ;
        Omi.instances[this.id] = this;    // Omi._instanceId 對應 Component類的每個實例(或者子類)
        this.dataFirst = true;    

        this._omi_scoped_attr =  Omi.STYLESCOPEDPREFIX + this.id;    // 實例的樣式局部屬性 omi_scoped_ + id
        //this.BODY_ELEMENT = document.createElement('body')
        this._preCSS = null;    // 預設樣式
        this._omiGroupDataCounter = {}; 
        if (this._omi_server_rendering || isReRendering) {
 this.install
 this._render(true)
 this._childrenInstalled(this)
 this.installed
        }
    }

疑問3:style方法是幹啥的?

答: style方法返回一個css字符串,用來生成局部css的。style方法中可以寫各種語句哈。只要返回css字符串即可。

疑問4:render方法是幹啥的?

答:render方法返回一個html字符串,就是html組合好的標籤啦。render方法中可以寫各種語句哈。只要返回字符串即可。

疑問5:

那omi是怎麼幫我們生成局部css和html插入到指定的dom容器中呢。html內置的事件又是怎樣對應起Hello類中clickHandle方法?

答:這一切就得從Omi.render(hello, '#app'); 這個語句開始。

Omi.render = function(component , renderTo , incrementOrOption){    // 實例, 渲染到的dom, xx
        component.renderTo = typeof renderTo === "string" ? document.querySelector(renderTo) : renderTo;    // 實例的renderTo屬性
        if (typeof incrementOrOption === 'boolean') {
 component._omi_increment = incrementOrOption;    // 實例的_omi_increment 屬性(老版)
        } else if (incrementOrOption) {    // 新增
 component._omi_increment = incrementOrOption.increment;
 component.$store = incrementOrOption.store;
 if (component.$store) {
 component.$store.instances.push(component);
 };
 component._omi_autoStoreToData = incrementOrOption.autoStoreToData;
        };
        component.install;    // Component類的install方法(被實例繼承了)
        component._render(true);    // Component類的_render方法(被實例繼承了)
        component._childrenInstalled(component);
        component.installed;
        return component;
    }

render方法就omi上的一個靜態方法,接收3個參數(實例, 渲染到的dom, xx), xx表示我也不清楚這個變量幹啥的,以後肯定會知道的。 方法中的這個語句對於這個demo很重要component._render(true);接下來終點看看這個方法.

這個方法裡面對於這個demo最重要的語句就是 this._generateHTMLCSS; // 生成 html 和 css, 怎麼生成html和css呢,看如下代碼

    _generateHTMLCSS {    // 生成css 和 html
        this.CSS = (this.style|| '').replace(/<\/?style>/g,'');    // 處理下style方法中的字符串樣式
        if (this.CSS) {
 this.CSS = style.scoper(this.CSS, "[" + this._omi_scoped_attr + "]");    // 給css標籤搞成 (標籤名[omi_scoped_0], [omi_scoped_0] 標籤名) 變成屬性選擇器
 if (this.CSS !== this._preCSS && !this._omi_server_rendering) {    // 現在的CSS不等於_preCSS 且 _omi_server_rendering 為假值
 style.addStyle(this.CSS, this.id);    // 在head中添加局部css
 this._preCSS = this.CSS;    // 本次的css存一下
 };
        };
        let tpl = this.render;    // 用戶提供的html字符串
        this.HTML = this._scopedAttr(Omi.template(tpl ? tpl : "", this.data), this._omi_scoped_attr).trim;    // 給每個html元素添加omi_scoped_0 = '', 有模板數據的也計算好了. eg: <h1 omi_scoped_0 onclick="clickHandle(this, event)">aaahello world</h1>
        if (this._omi_server_rendering) {
 this.HTML = '\r\n<style id="'+Omi.STYLEPREFIX+this.id+'">\r\n' + this.CSS + '\r\n</style>\r\n'+this.HTML
 this.HTML += '\r\n<input type="hidden" data-omi-id="' + this.id + '" class="' + Omi.STYLESCOPEDPREFIX + '_hidden_data" value=\'' + JSON.stringify(this.data) + '\'  />\r\n'
        }
    }

局部css生成好了,並且添加到了head中了,html也生成好了,並且把data數據也生成好了,這裡使用的是Mustache模板。 那html中的聲明式事件綁定怎麼和clickHandle方法對應的呢,代碼如下:

// event.js 文件 正則不好,感興趣的可以研究一把
function scopedEvent(tpl,id) {    // 模板字符串, id
    return tpl.replace(/<[\s\S]*?>/g, function (item) {
        return item.replace(/on(abort|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|dblclick|drag|dragend|dragenter|dragleave|dragover|dragstart|drop|durationchange|emptied|ended|error|focus|input|invalid|keydown|keypress|keyup|load|loadeddata|loadedmetadata|loadstart|mousedown|mouseenter|mouseleave|mousemove|mouseout|mouseover|mouseup|mousewheel|pause|play|playing|progress|ratechange|reset|resize|scroll|seeked|seeking|select|show|stalled|submit|suspend|timeupdate|toggle|volumechange|waiting|autocomplete|autocompleteerror|beforecopy|beforecut|beforepaste|copy|cut|paste|search|selectstart|wheel|webkitfullscreenchange|webkitfullscreenerror|touchstart|touchmove|touchend|touchcancel|pointerdown|pointerup|pointercancel|pointermove|pointerover|pointerout|pointerenter|pointerleave|Abort|Blur|Cancel|CanPlay|CanPlayThrough|Change|Click|Close|ContextMenu|CueChange|DblClick|Drag|DragEnd|DragEnter|DragLeave|DragOver|DragStart|Drop|DurationChange|Emptied|Ended|Error|Focus|Input|Invalid|KeyDown|KeyPress|KeyUp|Load|LoadedData|LoadedMetadata|LoadStart|MouseDown|MouseEnter|MouseLeave|MouseMove|MouseOut|MouseOver|MouseUp|MouseWheel|Pause|Play|Playing|Progress|RateChange|Reset|Resize|Scroll|Seeked|Seeking|Select|Show|Stalled|Submit|Suspend|TimeUpdate|Toggle|VolumeChange|Waiting|AutoComplete|AutoCompleteError|BeforeCopy|BeforeCut|BeforePaste|Copy|Cut|Paste|Search|SelectStart|Wheel|WebkitFullScreenChange|WebkitFullScreenError|TouchStart|TouchMove|TouchEnd|TouchCancel|PointerDown|PointerUp|PointerCancel|PointerMove|PointerOver|PointerOut|PointerEnter|PointerLeave)=(('([\s\S]*?)')|("([\s\S]*?)"))/g, function (eventStr, b, c) {
 if (c.indexOf('Omi.instances[') === 1) {    // 聲明式事件函數如果包含Omi.instances[ 的話
 return eventStr;    // 那就直接返回 "onclick="Omi.instances[0].clickHandle(this, event)""
 } else if (c.lastIndexOf(')') === c.length - 2) {    // 找到c指定的)最後出現的位置 和 去掉2個"" 相等的話
 return eventStr.replace(/=(['|"])/, '=$1Omi.instances[' + id + '].');   // eg: "onclick="Omi.instances[0].clickHandle(this, event)""
 } else {
 let str = eventStr.replace(/=(['|"])/, '=$1Omi.instances[' + id + '].');
 return str.substr(0, str.length - 1) + "(event)" +  str.substr(str.length - 1, 1);
 };
        });
    });
}

內置事件也對應好了,那就把html插到指定的dom容器中去吧。

this.renderTo.innerHTML = this.HTML;

ps:

好了, hello world的這個demo就這麼說完了, 這個簡單的demo只是抽取了對應的源碼, 每個demo都將只抽取對應源碼, 等到所有demo寫完,omi源碼基本就明白是怎麼一回事了。

如果寫的有問題,歡迎指出,也可以進群(看開篇扯蛋的帖子)和原作者交流。哈哈

相關推薦

推薦中...