【Amaple教程】4. 組件

在Amaple單頁應用中,一個頁面其實存在兩種模塊化單位,分別是javascript

  1. 模塊 am.Module類),它是以web單頁應用跳轉更新爲最小單位所拆分的獨立塊;
  2. 組件 am.Component類),它的定位是擁有特定功能的封裝塊,就像由一堆代碼封裝成的具備特定功能的函數同樣,一個組件也有獨立的視圖、狀態數據對象、組件行爲以及生命週期。經常使用的組件有DialogBubbleNavigatorMenubar等。

在模塊中定義並使用一個簡單的組件衍生類

使用am.class類構造器繼承am.Component類定義一個組件,而繼承am.Component建立的類被稱爲 組件衍生類 ,你能夠這樣定義一個組件衍生類:css

// 在am.class函數的參數中指定該組件衍生類的類名,它返回指定名稱的組件衍生類
// 類名須遵循首字母大寫的駝峯式命名規範,如"BubbleDemo",不然將會報錯。但接收變量名沒有限制
var BubbleDemo = am.class ( "BubbleDemo" ).extends ( am.Component ) ( {

    // 在init函數返回該組件的狀態數據對象
    init : function () {
        return {
            bubbleText: "this is a component bubble"
        };
    },

    // 組件中必須定義render函數,在該函數中指定組件的template模板和樣式
    render : function () {
        this.template ( "<span>{{ bubbleText }}</span>" )
        .style ( {
            span: { background: "red", fontSize: 20, padding: "10px 16px" }
            // !注意:當元素選擇器爲符合變量命名規則時可不用引號,如上面選擇span元素時。當選擇器不符合變量名規則時需使用引號,如:
            // ".class-name": { fontSize: 15 }
            // "span #id": { margin-top: 24 }
        } );
        // this.template ( templateHTML )函數中傳入html字符串來定義該組件的視圖
        // this.style ( styleObj )函數爲該組件的視圖定義樣式,這些樣式也只做用於組件視圖
        // 需注意的是該函數傳入一個對象,對象屬性名爲css選擇器語法,值爲css樣式對象,樣式名也是使用駝峯式表示,樣式值爲量值時可直接寫爲數字
    }
} );

在一個模塊中使用 組件衍生類 渲染組件視圖也是很是簡單的,首先在am.startRouter函數中配置組件加載的baseURLhtml

am.startRouter ( {
    baseURL : {
        // ...

        // 爲組件文件設置base路徑,全部的組件文件請求路徑都將基於「/component」目錄,不設置時默認「/」
        component: "/component"
    },
    // ...
} );

而後在須要使用的模塊或組件中經過import函數引入,並在<template>中經過自動以標籤名來使用組件java

<template>
    <!-- 自定義標籤名爲該組件衍生類的類名以所有小寫的中劃線式規範轉換而來,而不是接收的變量名的轉換 -->
    <bubble-demo></bubble-demo>
</template>
<script>

    // 當你將上面的組件衍生類單獨編寫在src/component文件裏時,你須要使用「import ( componentFilePath )」來引入此組件,
    // 這樣在template模板中的<bubble-demo>元素就會解析爲組件模板「<span>this is a component bubble</span>」了。
    // 引入時可省略「.js」文件後綴
    var BubbleDemo = import ( "BubbleDemo" );

    // 固然你也能夠直接在模塊中編寫使用一個組件,像這樣:
    // var BubbleDemo = am.class ( "BubbleDemo" ).extends ( am.Component ) ( ... );
    new am.Module ( {
        init : function () { ... }
    } );
</script>

組件生命週期

與模塊生命週期階段數同樣,一個組件從建立到卸載也分爲5個階段的生命週期,具體以下:web

  • init:組件初始化時觸發,它返回組件<template>模板解析與掛載所使用的狀態數據。init函數內可調用this.propsType函數進行props參數驗證。
