【Amaple教程】3. 模板指令與狀態數據(state)

一個模塊的template模板、JavaScript和css之間的關係其實能夠以下圖表示:
css

若是你瞭解Angular、Vue動態模板,那你將會對Amaple的模板感到很熟悉,在Amaple中,template模板也是基於模板指令和狀態數據的動態模板引擎,當一個狀態數據改變時,在template模板中與它綁定的dom元素將自動做出相應的更新,因此此時你只需關心模塊內的狀態數據,而不需去理會視圖層的更新。html

指令類型

指令分爲動態指令和靜態指令,動態指令的值會被看成JavaScript代碼被解析,因此它們能夠獲取並綁定狀態屬性的值,如:if:for等指令;而靜態屬性的值只會被當作普通的字符串處理,沒法綁定狀態屬性,如:module:ref指令。數據庫

使用插值表達式輸出文本

咱們直接先看看在index.html模板中使用插值表達式輸出文本和屬性,你應該會很快明白是怎麼回事了:segmentfault

<module>
    <template>
        <!-- 插值表達式由{{ }}來表示 -->
        <!-- 插值表達式可在dom元素內和元素屬性上進行狀態數據的綁定 -->
        <span>{{ text }}</span>

        <!-- 插值表達式也能夠多個插值表達式同時使用,或與固定字符串混合使用 -->
        <a href="{{ link }}?{{ search }}">direct to page about</a>
    </template>

    <script>

        // init函數將返回狀態屬性對象用於解析掛載模板數據
        new am.Module ( {
            init : function () {
                return {
                    text : "hello amaplejs,page index",
                    link : "/about",
                    search : "from=index"
                };
            },
            mounted : function () {
                
                // 經過this.state獲取全部的狀態屬性
                this.state.text = "text has changed";
                // this.state.text被賦值後模板中「<span>hello amaplejs,page index</span>」也將自動更新爲「<span>text has changed</span>」
                // 在queryUpdated、paramUpdated和unmount周期函數中也能夠經過此方法來獲取或賦值狀態屬性的值
            }
        } );
    </script>

    <style scoped>
        span { font-size: 20px; }
    </style>
</module>
【注意】①. <template>模板中的取值範圍爲當前模塊的狀態數據對象,在上面示例中,解析掛載時 {{ text }}被替換爲狀態數據的 text屬性值;
②. 插值表達式的 {{}}將被做爲JavaScript代碼解析,如你能夠這樣寫 {{ text === 1 ? "show" : "hidden" }},表示 text屬性值等於數字 1時輸出 "show",不然輸出 "hidden"

# 插值表達式在style與class屬性的特殊表現

插值表達式通常輸出字符串(狀態屬性值不爲字符串時將會調用該值的toString函數),但在style屬性上使用插值表達式時會將一個object對象轉換爲內聯樣式的格式,在class屬性上使用時會將一個array數組轉換爲以空格隔開的字符串:數組

<template>
    <div class="{{ clsList }}" style="{{ styleObj }}"><div>
</template>
<script>
    new am.Module ( {
        init : function () {
            return {
                
                // 在class屬性使用插值表達式輸出時自動轉換爲"cls1 cls2"
                clsList: [ "cls1", "cls2" ],

                // 在style屬性使用插值表達式輸出時自動轉換爲"background:red;font-size:20px"
                // 注意兩點:
                // ①.使用駝峯式來定義css屬性名,如fontSize,在輸出時將自動轉換爲font-size
                // ②.爲css屬性名賦值爲一個量值時可直接寫爲數字,如fontSize: 20,它將自動補充「px」單位
                styleObj: {
                    background: "red",
                    fontSize: 20
                }
            };
        },
    } );
</script>

循環渲染輸出dom元素

在實際項目中,常常會遇到根據數據庫的數據來渲染一個列表的需求,如用戶列表、使用表格展現數據等,此時咱們就須要使用模板指令:for來完成需求:緩存

