iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 23
1
Modern Web

學習 vue 30天系列 第 23

Vue 23 Component - 元件組合與溝通 [1]

在昨天有介紹到 x-template ,在這裡面提到了 props,所以今天就來談談元件組溝通的方式及做法。

一個元件可以包含另一個或多個其他的元件,當一個元件包含另一個元件,它們就形成了父子關係,這也是最常見的組合,所以我們需要知道父子元件是如何溝通的。

父子元件溝通

在 Vue 中,父元件將資料傳遞給子元件的動作叫做 Pass Props(又稱 Props down),而由子元件將資料傳遞給父元件的動作叫做 Emit Events(又稱 Events up)。

父元件對子元件的 Props down

每個元件的作用域都是互相獨立互相不干涉的,意思是說我們不能(也不應該)在子元件中直接引用父元件的資料。因此父元件想要傳遞資料給子元件時,可以使用 props 屬性,將資料傳遞給子元件。

用 x-templat 來做例:

<div id="app">
    <whoami :myname="name" area="鳳山"></whoami>
</div>
<script type="text/x-template" id="whoami">
    <div>
        <p>我是<strong>{{area}}</strong><em>{{myname}}</em></p>
    </div>
</script>
<script>
    Vue.component('whoami', {
        props: ["myname", "area"],
        template: '#whoami'
    })

    var app = new Vue({
        el: '#app',
        data: {
            name: "金城武"
        }
    });
</script>

資料從最上層(Root)到最下層(子元件)的資料流:

  • 我們建了一個全域元件 whoami,在此模板使用時定義兩個屬性 mynamearea
  • whoami 裡有個可自訂屬性 props,它會去接收寫在模板的屬性 mynamearea 上的資料。
  • 再借由 mynamearea 收到的資料用 mustache({{}}),來渲染x-template畫面。
  • 其中 myname 使用了 v-bind 指令,為了把 vue instance data 中的 name帶入。

Props 功能

  • 為元件中的自訂屬性。
  • 它的接受值可以是陣列或物件。
  • 拿來接收父元件的資料

駝峰式名稱的問題

因為 HTML 的屬性是不分大小寫的,所以元件中以駝峰式命名的 prop ,必須在 HTML中改用橫線連接的方式,或是改用其他方式命名:

<div id="vm">
  <!-- 顯示:我是 -->
  <my-profile myName="Tony"></my-profile>
  
  <!-- 顯示:我是 Tony -->
  <my-profile my-name="Tony"></my-profile>
</div>

<script>
Vue.component('my-profile', {
  props: ['myName'],
  template: '<p>我是 {{ myName }}</p>'
})

new Vue({
 el: '#vm'
})
</script>

註:使用字串模板,就沒有這個限制。

動態 Prop

我們可以對父元件的資料做些手腳,讓父元件資料的改變間接的影響子元件來達到動態的效果。如果我們要達到這個效果就要用到 v-bind 和資料雙向綁定的指令 v-model 讓子元件可以動態的變更資料:

<div id="app">
    <input type="text" v-model="name">
    <whoami :myname="name" area="鳳山"></whoami>
</div>
<script type="text/x-template" id="whoami">
    <div>
        <p>我是<strong>{{area}}</strong><em>{{myname}}</em></p>
    </div>
</script>
<script>
    Vue.component('whoami', {
        props: ["myname", "area"],
        template: '#whoami'
    })

    var app = new Vue({
        el: '#app',
        data: {
            name: "金城武"
        }
    });
</script>

Props 注意事項

單向數據流

Props 是單向綁定的,也就是說當父元件的屬性發生變動,會立即將變動的資料傳給子元件,這個方向只能單向的傳資料給子元件,相反過來是不行的。

以下範例會報錯誤:

<div id="app">
    <photo :img-url="url"></photo>
    <p>修正單向數據流所造成的錯誤</p>
</div>
<script type="text/x-template" id="photo">
    <div>
        <img :src="imgUrl"/>
        <input type="text" v-model="imgUrl">
    </div>
</script>
Vue.component('photo', {
    props: ['imgUrl'],
    template: '#photo',
})
Vue.component('card', {
    props: ['userData'],
    template: '#card',
    data: function() {
        return {
            user: this.userData
        }
    }
});

var app = new Vue({
    el: '#app',
    data: {
        url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80',
    },
});

