Ques前端組件化體系

Ques是一套組件化系統,解決如何定義、嵌套、擴展、使用組件。javascript

傳統開發模式的痛點

  • 沒法方便的引用一個組件,須要分別引用其JavascriptTemplateCSS文件
  • 咱們指望能以MV*的方式去寫代碼,結果發現只有JavascriptMV*
  • UI庫打包成一坨(相似Bootstrap),可是實際上UI庫伴隨產品迭代在反覆變動,每次打開網站,用戶依然反覆下載UI庫
  • CSS沒有命名空間致使兩個組件容易衝突
  • 組件沒法嵌套或者沒法擴展,因此實際上組件根本沒法複用
  • 組件沒法複製後可用,在組件沒法嵌套或沒法擴展的狀況下,連定製一個組件都困難連連
  • 每次性能優化都將代碼弄的很噁心,很差維護

UI組件

UI組件使用來專門作UI的組件,其特色爲只有模版、樣式文件。系統會根據用戶在頁面已使用的UI組件動態引用其依賴的資源。注意,UI組件的前綴必須是ui-css

下面是一個簡單的ui-button的例子:html

定義
  • CSS文件
.ui-button {
    display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; touch-action: manipulation; cursor: pointer; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 4px; text-transform: none; -webkit-appearance: button; overflow: visible; margin: 0; } .ui-default { color:#333; background-color:#fff; border-color:#ccc } .ui-default.focus,.ui-default:focus { color:#333; background-color:#e6e6e6; border-color:#8c8c8c } .ui-default:hover { color:#333; background-color:#e6e6e6; border-color:#adadad } // 後面可添加更多樣式的按鈕
  • 模版文件
<button class="ui-button"> <content></content> </button>
效果
  • 在頁面上直接引用:
<ui-button class="ui-default">DEFAULT</ui-button> <ui-button class="ui-success">SUCCESS</ui-button> <ui-button class="ui-info">INFO</ui-button> <ui-button class="ui-warning">WARNING</ui-button> <ui-button class="ui-danger">DANGER</ui-button>
  • 展現

這樣咱們就實現了一個ui-button組件,其能夠在任意其餘組件中嵌套使用。其以來的資源會動態引用,也就是說,只有咱們使用了ui-button他的模版和樣式纔會被引入。java

備註
  • 因爲咱們使用了強大的cssnext,因此CSS吐出來的時候會根據配置轉換成兼容版本,也就是說咱們只須要按照標準去寫CSS,系統會自動幫咱們適配:
.ui-button {
    display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 4px; text-transform: none; -webkit-appearance: button; overflow: visible; margin: 0; } .ui-default { color:#333; background-color:#fff; border-color:#ccc } .ui-default.focus,.ui-default:focus { color:#333; background-color:#e6e6e6; border-color:#8c8c8c } .ui-default:hover { color:#333; background-color:#e6e6e6; border-color:#adadad }
  • 注意到咱們引用了Shadow DOM中的<content>標籤,<content>標籤做爲Component內部的插入點(或者能夠理解成佔位符),當外部引用該Component時能夠從外部向內部插入節點,例如:
<ui-button class="ui-default">DEFAULT</ui-button>

則表示向Component的插入點插入DEFAULT這個文字節點。關於<content>標籤咱們後面還會提到其高級應用。jquery

Component

Component是最多見的組件,其擁有模版、樣式以及邏輯文件,使得這種Component更像一個自定義的元素(Custom Element)。體驗上像引入一個<input>標籤同樣,咱們能夠設置她的值,綁定她的事件,調用她的函數。git

下面是一個dialog組件的例子:github

定義
  • CSS文件:
.$__mask {
    position: fixed; width: 100%; height: 100%; padding: 0px; margin: 0px; left: 0px; top: 0px; z-index: 999; background-color: rgba(0,0,0,.6) !important; display: none; } .$__mask.show { display: block; } .$__$ { position: fixed; top: 10%; opacity: .5; left: 50%; width: 490px; margin-left: -245px; z-index: 999; background: #fff; font-size: 14px; border-radius: 4px; overflow: hidden; transition: all 200ms ease-in-out; } .$__mask .$__$.show { top: 50%; opacity: 1; } .$__$ .header { height: 30px; line-height: 30px; text-indent: 12px; background: #039ae3; color: #fff; font-size: 14px; } .$__$ .body { padding: 30px 40px; position: relative; line-height: 24px; max-height: 500px; overflow-y: auto; overflow-x: hidden; } .$__$ .msg { margin-left: 66px; position: relative; top: 10px; word-break: break-all; } .$__$ .bottom { margin: 20px; text-align: right; } .icon-info-large { background: url(http://9.url.cn/edu/img/sprite/common.a8642.png) -41px -276px no-repeat; width: 36px; height: 36px; display: block; float: left; margin-top: 4px; }
  • 模版文件:
<div class="$__mask" q-class="show: isShow"> <div class="$__$"> <div class="header"> <content select="header *"></content> </div> <div class="body"> <div class="icon-info-large"></div> <div class="msg"> <content select="article *"></content> </div> </div> <div class="bottom"> <ui-button class="ui-info" q-on="click: submit">肯定</ui-button> <ui-button class="ui-default" q-on="click: cancel">取消</ui-button> </div> </div> </div>
  • Javascript文件:
var $ = require('jquery'); module.exports = { data: { isShow: false }, methods: { submit: function () { this.$emit('submit'); }, cancel: function () { this.$emit('cancel') .hide(); }, show: function () { this.$set('isShow', true); }, hide: function () { this.$set('isShow', false); } }, ready: function () { var dialog = $('.$__$', this.$el); this.$watch('isShow', function (val) { if (val) { setTimeout(function () { dialog.addClass('show'); }, 20); } else { dialog.removeClass(dialog, 'show'); } }, false, true); } }
效果
  • 在頁面直接引入<dialog>
<dialog id="my-dialog"> <header> 歡迎使用Ques </header> <article> Hello World! </article> </dialog>
  • 則能夠在Controller中直接使用,例如拿到其實例,再調用其show方法,將其展現:
var Q = require('Q'); Q.get('#my-dialog') .show();
  • 展現

備註
  • 因爲CSS沒有命名空間,因此咱們引入了兩個$__$__$兩個佔位符來充當命名空間,系統會自動轉換成當前Component的名字,因此CSS最終變成:
.dialog__mask {
    position: fixed; width: 100%; height: 100%; padding: 0px; margin: 0px; left: 0px; top: 0px; z-index: 999; background-color: #000000 !important; background-color: rgba(0,0,0,.6) !important; display: none; } .dialog__mask.show { display: block; } .dialog { position: fixed; top: 10%; opacity: .5; left: 50%; width: 490px; margin-left: -245px; z-index: 999; background: #fff; font-size: 14px; border-radius: 4px; overflow: hidden; -webkit-transition: all 200ms ease-in-out; transition: all 200ms ease-in-out; } .dialog__mask .dialog.show { top: 50%; opacity: 1; } .dialog .header { height: 30px; line-height: 30px; text-indent: 12px; background: #039ae3; color: #fff; font-size: 14px; } .dialog .body { padding: 30px 40px; position: relative; line-height: 24px; max-height: 500px; overflow-y: auto; overflow-x: hidden; } .dialog .msg { margin-left: 66px; position: relative; top: 10px; word-break: break-all; } .dialog .bottom { margin: 20px; text-align: right; } .icon-info-large { background: url(http://9.url.cn/edu/img/sprite/common.a8642.png) -41px -276px no-repeat; width: 36px; height: 36px; display: block; float: left; margin-top: 4px; }

能夠看見$__被轉換成了dialog__,而$__$被轉換成了dialogweb

  • 邏輯層咱們使用了MVVM庫Q.js,這裏就不細說了。ajax

  • 這裏還用到<content>標籤的高級功能,select屬性。select屬性是用來選擇外部符合選擇器的節點進行替換。例如:json

<content select="header *"></content>

的意思是選擇外部<header>標籤內全部東西進行替換,因此「歡迎使用Ques」就被替換進去了。

第三方Component

第三方Component是一套兼容方案,使得業務方不依賴Q.js也能定義一個Component,可是因爲系統沒法控制第三方組件DOM的做用域,以及實現其擴展彷佛沒啥意思,因此第三方沒法嵌套和擴展。總的來講第三方Component本質上就是系統給第三方一個容器,他在裏面幹什麼,系統就無論了。第三方組件必定以third-爲前綴。

下面是一個高亮代碼third-code的例子:

定義
  • CSS文件:
.$__pre {
    width: 900px; margin: 10px; } /** 引入hightlight.js的css庫 **/ @import "http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/default.min.css";
  • 模版文件:
<pre class="$__pre"> <code> <content></content> </code> </pre>
  • Javascript文件:
module.exports = { bind: function () { var el = this.el, script = document.createElement('script'); script.onload = function () { hljs.highlightBlock(el); } script.src = '//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js'; document.body.appendChild(script); }, unbind: function () {} };
效果
  • 引入third-code
<third-code> &lt;ui-button class=&quot;ui-default&quot;&gt;DEFAULT&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-success&quot;&gt;SUCCESS&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-info&quot;&gt;INFO&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-warning&quot;&gt;WARNING&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-danger&quot;&gt;DANGER&lt;/ui-button&gt; </third-code>
  • 效果:

備註
  • 第三方Component須要實現兩個接口bindunbind,即在容器建立時須要綁定什麼,當容器刪除時須要解綁什麼。this會帶來必要的東西,例如容器、父級ViewModel等等。

組件嵌套

當組件能夠嵌套,組件件能夠拆成較小的顆粒,使得複用性大大提高。

下面咱們是一個codeclick組件,其用途是高亮展現代碼片斷,點擊代碼彈出dialog,也就是說咱們準備嵌套上面作出來的third-codedialog組件:

定義
  • CSS文件:
/** 能夠給組件定義一些特殊樣式,但爲了簡單咱們什麼也不作 **/
  • 模版文件:
<div>
    <third-code q-ref="code">
        <content></content>
    </third-code>
    <dialog q-ref="dialog">
        <header>歡迎使用Ques</header>
        <article>你點擊了代碼</article>
    </dialog>
</div>
  • Javascript文件:
var $ = require('jquery'); module.exports = { data: {}, ready: function () { var code = this.$.code, dialog = this.$.dialog; // 點擊代碼,彈出dialog $(code.el).on('click', function () { dialog.show(); }); } };
效果
  • 在頁面上引用:
<codeclick>
    &lt;ui-button class=&quot;ui-default&quot;&gt;DEFAULT&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-success&quot;&gt;SUCCESS&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-info&quot;&gt;INFO&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-warning&quot;&gt;WARNING&lt;/ui-button&gt; &lt;ui-button class=&quot;ui-danger&quot;&gt;DANGER&lt;/ui-button&gt; </codeclick>
  • 展現:

備註
  • 咱們看到<content>標籤另外一個神奇的用法是可傳遞,咱們從third-code傳遞到codeclick,再傳遞到最外部。使得咱們能夠在最外部改third-code內部的節點。

  • 咱們注意到q-ref原本是Q.js用於組件嵌套從母Component(爲了和擴展中的父Component其分開來,這裏稱之爲母Component)拿到子Component的引用,一樣能夠拿到第三方Component的引用。

組件擴展

組件可擴展,則差異不大的組件能夠繼承同一個父組件。

下面dialog組件擴展的例子,效果是彈出一個dialog,要求輸入內容:

定義
  • CSS文件:
/** 一樣爲了簡單咱們什麼也不作 **/
  • 模版文件:
<dialog extend> <header> <h2>歡迎使用Ques</h2> </header> <article> <p>請輸入要設置的值</p> <ui-input value="" q-model="curVal" q-on="keyup: submit | key enter" q-focus="focus"></ui-input> </article> </dialog>
  • Javascript文件:
var filters = require('filters'); module.exports = { methods: { submit: function () { if (!this.curVal) { this.$set('focus', true); } else { this.$emit('submit', this.curVal); this.$set('curVal', ''); this.hide(); } }, show: function () { // call super.show this.constructor.super.options.methods.show.call(this); this.$set('focus', true); } }, directives: { focus: function (val) { val && this.el.focus(); } }, filters: { key: filters.key } };
效果
  • 在頁面上引用inputval
<inputval id="my-dialog"></inputval>
  • 在Controller調用其show方法:
var Q = require('Q'); Q.get('#my-dialog').show();
  • 則頁面彈出一個彈出,要求輸入值:

備註
  • 這裏咱們引入extend屬性,用於表示該組件繼承哪一個組件。

  • 咱們還複寫了dialogsubmitshow方法,而且能夠調用其父Componnet的方法,如:

this.constructor.super.options.methods.show.call(this);

嵌套使用擴展組件

咱們能夠嵌套使用擴展後的組件。

下面是一個複雜的例子,一個按鈕點擊後彈出inputval,輸入後alert出輸入的內容。

定義
  • CSS文件
.$__anchor {
    padding: 13px 35px 17px;
    box-shadow: inset 0 -4px 0 #2a6496;
    border: 0;
    color: #fff;
    transition: all .2s ease-in-out;
    background-color: #337ab7;
    border-color: #2e6da4;
    display: block;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    touch-action: manipulation;
    cursor: pointer;
    user-select: none;
    border-radius: 4px;
    text-decoration: none;
    margin: 0 auto;
}

.$__anchor:hover,
.$__anchor:active
.$__anchor:focus {
    background-color: #286090;
    border-color: #204d74;
}
  • 模版文件:
<div>
    <a href="javascript:void(0);" class="$__anchor" q-on="click: setMessage"> <content></content> </a> <inputval q-ref="input"></inputval> </div>
  • Javascript文件:
module.exports = { data: {}, methods: { setMessage: function (e) { var self = this; this.$.input.$once('submit', function (value) { value && alert('輸入了:' + value); }); this.$.input.show(); } } };
效果
  • 在頁面引用:
<clkme>請點擊我</clkme>
  • 效果:

DIY組件

DIY組件容許頁面經過Markup的方法引用NodeJS組件,這樣咱們可使用該NodeJS組件分析咱們的代碼來作一些神奇的事情。

例如咱們提供的diy-preload組件提供的CGI預加載方案,整個過程沒有改變開發人員的編碼體驗,只是簡簡單單標記一下哪一個CGI須要預加載,則系統會自動預加載CGI。

使用
  • 在頁面引入diy-preload組件
<diy-preload></diy-preload>
  • 在頁面對應的數據層配置文件,這裏咱們的規範是該文件名爲db.*.js,的對應CGI設置成preload = true:
    var DB = require('db'); DB.extend({ ke: DB.httpMethod({ url: 'http://ke.qq.com/cgi-bin/index_json', type: 'JSONP', preload: true }) }) module.exports = DB;
  • diy-preload組件會找到db.*.js,而後分析出什麼CGI須要預加載,在<diy-preload>標籤對應的位置插入預加載腳本。整個過程開發人員感知不到,體驗上仍是調取一個CGI,可是實際上在頁面文檔打開後CGI請求馬上發出,而不是等待Javascript加載完後才發出。
相關文章
相關標籤/搜索