<template>
    <ul>
        <!-- :for指令中只能使用兩種特定語法 -->
        <!-- 第一種爲「item in list」,item爲list循環時的每一項值,它是一個局部變量,只能在:for屬性所在的元素上及內部使用 -->
        <!-- 有時候咱們須要獲取遍歷時的索引,此時你能夠這樣寫「(item, i) in list」,這樣就能夠在:for屬性所在的元素上及內部使用「i」變量了 -->
        <li :for="item in list">{{ item }}</li>
    </ul>
</template>

<script>
    new am.Module ( {
        init : function () {
            return {
                list: [ "apple", "orange", "grape" ]
            };
        },
        mounted : function () {

            // 咱們能夠調用數組的相關變異函數,在模板中綁定了此數組的地方也將做出相應更新,以下
            this.state.list.push ( "peach" );
            // 其餘可用的變異函數還有push、pop、sort、shift、unshift、splice、reverse
        }
    } );
</script>

# 在<template>上使用:for

你能夠在<template>上使用:for來循環渲染多個dom元素:app

<template>
    <div>
        <template :for="i in names">
            <span>list.firstName</span>
            <span>list.lastName</span>
        </template>
    </div>
</template>

<script>
    new am.Module ( {
        init : function () {
            return {
                names: [
                    {firstName: "George", lastName: "Bush"},
                    {firstName: "Jake", lastName: "Wood"}
                ]
            };
        },
    } );
</script>

它將被渲染爲:框架

<div>
    <!-- <template>標籤自動被去除 -->
    <span>George</span>
    <span>Bush</span>
    <span>Jake</span>
    <span>Wood</span>
</div>

# 使用:for指令遍歷字符串

:for指令遍歷字符串時,item值爲字符串每一個字符:dom

<div>
    <strong>Amaple由</strong>
    <span :for="char in 'Amaple'">[{{ char }}]</span>
    <strong>組成</strong>
</div>
<!-- 渲染後的文字爲:Amaple由[A][m][a][p][l][e]組成 -->

# 使用:for指令遍歷數字

:for指令遍歷數字時,item值爲從0開始累加的索引數字:函數

<div>
    <strong>小於5的非負數有</strong>
    <span :for="num in 5">[{{ num }}]</span>
</div>
<!-- 渲染後的文字爲:小於5的非負數有[0][1][2][3][4] -->
【注意】使用狀態數組應該避免直接經過索引操做,如 this.state.list [ 0 ] = "banana"將不會觸發自動更新。

經過條件判斷顯示與隱藏元素

咱們常常須要經過條件判斷來肯定應該顯示哪一部分的內容,以簡單的用戶登陸爲例,當有用戶信息時顯示信息,沒有時顯示登陸按鈕,此時能夠使用模板指令的:if:else-if:else,它與咱們熟知的ifelse ifelse關鍵字的用法相同:

<template>
    <div :if="userInfo">
        <img src="{{ userInfo.avatar }}" />
        <span>{{ userInfo.username }}</span>
    </div>
    <div :else>
        <a href="login.action">您尚未登陸,點擊登陸</a>
    </div>
</template>
<script>
    new am.Module ( {
        init : function () {
            return {
                userInfo : null
            };
        },
        mounted : function () {
            this.state.userInfo = { username: "Tom", avatar: "tom_101101.jpg" };
        }
    } );
</script>

當初始化時module.state.userInfo=null,它將被渲染爲:

<div>
     <a href="login.action">您尚未登陸,點擊登陸</a>
</div>

mounted鉤子函數觸發後module.state.userInfo={ username: "Tom", avatar: "tom_101101.jpg" },它將被渲染爲:

<div>
     <img src="tom_101101.jpg" />
     <span>Tom</span>
</div>

# 在<template>上使用條件判斷

它將<template>的子元素做爲一個總體進行條件判斷,並在渲染的時候去掉<template>父元素。