props相關知識將在本章節的後面部分介紹
  • render:渲染組件視圖時觸發,該生命週期函數內可分別調用this.template函數定義視圖標籤的字符串,和this.style函數爲組件視圖添加樣式
  • mounted:解析並掛載狀態數據到組件視圖後觸發,你能夠在此函數中處理一些視圖解析完成後的操做,如爲此組件請求網絡數據並更新到模板等
  • updated:當組件在頁面中的位置改變時觸發,在組件上使用:for指令時將會渲染多個組件,此時改變:for指令所綁定的狀態數組時將可能改變組件的位置
  • unmount:組件卸載時觸發,有兩種狀況將會卸載組件:
    ①. 經過:for指令渲染多個組件視圖後,調用綁定的狀態數組的變異函數均可能在頁面上卸載一個或多個組件;
    ②. 當一個模塊或組件被卸載時,該模塊或組件中的組件也將會卸載。

組件行爲

咱們已經知道組件是一個擁有特定功能的封裝塊,因此它會有本身特定的 組件行爲 ,如Dialog組件有打開和關閉行爲,輪播圖組件有翻頁行爲等。你能夠這樣定義 組件行爲 正則表達式

var Dialog = am.class ( "Dialog" ).extends ( am.Component ) ( {
    init : function () {
        return { open: false, text: "" };
    },
    render : function () {
        this.template ( [
            '<div :if="open">',
                '<span>{{ text }}</span>',
            '</div>'
        ].join ( "" ) );
    },

    // 添加action成員函數,該函數返回組件行爲的函數集合對象,該對象被稱爲組件行爲對象
    // action函數的this指針也是指向該組件對象自己
    action : function () {
        var _this = this;
        return {

            // 組件行爲函數的this指針不會指向任何值
            // 經過state.open來控制Dialog視圖的隱藏與顯示
            open: function ( text ) {
                _this.state.text = text;
                _this.state.open = true;
            },
            close: function () {
                _this.state.open = false;
            }
        };
    }
} );

# 組件行爲的兩種使用方法

  • [1]在組件的生命週期函數mountedupdateunmount中可經過this.action使用組件行爲對象;
  • [2]在組件元素上使用:ref指令,調用module.refs函數獲取組件引用時將返回該組件的組件行爲對象。

嵌套組件

組件與組件之間配合使用能夠發揮更強大的組件能力,在一個組件的<template>模板中能夠嵌套其餘組件,你能夠這樣寫:segmentfault

// ComponentB組件依賴ComponentA組件
// ComponentA組件的編寫與普通組件編寫相同,這裏省略
var CompoenntB = am.class ( "CompoenntB" ).extends ( am.Component ) ( {

    // 在構造函數中經過this.depComponents來指定該組件的依賴組件數組
    constructor : function () {
    
        // 和ES6的class關鍵字定義類同樣,在構造函數中需首先調用super()函數,不然將會拋出錯誤
        this.__super ();
        this.depComponents = [ ComponentA ];
    },
    init : function () { ... },
    render : function () {
        this.template ( "<component-a></component-a>" );
    }
} );

ComponentAComponentB組件都編寫在單獨的文件中時,你須要在模塊中同時引入 組件 嵌套組件 ,像這樣:數組

<template>...</template>
<script>

    // 在ComponentB組件中只需經過this.depComponents = [ ComponentA ]指定它所依賴的組件便可,而後在使用的模塊中統一引入這些組件文件
    // 由於ComponentB組件依賴ComponentA組件,因此需在ComponentB以前引入ComponentA
    // 此時ComponentA組件就能夠被ComponentB所獲取到
    var ComponentA = import ( "component/ComponentA" );
    var ComponentB = import ( "component/ComponentB" );

    new am.Module ( ... );
</script>

組件與組件、組件與模塊之間的通訊

組件做爲一個單獨的封裝塊,它必須與其餘組件或模塊進行通訊,你能夠在模塊中分發數據到不一樣組件,也能夠在組件中分發數據到嵌套組件中。在組件中可使用props進行數據的通訊,使用subElements進行html模板塊分發。網絡

# 使用props傳遞靜態值

<template>
    <!-- 在組件元素上定義任何非指令屬性(屬性名不爲「:」開頭),它都會被當作props屬性傳入組件中 -->
    <dialog text="this is a external message!"></dialog>
