avalon2學習教程13組件使用


avalon2最引以為豪的東西是,終於有一套強大的類Web Component的組件系統。這個組件系統媲美於React的JSX,並且能更好地控制子組件的傳參。

avalon自誕生以來,就一直探索如何優雅的定義組件使用組件。從avalon1.4的ms-widget,到avalon1.5的自定義標簽。而現在的版本恰好是它們的結合體,並從web component那里借鑒了slot插入點機制及生命周期管理,從react那里抄來了render字符串模板。

在avalon1.4中,ms-widget指令的值是一個字符串,使用逗號隔開幾個有限的元消息

<div ms-widget="widgetType, widgetVmID, widgetOption"></div>

avalon1.5中,改成自定義標簽做載體,使用config對象屬性作為widgetOption, 使用$id或identifier屬性來指定組件VM的$id, 使用標簽名來指定組件的類型。

  <ms:button ms-repeat="array" ms-attr-config="x{{$index}}"></ms:button>

此外還有其他夾七夾八的東西,功能更強大了,但上手更難了。

現在細細回想起來,其中重要的配置項就只有兩個組件的ID,組件的類型。其他的配置項需要用更優雅的方式加入去。幸好在開始寫新組件指令前,我已經解決了。大家可以回去看一下, ms-attr, ms-css. 讓指令的屬性值以對象或對象數組的形式存在,不就能放許多東西嗎。

  <xmp ms-widget="@obj"></xmp>
  <xmp ms-widget="{is:'panel',$id:'aaa', title:@title}"></xmp>
  <xmp ms-widget="[{is:'panel',$id:'aaa', title:@title},@otherConfig,@thirdConfig]"></xmp>

其次是生命周期。avalon2的組件生命周期更完善。

從上表可以看到,avalon2與Web Component的生命周期很相近了。

  1. onInit,這是組件的vm創建完畢就立即調用時,這時它對應的元素節點或虛擬DOM都不存在。只有當這個組件里面不存在子組件或子組件的構造器都加載回來,那么它才開始創建其虛擬DOM。否則原位置上被一個注釋節點占着。

  2. onReady,當其虛擬DOM構建完畢,它就生成其真實DOM,並用它插入到DOM樹,替換掉那個注釋節點。相當於其他框架的attachedCallback, inserted, componentDidMount.

  3. onViewChange,當這個組件或其子孫節點的某些屬性值或文本內容發生變化,就會觸發它。它是比Web Component的attributeChangedCallback更加給力。

  4. onDispose,當這個組件的元素被移出DOM樹,就會執行此回調,它會移除相應的事件,數據與vmodel。

我們再來看一下如何定義組件。上面只是說如何添加配置項。onInit, onReady, onViewChagne, onDispose只是其中的四個配置項。

avalon2 的默認配置項比avalon1.5 少許多。

  1. is, 字符串, 指定組件的類型。如果你使用了自定義標簽,這個還可以省去。
  2. $id, 字符串, 指定組件vm的$id,這是可選項。
  3. define, 函數, 自己決定如何創建vm,這是可選項。
  4. onInit, onReady, onViewChange, onDispose四大生命周期鈎子。

然后就沒有了, 沒有$replace, $slot, $template, $extend, $container, $construct, $$template 這些怪怪的東西。

說起自定義標簽。之前1.5為了兼容IE6-8,是使用舊式的帶命名空間的標簽作為容器,而Web Component則是使用中間帶杠的標簽,如<ms-button>,風格大相徑庭。顯然后者是主流,是未來!

經過一番研究,發掘出三大標簽作為組件定義時的容器。

xmp, wbr, template

xmp是閉合標簽,與div一樣,需要寫開標簽與閉標簽。但它里面的內容全部作為文本存在,因此在它里面寫帶杠的自定義標簽完全沒問題。並且有一個好處時,它是能減少真實DOM的生成(內部就只有一個文本節點)。

<xmp ms-widget="@config"><ms-button ms-widget="@btn1"><ms-button><div></div><ms-tab ms-widget="@tab"><ms-tab></xmp>

