【Day04】元件


前言

Day04 只有一個主題,那就是components!他的分量寫起來真的就足夠一篇了!!
用語部分,中國那邊叫做組件;臺灣比較多人叫做元件。我之前比較習慣說是組件,如果文章內有筆誤請見諒QQQ


元件


▲ 元件的組織,源自Vue官網

為什麼要用元件?
初學時都是用HTML+CSS建構網頁,要改東西很可能就要找、改半天,
或是遇到需要重複的東西時要一直寫一樣的,讓整個版面亂糟糟。

這時候Vue就是很好的幫手了!
看看上面那張圖,分成了好幾個元件;把功能細分後再組織起來變成一個網頁。
可以達到將程式碼封裝,更好的重複利用!


全域元件

元件有分成全域區域,因為懶惰篇幅關係這裡就只說全域的。
來註冊一個按鈕元件試試看:

Vue.component('btn-count', {
    data: function() {
            return {
                count: 0
            }
        },
    template: `<button @click="count++" class="btn btn-info">被點擊了{{ count }}下</button>`
})
<div id="app">
    <btn-count></btn-count>
</div>


▲ 一個按鈕元件就做出來了!很簡單吧

來分析一下這個元件是怎麼組成的呢?

// 語法
Vue.component(tagName, option)

// 我們上面的範例
Vue.component('btn-count', {
    ...
    (省略)
    ...
})
  • tagName:元件的名字,掛到HTML上就寫這個tag使用此元件。
    options:因為元件是可複用的Vue實例,所以與new Vue接收相同的選項,例如datacomputedwatch……等。僅有的例外是el

⚠️ 元件的註冊必須在「Vue實例初始化前」完成,不然無法使用!
(↑↑也就是 new Vue({...}) 之前)

💡 關於命名,官方的建議...
強烈推薦遵循W3C規範中的自定義組件名(字母全小寫且必須包含一個連字符)。
這會幫助你避免和當前以及未來的HTML元素相衝突。


data

這裡的data和new Vue({ ... })裡的data,怎麼感覺不一樣?

元件內的data必須是一個函數!
元件內的data必須是一個函數!
元件內的data必須是一個函數!

很重要所以說三次