</template>
<script>
    am.class ( "Dialog" ).extends ( am.Component ) ( {
        init : function () {

            // 在組件中使用this.props接收外部傳入的數據
            // this.props.text的值爲"this is a external message!",即外部傳入的字符串
            return { text: this.props.text };
        },
        // ...
    } );

    new am.Module ( ... );
</script>

# 使用props傳遞動態值

props還支持使用插值表達式的方式傳遞狀態數據,這被稱爲 動態props 。動態props將建立一個對外部狀態數據的代理屬性,當在組件內更改了此代理屬性時,外部對應的狀態數據也將同步更新。以下:dom

  • 在使用Dialog組件的視圖中,將狀態屬性text傳入組件後,組件的this.props.text即爲該狀態屬性的代理屬性。
<template>
    <dialog text="{{ text }}"></dialog>
</template>
<script>
    new am.Module ( {
        init : function () {
            text: "this is a external message!"
        },
        // ...
   } );
</script>
  • Dialog組件的代碼中,可經過this.props.text獲取外部傳遞的text狀態屬性。
am.class ( "Dialog" ).extends ( am.Component ) ( {
    init : function () {
        return {

            // 使用text1接收並使用this.props.text的值
            text1: this.props.text,

            // 若是你但願更新外部的text屬性後,組件視圖中掛載了this.props.text數據的地方也同步更新,
            // 你能夠在組件中建立一個計算屬性做爲this.props.text的代理,以下建立的text2計算屬性:
            computed: {
                var _this = this;
                text2: {
                    get: function () {
                        return _this.props.text;
                    },
                    set: function ( newVal ) {
                        _this.props.text = newVal;
                    }
                }
                // 由於組件內對this.props.text的值更新後,外部的text狀態屬性也會同步更新,反之也成立
                // 這樣在組件視圖中掛載text2就等於掛載props.text
                // 此時需注意的是,更改text2的值也將同步更改外部text屬性的值
            }
        };
    },
    // ...
} );

# props驗證

當你但願開放你所編寫的組件給其餘開發者使用時,你不肯定其餘開發者傳入的props參數是否符合組件內的處理要求,此時你能夠爲你的組件設置props數據驗證,你能夠在組件的init函數內調用this.propsType函數進行驗證:

am.class ( "Dialog" ).extends ( am.Component ) ( {
    init : function () {

        // 每項值的驗證均可以設置validate、require和default屬性
        this.propsType ( {
            text: {
                validate: String,  // 表示當傳入text值時它必須爲字符串
                require: true,  // 表示text參數爲必須傳入的參數,默認爲false
                default: "Have no message"   // 表示不傳入text參數時的默認值,默認值不會參與props驗證,不指定default時無默認值
                // validate的值能夠有四種類型的參數,分別爲:
                // ①. 基礎數據構造函數,分別有String、Number、Boolean三種基本數據類型構造函數,Function、Object、Array三種引用類型構造函數,
                //     以及undefined和null,它表示容許傳入的數據類型
                // ②. 正則表達式,如/^1\d{10}$/表示只容許傳入一個手機號碼
                // ③. 函數,它接收此props參數值,必須返回true或false表示是否經過驗證,如:
                // function ( text ) { return text.length > 5 }
                // ④. 數組,數組內是以上三種值的組合,經過數組內任意一項驗證均可以經過,至關於「||」運算符

            }

            // 當text屬性驗證只要設置validate屬性時,可直接以下縮寫:
            // text: String
        } );

        return { text: this.props.text };
    },
    // ...
} );

使用subElements分發html片斷

若是你想開發一個更加通用的Dialog組件,你應該不但願Dialog的視圖佈局是固定不變的,而是能夠根據不一樣的需求自定義Dialog視圖,由於這樣才顯得更加靈活多變,組件的subElements就是用來解決這個問題的,它可使你從組件外部傳入html片斷與組件視圖混合:

<dialog>
    <!-- <span>的內容會被做爲html片斷傳入組件內 -->
    <span>this is external HTML template</span>
</dialog>

而後在組件內經過subElements屬性獲取外部傳遞的視圖,並插入到組件視圖中的任意位置。subElement接收的視圖可分爲 默認subElements subElements的單數分塊 subElements的不定數分塊 三種形式。

# 默認subElements

在組件元素中傳入html片斷時,組件內將會建立一個默認的subElements局部變量,你能夠在組件內的模板中經過{{ subElements.default }}插入此html片斷:

am.class ( "Dialog" ).extends ( am.Component ) ( {
    init : function () { ... },
    render : function () {

        // {{ subElements.default }}將會輸出外部傳入的「<span>this is external HTML template</span>」
        this.template ( "<div>{{ subElements.default }}</div>" );
    }
    // ...
} );

Dialog組件將會被渲染成:

<div>
    <span>this is external HTML template</span>
</div>
【注意】分發的html片斷也可使用模板指令與組件,此html片斷解析時掛載的數據來源是組件外部的狀態數據,以下:
<template>
     <dialog>
          <!-- {{ text }}的數據來源是此模塊的狀態 -->
          <!-- 它就像JavaScript中傳入某個函數內的回調函數,該回調函數可對外部數據訪問而不是函數內 -->
          <span>{{ text }}</span>
     </dialog>
</template>
<script>
     new am.Module ( {
          init : function () {
               return { text: "this is external HTML template" };
          },
          // ...
     } );
</script>

# subElements的單數分塊

若是你但願開發的Dialog分爲頭部和內容兩部分視圖,再混合到組件模板的不一樣位置,subElements也容許你這樣編寫html片斷:

<dialog>
    <header>
        <span>this is a title</span>
    </header>
    <content>
        <span>this is external HTML template</span>
    </content>
</dialog>

<header><content>將分發的代碼塊分爲了兩部分,你能夠在組件視圖中分別將它們插入到不一樣的位置,只需在組件中分別定義HeaderContent兩個 組件子元素

am.class ( "Dialog" ).extends ( am.Component ) ( {
    init : function () { ... },
    render : function () {

        // 指定分塊的組件子元素名
        // 組件子元素名也需遵循首字母大寫的駝峯式規則,在組件元素內使用時也是所有小寫的中劃線式規範
        this.subElements ( "Header", "Content" )
        .template ( [
            "<div>",
                "<div>{{ subElements.Header }}</div>",
                "<div>{{ subElements.Content }}</div>",
            "</div>"
        ].join ( "" ) );
        // <header>、<content>兩個子元素只能做爲<dialog>子元素使用
        // 組件模板中分別經過subElements.Header和subElements.Content輸出對應的html分塊片斷
    }
    // ...
} );

此時Dialog組件將會被渲染成:

<div>
    <div><span>this is a title</span></div>
    <div><span>this is external HTML template</span></div>
</div>
【注意】①. 如沒有在 this.subElements函數中定義相應的組件子元素時,Amaple只會將它們做爲普通dom元素對待。
②. 除 <header><content>外的其餘html片斷會自動包含在 subElements.default中。

# subElements的不定數分塊

subElements的分塊分發可能會讓你想到不少原生的元素,如<ul><li><select><option><table><tr><td>等,他們都屬於包含與被包含的關係,但你會發現其實<ul>中能夠定義一個或多個,在subElements中你也能夠定義一個 組件子元素 的不定數分塊,如Grid組件(網格)可包含不定個數的GridItem

<grid>
    <grid-item>a</grid-item>
    <grid-item>b</grid-item>
    <grid-item>c</grid-item>
</grid>

在組件中這樣定義不定數分塊的subElements

am.class ( "Grid" ).extends ( am.Component ) ( {
    init : function () { ... },
    render : function () {
        this.subElements ( { elem: "GridItem", multiple: true } )
        .template ( [
            "<ul>",
                "<li :for='item in subElements.GridItem'>{{ item }}</li>",
            "</ul>"
        ].join ( "" ) )
        .style ( ... );
        // 此時局部變量subElements.GridItem爲一個包含全部GridItem分塊片斷的數組,在組件內使用:for指令循環輸出,
        // 也可使用數組索引如subElements.GridItem [ 0 ]
        
        // 其實上面定義單數分塊的Header的全寫是{ elem: "Header", multiple: false },但它可縮寫爲"Header"
    }
    // ...
} );

繼續學習下一節:【AmapleJS教程】5. 插件
也可回顧上一節:【AmapleJS教程】3. 模板指令與狀態數據(state)

相關文章
相關標籤/搜索