<template>
    <div>
        <template :if="show === true">
            <span>content1</span>
            <span>content2</span>
        </template>
        <template :else-if="show === false">
            <span>content3</span>
            <span>content4</span>
        </template>
        <div>
            <span>content5</span>
        </div>
    </div>
</template>

<script>
    new am.Module ( {
        init : function () {
            return {
                show: true
            };
        },
    } );
</script>

module.state.show=true時將被渲染爲:

<div>
    <span>content1</span>
    <span>content2</span>
</div>

當module.state.show=false時將被渲染爲:

<div>
    <span>content3</span>
    <span>content4</span>
</div>

當module.state.show=1時將被渲染爲:

<div>
    <div>
        <span>content5</span>
    </div>
</div>

# :for:if在同一個元素上使用

:for指令的解析優先級高於:if,這意味着全部循環渲染的元素都會判斷:if的條件。

<template>
    <ul>
        <li :for="item in list" :if="item !== 'apple'">{{ item }}</li>
    </ul>
</template>

<script>
    new am.Module ( {
        init : function () {
            return {
                list: [ "apple", "orange", "grape" ]
            };
        },
    } );
</script>

它將被渲染爲:

<ul>
    <li>orange</li>
    <li>grape</li>
</ul>
【注意】帶有 :else-if:else屬性元素的上一個兄弟元素必須使用了 :if:else-if指令,且 :else屬性是沒有值的

模塊函數與事件綁定

在狀態數據對象上定義的函數叫作模塊函數,咱們在子模塊主動向父模塊傳值時提到過它。除了傳值的做用外,模塊函數還可當作事件綁定函數來使用,以下面事件綁定的示例,在<button>上使用:on指令聲明click事件並指定回調函數。

<template>

    <!-- :on指令的用法是在它的後面補充上事件名稱,如:onclick、:onchange等 -->
    <!-- 使用這個指令綁定事件共有三種形式 -->
    <!-- ①.直接指定一個模塊函數做爲事件回調函數 -->
    <button :onclick="clickHandler">BTN1</button>

    <!-- ②.在指定一個模塊函數爲事件回調函數的同時傳遞參數到此模塊函數內 -->
    <button :onclick="clickHandler ( 'a', 111 )">BTN2</button>

    <!-- ③.當你認爲你的事件處理函數很簡單,而且不會在其餘地方重用時,那你也能夠直接將JavaScript代碼寫到:on指令的值中 -->
    <button :onclick="var text = 'BTN2 has clicked!'; alert(text);">BTN3</button>
</template>
<script>
    new am.Module ( {
        init : function () {
            return {

                // 需注意的是,做爲事件綁定的模塊函數的第一個參數永遠爲event對象,而在額外傳入的參數在模塊函數中第二個開始接收
                // 在第②種形式中傳入了字符串"a"和數字111到模塊函數中,模塊函數在第二個開始接收,即str的值爲"a",num爲111
                clickHandler: function ( event, str, num ) { ... }
            };
        }
    } );
</script>
【注意】模塊函數內的 this指針指向當前模塊的狀態數據對象(即 module.state對象),它也不可以使用ES6的箭頭函數(Arrow function),由於這樣會致使函數內 this指針指向不正確而出錯。

計算屬性

假如項目的某個模塊中定義了兩個狀態屬性,分別爲產品品牌brand、產品型號model,此時你須要在模板中輸出品牌與型號的組合,此時你可能會這樣寫:

<template>
    <span>{{ brand }} {{ model }}</span>
</template>
<script>
    new am.Module ( {
        init : function () {
            return {
                brand : "Samsung",
                model : "Note8"
            };
        }
    } );
</script>

這沒有任何問題,但當有多處都需輸出一樣信息時,使用多個差值表達式就顯得有點麻煩,因此對於如須要屢次拼接字符串或其餘任何較爲複雜處理的輸出,應該使用計算屬性來實現,像這樣:

<template>
    <span>{{ productName }}</span>