上面有說到,元件是可複用的Vue實例,可複用就是可以重複使用 (廢話
所以我為什麼一定要寫成function?

  • 不寫成function大家就會共用一個資料狀態。
  • 使用function return每個元件間就會互相獨立不影響彼此。

不相信嗎?我們實測元件data如果沒寫成function會怎樣呢:

▲ 告訴你data的option要是個function。

Vue很貼心的噴錯給你看,這裡是引入vue.common.dev.js的CDN
我不確定用其他版本會不會也是如此,想看沒寫成函數的效果可以去官方


元件傳遞資料

元件間的溝通方式很重要,來看看下圖的傳遞方式:

Parent是父元件,Child是子元件
元件的父子都是相對的,以剛才上面創的按鈕元件為例:

可以看到btn-countvm(Vue實例)之下,所以我們可以說:

  • vmbtn-count的父元件
  • btn-countvm的子元件

還是不太清楚嗎?我們來用簡單的例子理解
雖然我舉的例子可能不是很好

就以A跟A的上司B舉例:

  • BA的上司
  • AB的下屬

單拿A這個人看,你不知道他跟B有什麼關係;B也同理。
但他們兩個擺在一起,就能知道他們間的關係了!

元件也是一樣,單看vm並不能說他是父元件
(底下沒人啊~不能半路亂認爹!)

btn-count放進來vm裡,他就是vm下的子元件。

以上圖來看,我們可以看到父傳子用prop,子傳父用$emit
這部分實作看看就知道是什麼意思了!


父傳子,prop

用場景描述來寫一個父傳子的prop,今天有對父子在對話,
爸爸說:「我是你爸,給我去學Vue!」 (不是賣火柴
兒子聽到之後回覆:「好的爸爸」
用父子元件要怎麼寫呢?

<div id="app">
    <child :req="msg" res="好的爸爸"></child>
</div>
Vue.component('child', {
    props: ['req', 'res'],
    template: `
        <div>
            父元件的爸爸說:{{ req }} <p/>
            子元件兒子回覆:{{ res }}
        </div>
        `
})

var vm = new Vue({
    el: '#app',
    data: {
        msg: '我是你爸,去給我學Vue'
    }
})

child的tag上有reqres,這是要傳入子元件裡面的屬性。
req有使用v-bind綁定,將父元件的msg傳入,這是動態綁法。
res後面可以隨意輸入字串,這裡是靜態綁法。

屬性名稱請自己更改,這裡是為了示範爸爸的要求(req)和兒子的回覆(res)XD

命名規則

HTML不會區分大小寫,所以命名上請遵照這樣的規格:
props:使用 camelCase(駝峰式大小寫),像fatherRequest、sonResponse。
HTML :使用kebab-case(短橫線隔開式)。以上面例子來說:

<child :father-request="msg" son-response="好的爸爸"></child>

prop單向綁定的,當父元件資料改變或更新時,只會單向傳遞給子元件。
反過來是不行的(這裡指prop不行),是為了不讓子元件可以任意去更改父元件的狀態。


子傳父➀,$emit

不能用Prop該怎麼把子元件更新後傳給父元件呢?
在子層資料發生變化後觸發一個事件方法,告訴父層我更新了;
父層需要監聽這個事件,當捕獲到這個事件運行後,再對其資料進行變更就好~

<div id="app">
    父元件msg: {{ msg }} <p/>
    <child @child-click="parentClick"></child>
</div>
// 註冊元件Child
Vue.component('child', {
    data() {
        return {
            msg: 'child',
        }
    },
    template: ` 
        <div>
            子元件msg: {{ msg }} <p/>
            <input type="text" v-model="msg">
            <button @click="myClick" class="btn btn-outline-info">button</button>
        </div>
    `,
    methods: {
        myClick() {
            this.$emit("child-click", this.msg)
            }
        }
});

new Vue({
    el: "#app",
    data: {
        msg: "parent"
    },
    methods: {
        parentClick(childmsg) {
            this.msg = childmsg
        }
    }
});


用小畫家貼心der畫了一張圖表示$emit流程,$emit真的好煩RRR!


子傳父➁,$ref

上面$emit我們可以清楚了解到:子層更新後觸發事件讓父層也做更動。
如果想要從父元件向子元件要資料,就可以透過$ref取得~

子元件1點擊了:<child ref="child1"/>次 <p/> 
子元件2點擊了:<child ref="child2"/>次 <p/>
<button @click="show" class="btn btn-outline-info">查看</button>
Vue.component('child', {
    data() {
        return {
            count: 20
        }
    },
    methods: {
        getCount() {
            this.count++
        }
    },
    template: `
        <button @click="getCount" class="btn btn-info mt-2">{{  count }}</button>
        `
})

// 父元件省略其他,只寫methods

methods: {
    show() {
        console.log(this.$refs.child1.count);
        console.log(this.$refs.child2.count);
    }
}


slot

上面我們介紹了元件,知道它有個功能:用來解決需要重複使用的元件。
如果我們做了警告框元件,像底下這樣:

⚠️

現在要使用於各個場景,要怎麼解決警告文字的問題呢?

⚠️ 帳號密碼不能空白!
⚠️ 文章內容不得空白!

這時候好幫手slot就出現了!slot是插槽,用於內容分配。
我們實作上面警告框的例子來看:

<div id="app">
    <danger></danger>
</div>
const danger = {
    template: `
        <div class="alert alert-danger">⚠️<slot></slot> </div>
        `,
    }

new Vue({
    el: '#app',
    components: {
        danger,
        },
    })

我們要在警告icon後面加上放文字的地方,用slot插槽

子元件danger中放入文字

<div id="app">
    <danger>這樣會不會出現文字呢!</danger>
</div>

子元件 danger 預留了一個空間,讓父元件插入內容並顯示於預留空間。
(重點劃起來:⭐ slot = 預留空間 ⭐)

所以推論出:

  • 父元件:負責內容,不負責擺放的位置;
  • 子元件:負責擺放位置,不負責內容。

Named Slot 具名插槽

從上面例子可以知道插槽的應用。如果今天有很多slot,要怎麼知道如何預留空間?
slot要知道自己要在哪裡預留,必須透過給名字讓他們對號入座。

以例子來說,如果我的元件要顯示吉娃娃的名字、年齡和毛色,要怎麼讓slot對應呢?

在元件中這麼寫:

 const card = {
     template: `
     ...
        <h4>Name</h4>
        <p class="card-text">
            <slot name="name"></slot>
        </p>

        <h4>Age</h4>
        <p class="card-text">
            <slot name="age"></slot>
        </p>

        <h4>Color</h4>
        <p class="card-text">
            <slot name="color"></slot>
        </p>
    ...
        `
    }

在HTML裡放入元件~

<card>
    <template v-slot:name>吉娃娃寶寶</template>
    <template v-slot:age>100</template>
    <template v-slot:color>彩色</template>
</card>

Compilation Scope 編譯作用域

這裡必須記住以下規則!!

  • 父元件裡的所有內容都是在父級作用域中編譯的;
  • 子元件裡的所有內容都是在子作用域中編譯的。

WHATTTTTT !?

圖源GIPHY

如果不好理解我們可以拆!

<div id="app">
    <card> {{ msg }} </card>
</div>
const card = {
    data() {
        return {
            msg: "我是card元件的msg",
        }
    },
    template: `
        <div>
            <span>我是card元件</span>
            <slot></slot>
        </div>
    `
}

var vm = new Vue({
    el: "#app",
    data: {
        msg: "我是vm的msg"
    },
    components: {
        card
    },

})

看看上面的規則,推論{{msg}}的結果會是什麼呢?

{{ msg }}並不是作用在子元件裡!!他是在父元件中執行的。
如果不太清楚可以往上看元件傳遞資料部分,跟這裡是差不多意思的。

<template v-slot:name>吉娃娃寶寶</template>
<template v-slot:age>100</template>
<template v-slot:color>彩色</template>

這些都是由父元件對子元件提供的內容,所以這裡我們也可以使用父元件的data。


Scoped Slots

接續例子,我要把吉娃娃的年齡換算成人類年齡。
如果age在子元件的data裡,想在父元件提供插入內容時順便換算,該怎麼做呢?


首先我們到要傳遞資料的slot,把要傳出去的值給綁定。
將子元件data中的ageData綁定給age,這會做用在name為age的slot。

const card = {
    data() {
        return {
            ageData: 10,
        }
    },
    template: `
    ...
    <slot name="age" :age="ageData"></slot>
    ...
    `
}

age要能在父級的插槽內容可用,要將age作為<slot>元素的一個attribute綁定上去。
這裡我命名為props

<template v-slot:age="props">人類年齡約:{{ props.age * 7 }}</template>

這樣就可以看到資料有傳遞過來了!


slot技術總結

  1. v-slot 只能添加在<template> 上 。
  2. 上面情況的例外:只有默認slot(沒有name),元件標籤才可被當作插槽的模板來使用。
  3. 默認插槽的縮寫語法不能和具名插槽混用,因為它會導致作用域不明確

小小心得

若觀念或內容有誤請不吝指教,謝謝您( ᐛ)パァ

元件部分是我比較不熟的,今天複習完之後感覺好很多了QQ
尤其是資料傳遞的部分 & slot,收穫很多。

但我還有同層的event bus沒說到QQ
→ 參考掘金vue篇之事件总线(EventBus)
專案如果大的話其實不好維護,這部分可以改用vuex!後天會講的:)

還剩3天,加油!(๑•̀ㅂ•́)و✧


📢參考文章

#javascript #Vue #Web #前端
學習 Vue.js 一段時間了, 會基本的語法,但做一些應用還是要看官方文件... 一邊查著一邊思考這些不是學過了嗎嗚嗚嗚 就跟剛認識的朋友一樣生疏 QQ 所以呢!! 這系列我會利用七天的時間將 Vue 重新、快速的學習! 搭配一些簡單的應用:)






Related Posts

每日心得筆記 2020-06-26(五)

每日心得筆記 2020-06-26(五)

Leetcode 刷題雜談 - 該如何每天都刷題

Leetcode 刷題雜談 - 該如何每天都刷題

設計模式 7 Day 目錄

設計模式 7 Day 目錄



Sponsored



Comments