wbr與xmp一樣,是一個很古老的標簽。它是一個空標簽,或者說是半閉合標簽,像br, area, hr, map, col都是空標簽。我們知道,自定義標簽都是閉合標簽,后面部分根本不沒有攜帶更多有用的信息,因此對我們來說,沒多大用處。

<wbr ms-widget="@config" />

template是HTML5添加的標簽,它在IE9-11中不認,但也能正確解析得出來。它與xmp, wbr都有一個共同特點,能節省我們定義組件時頁面上的節點規模。xmp只有一個文本節點作為孩子,wbr沒有孩子,template也沒有孩子,並且用content屬性將內容轉換為文檔碎片藏起來。

<template ms-widget="@config" ><ms-dialog ms-widget="@config"></ms-dialog></template>

當然如果你不打算兼容IE6-8,可以直接上ms-button這樣標簽。自定義標簽比起上面三大容器標簽,只是讓你少寫了is配置項而已,但多寫了一個無用的閉標簽。

<ms-dialog ms-widget="@config" ><ms-panel ms-widget="@config2"></ms-panel></ms-dialog>
<!--比對下面的寫法-->
<xmp ms-widget="@config" ><wbr ms-widget="@config2"/></xmp>

如果你想在頁面上使用ms-button組件,只能用於以下四種方式

<!--在自定義標簽中,ms-widget不是必須的-->
<ms-button></ms-button>
<!--下面三種方式,ms-widget才是存在,其中的is也是必須的-->
<xmp ms-widget='{is:"ms-button"}'></xmp>
<wbr ms-widget='{is:"ms-button"}'/>
<template ms-widget='{is:"ms-button"}'></template>

在JS中,我們是這樣使用它

<!DOCTYPE html>
<html>
    <head>
        <title>ms-validate</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
        <script src="../dist/avalon.js"></script>
        <script>
            var vm = avalon.define({
                $id: 'test',
                button: {//注意這里不能以 $開頭
                    buttonText: "VM內容"
                }
            })

            avalon.component('ms-button', {
                template: '<button type="button"><span><slot name="buttonText"></slot></span></button>',
                defaults: {
                    buttonText: "默認內容"
                },
                soleSlot: 'buttonText'
            })

        </script>
    </head>

    <body ms-controller="test">
    <!--在自定義標簽中,ms-widget不是必須的-->
    <ms-button ></ms-button>
    <!--下面三種方式,ms-widget才是存在,其中的is也是必須的-->
    <xmp ms-widget='{is:"ms-button"}'></xmp>
    <wbr ms-widget='{is:"ms-button"}'/>
    <template ms-widget='{is:"ms-button"}'></template>
</body>
</html>

圖片描述
但這樣我們就不好控制組件的更新。我們改一改。

<!DOCTYPE html>
<html>
    <head>
        <title>ms-validate</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
        <script src="../dist/avalon.js"></script>
        <script>
            var vm = avalon.define({
                $id: 'test',
                button: {//注意這里不能以 $開頭
                    buttonText: "按鈕內容"
                }
            })

            avalon.component('ms-button', {
                template: '<button type="button"><span><slot name="buttonText"></slot></span></button>',
                defaults: {
                    buttonText: "button"
                },
                soleSlot: 'buttonText'
            })

        </script>
    </head>

    <body ms-controller="test">
    <!--在自定義標簽中,ms-widget不是必須的-->
    <ms-button ms-widget="@button"></ms-button>
    <!--下面三種方式,ms-widget才是存在,其中的is也是必須的-->
    <xmp ms-widget='[{is:"ms-button"},@button]'></xmp>
    <wbr ms-widget='[{is:"ms-button"},@button]'/>
    <template ms-widget='[{is:"ms-button"},@button]'></template>
</body>
</html>

圖片描述
這樣我們直接操作 vm中的button對象中對應屬性就能更新組件了。這比原來avalon1.*好用一萬倍。