</template>
<script>
    new am.Module ( {
        init : function () {
            return {
                brand: "Samsung",
                model: "Note8",

                // 在狀態數據對象中指定computed屬性,它的值爲一個對象,此對象內的全部屬性都將被轉換爲計算屬性
                computed: {

                    // 這裏定義了一個計算屬性productName,它分別有get和set函數
                    // 框架在初始化計算屬性時將會調用get函數獲取並緩存此計算屬性的值,而獲取this.state.productName的值時都將會返回緩存的值
                    // 當爲this.state.productName賦值時,則會調用此計算屬性的set函數並接收一個新值
                    // 從該計算屬性productName的get函數可看出,它依賴於狀態數據brand和model,因此當brand或model的值更新時,此計算屬性的緩存值也會更新
                    productName: {
                        get: function () {
                            return this.brand + " " + this.model;
                        },
                        set: function ( val ) {
                            var split = val.split ( " " );
                            this.brand = split [ 0 ];
                            this.model = split [ 1 ];
                        }
                    }
                    
                    // 一個計算屬性能夠只設置get函數,你能夠像上面那樣直接去掉set函數,也能夠直接將此計算屬性定義爲一個函數做爲get函數,像這樣
                    // productName: function () { return this.brand + " " + this.model; }
                }
            };
        }
    } );
</script>

表單的雙向綁定

在表單元素上使用:model指令便可實現表單輸入值與狀態屬性的雙向綁定,以下:

<template>
    <!-- 當此input輸入框的value值改變時,狀態屬性username的值也將自動同步更新 -->
    <input type="text" name="username" :model="username" />
</template>
<script>
    new am.Module ( {
        init : function () {
            return {
                
                // 當此狀態屬性的值改變時,上面的input輸入框的value值也將同步更新
                username: ""
            };
        }
    } );
</script>
可以使用model指令的表單元素有type爲任何值的 inputtextareaselect

# :model指令在checkbox上使用

checkbox綁定的狀態屬性值必須爲Array類型,且當多個checkbox綁定同一個狀態屬性時,被選中checkbox的值將會保存在此數組中。

# :model指令在radio上使用

radio元素沒有設置name屬性時,自動設置綁定的狀態屬性名爲name屬性。

以上全部指令爲動態指令,接下來的是靜態指令

設置當前頁面顯示的標題

當url跳轉更新模塊時,咱們但願標題隨模塊改變,此時咱們可以使用:title指令來設置標題,它只能用於一個模塊的<module>元素上,像這樣:

<module :title="hello amaplejs">
    <template>...</template>
    <script>...</script>
    <style>...</style>
</module>
固然不少時候一個頁面將同時加載多個不一樣層級的模塊,此時框架將會從上到下、從外到內的順序解析並更新模塊,當以這樣的順序解析時將會獲取第一個 :title不爲空的標題做爲更新標題,而會自動忽略後面模塊所定義的標題,因此建議標題應該在最外層的主模塊中定義。

引用元素

有時候必須操做dom元素如聚焦input元素,咱們必須獲取此input元素的對象並調用input.focus函數,這時能夠在此input元素上使用:ref指令定義一個引用,而後調用am.Module對象的成員函數refs( refName )獲取被引用的dom元素:

<template>
     <input type="text" :ref="ref_dom" />
</template>
<script>
    new am.Module ( {
        init : function () { ... },
        mounted : function () {

            // 這樣就獲取到了模板中的input元素並聚焦
            this.refs ( "ref_dom" ).focus ();
            // refs函數不能在init生命週期函數中使用,由於init函數被調用時還未解析模板
        }
    } );
</script>
當多個元素的 :ref值設置同一個引用名時,使用 refs函數獲取該引用名的dom元素時將會返回一個包含全部該引用名的元素數組。

繼續學習下一節:【AmapleJS教程】4. 組件
也可回顧上一節:【AmapleJS教程】2. 模塊

相關文章
相關標籤/搜索