解法是我們在元件內使用 data 這個屬性,來接外部傳進來的內容:

<script type="text/x-template" id="photo">
    <div>
        <img :src="imgUrl"/>
        // 使用新的 newUrl ,這個 url 跟父元素沒有直接綁定
        <input type="text" v-model="newUrl">
    </div>
</script>
Vue.component('photo', {
    props: ['imgUrl'],
    template: '#photo',
    data: function() {
        return {
            // this.imgUrl 是 props 所傳進來的,我們定義一個新的 url 給他,
            // 讓他不要跟外部的資料做綁定。
            newUrl: this.imgUrl,
        }
    }
})

AJAX 延遲 及 物件傳參考特性

我們的 card 元件從父元件傳過去的資料是經由 AJAX 方式傳送。在父元件會先宣告一個 user 物件,然後透過 AJAX 行為把遠端的資料抓進來之後,才把它存進 user 物件裡面,在這之中會有時間差(因為執行的東西會先放在 event queue),所以 card 元件會報錯說找不到資料:

<div id="app">
    <div class="row">
        <div class="col-sm-4">
            <card :user-data="user"></card>
        </div>
    </div>
</div>
<script type="text/x-template" id="card">
    <div class="card">
        <img class="card-img-top" :src="user.picture.large" alt="Card image cap">
        <div class="card-body">
            <h5 class="card-title">{{ user.name.first }} {{ user.name.last }}</h5>
            <p class="card-text">{{ user.email }}</p>
        </div>
        <div class="card-footer">
            <input type="email" class="form-control" v-model="user.email">
        </div>
    </div>
</script>
Vue.component('card', {
    props: ['userData'],
    template: '#card',
    data: function() {
        return {
            user: this.userData
        }
    }
});

var app = new Vue({
    el: '#app',
    data: {
        user: {},
    },
    created: function() {
        var vm = this;
        $.ajax({
            url: 'https://randomuser.me/api/',
            dataType: 'json',
            success: function(data) {
                vm.user = data.results[0];
            }
        });
    }
});

這裏我們可以用簡單的方式來解決這個問題,使用 v-if 假設 user 某個特性並沒有完全讀進來之前,就不要把這個卡片執行出來:

<div id="app">
    <div class="row">
        <div class="col-sm-4">
            // 假設它有電話號碼就把資料繪製出來
            <card :user-data="user" v-if="user.phone"></card>
        </div>
    </div>
</div>

這樣就可以正常顯示了,這裏的觀念是如果資料匯入會有時間差,可以用 v-if 讓元件的產生往後移,與資料完成的時候一起同步繪製。

再來就是我們的 user 是個物件,因為物件是傳參考所以當我們在子元件修改資料的時候也會動到父元件的資料,這個是 JS 的特性,有此可知我們統整一下有幾種方式可以來打破子元件禁制回傳資料到父元件這件事:

  • 用 data function return 一個新的變數。
  • 使用 computed。
  • 父元素用物件讓子元素可以用傳參考方式修改資料。

Props 的型別

我們可以在元件定義 Prop 的資料型態,當傳入的資料不符合該型態時,Vue就會提出警告。

通常會使用物件的方式來定義資料型態,但是如果有多個資料型態,可以使用陣列。

Vue.component('child', {
    props: {
        // 直接定義屬性
        id: Number,

        // 使用陣列來定義多個型態
        name: [String, Number],

        // 使用物件來寫一些規則
        age: {
            type: String,
            default: '',
            validator: function(value) {
                return value > 0;
            },
            required: true
        }
    }
})

物件中的規則說明如下:

  • type:資料型態
  • default:預設值
  • validator:檢驗屬性資料是否有效
  • required:該屬性是否為必要

type可以有的屬性如下:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

type 可以是自訂的建構式函式,使用 instanceof 來檢測。

注意,因為元件會在 vue instance 創建之前建立好,所以在 defaultvalidator 函數裡,不能使用datacomputedmethods 等屬性無法使用。

參考資料

Day17 - [Components] 元件組合與溝通
Vue.js (9.1) - 元件(Component)


上一篇
Vue 22 Component - 元件建立與註冊
下一篇
Vue 24 Component - 元件組合與溝通 [2]
系列文
學習 vue 30天30

尚未有邦友留言

立即登入留言