此外,avalon2還支持Web Components規范中所說的slot插入點機制,它是用來配置
一些字符串長度很長的屬性。比如說ms-tabs組件,通常有一個數組屬性,
而數組的每個元素都是一個很長的文本,用於以應一個面板。這時我們可以在自定義標簽的
innerHTML內,添加一些slot元素,並且指定其name就行了。

當我們不使用slot,又不願意寫面板內部放進vm時,你的頁面會是這樣的:

<ms-tabs ms-widget='{panels:[
"第一個面板的內部dfsdfsdfsdfdsfdsf",
"第二個面板的內部dfsdfsdfsdfdsfdsf"
"第三個面板的內部dfsdfsdfsdfdsfdsf"]  }'
></ms-tabs>

使用了slot后

<ms-tabs>
<div slot='panels'>第一個面板的內部dfsdfsdfsdfdsfdsf</div>
<div slot='panels'>第二個面板的內部dfsdfsdfsdfdsfdsf</div>
<div slot='panels'>第三個面板的內部dfsdfsdfsdfdsfdsf</div>
</ms-tabs>

而你的組件是這樣定義

<ms-tabs>
<slot name='panels'></solt>
<slot name='panels'></solt>
<slot name='panels'></solt>
</ms-tabs>

上面的div會依次替代slot元素。

此外,如果我們只有一個插槽,不想在頁面上slot屬性,那么可以在組件里使用soleSlot。

注意avalon.component的第二個參數,是一個對象,它里面有三個配置項,template是必須的, defaults、 soleSlot是可選的。

組件屬性的尋找順序,會優先找配置對象,然后是innerHTML,然后是defaults中的默認值.我們可以看一下測試

div.innerHTML = heredoc(function () {
            /*
             <div ms-controller='widget0' >
             <xmp ms-widget="{is:'ms-button'}">{{@btn}}</xmp>
             <ms-button>這是標簽里面的TEXT</ms-button>
             <ms-button ms-widget='{buttonText:"這是屬性中的TEXT"}'></ms-button>
             <ms-button></ms-button>
             </div>
             */
        })
        vm = avalon.define({
            $id: 'widget0',
            btn: '這是VM中的TEXT'
        })
        avalon.scan(div)
        setTimeout(function () {
            var span = div.getElementsByTagName('span')
            expect(span[0].innerHTML).to.equal('這是VM中的TEXT')
            expect(span[1].innerHTML).to.equal('這是標簽里面的TEXT')
            expect(span[2].innerHTML).to.equal('這是屬性中的TEXT')
            expect(span[3].innerHTML).to.equal('button')
            vm.btn = '改動'
            setTimeout(function () {
                expect(span[0].innerHTML).to.equal('改動')
                done()
            })
        })

生命周期回調的例子.
avalon是使用多種策略來監聽元素是否移除,確保onDispose回調會觸發!

<!DOCTYPE html>
<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="./dist/avalon.js"></script>
        <script>
avalon.component('ms-button', {
    template: '<button type="button"><span><slot name="buttonText"></slot></span></button>',
    defaults: {
        buttonText: "button"
    },
    soleSlot: 'buttonText'
})
            var vm = avalon.define({
                $id: 'widget0',
                config: {
                    buttonText: '按鈕',
                    onInit: function (a) {
                        console.log("onInit!!")
                    },
                    onReady: function (a) {
                        console.log("onReady!!")
                    },
                    onViewChange: function () {
                        console.log("onViewChange!!")
                    },
                    onDispose: function () {
                        console.log("onDispose!!")
                    }
                }
            })
            setTimeout(function () {
                vm.config.buttonText = 'change'
                setTimeout(function () {
                    document.body.innerHTML = ""
                }, 1000)
            }, 1000)

        </script>
    </head>

    <body>
        <div ms-controller='widget0' >
            <div><wbr ms-widget="[{is:'ms-button'},@config]"/></div>
        </div>
    </body>
</html>

avalon倉庫中有許多簡單的例子,大家可以下回來研究研究。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM