Skip to content

元件

元件是一個可重複使用的區塊,可以將一個功能或一個區塊獨立成元件,方便管理及重複使用

基本語法

可以將網站中重複使用的區塊獨立成元件,例如商品卡片等,方便重複使用
也可以將頁面的各個區塊獨立成元件,拆分頁面,方便維護

html
<div id="app">
  <!-- 
    使用元件名稱當作標籤
    元件可以重複使用
  -->
  <counter></counter>
  <counter></counter>
  <counter></counter>
</div>

<!-- 宣告元件的 HTML 部分 -->
<script type="text/x-template" id="counter">
  <button @click="count++">你點擊了 {{ count }} 次</button>
</script>
js
const app = Vue.createApp({
  // ...略
})
app.component('counter', {
  // 指定模板 script id
  template: '#counter',
  // 或是直接定義 html
  template: `
    <button @click="count++">你點擊了 {{ count }} 次</button>
  `,
  // 元件資料
  data() {
    return {
      count: 0
    }
  }
  // ...其他
})
app.mount('#app')

props

使用 props 將資料傳入元件
如果剛好資料是物件,且 key 與 props 名稱相同,可以使用 v-bind 一次傳入

注意

  • props 是單向綁定,元件內只能讀取不能修改
  • 注意資料型態,沒有使用 v-bind:xxx:xxx 時會是文字
    html
     <!-- 元件內收到的 price 是文字,quantity 是數字 -->
     <product price="100" :quantity="50"></product>
  • 資料型態是布林值時,有寫就是 true,沒寫就是 false
    html
     <!-- 元件內收到的 sell 是 true -->
     <product sell></product>
     <!-- 元件內收到的 sell 是 false -->
     <product></product>
html
<product 
  v-for="product in products"
  :quantity="product.quantity"
  :price="product.price"
  :sell="product.sell"
  :name="product.name"
></product>

<product 
  v-for="product in products"
  v-bind="product"
></product>
js
const { toRef, toRefs } = Vue

app.component('product', {
  props: {
    // 指定資料型態
    quantity: Number,
    sell: {
      type: Boolean,
    },
    // 指定資料型態 + 必填
    name: {
      type: String,
      required: true,
    },
    // 指定資料型態 + 預設值 + 驗證
    price: {
      type: Number,
      required: true,
      default() {
        return 100
      },
      validator(value) {
        return value >= 0
      },
    },
  },
  setup (props) {
    // 直接使用 props
    console.log(props.text)

    // 整個 props 轉成 refs 並解構
    const { text } = toRefs(props)

    // 或是單獨轉成 ref 使用
    const text = toRef(props, 'text')

    console.log(text.value)
  }
})

emit

子元件傳出則需要使用 $emit

html
<!-- 外層 -->
<my-component @apple="handleApple"></my-component>
<!-- 子元件內 -->
<input type="button" @click="onBtnClick" value="點我">

元件觸發

js
const app = Vue.createApp({
  setup() {
    // 外部處理,會收到傳出的值
    const handleApple = (value) => {
      console.log(value) // 'abcd'
    }
    return {
      handleApple
    }
  }
}).component('component', {
  setup(props, { emit }) {
    // 子元件觸發自訂義事件,名稱為 btnClick,將 'abcd' 帶出去
    const onBtnClick = () => {
      emit('apple', 'abcd')
    }
    return {
      onBtnClick
    }
  }
}).mount('#app')

v-model

使用 v-model 搭配 computed 實現雙向綁定

html
<my-component v-model:data="data"></my-component>
js
app.component('my-component', {
  props: {
    data: String
  },
  setup (props, { emit }) {
    const syncData = computed({
      get () {
        return props.data
      },
      set (value) {
        emit('update:data', value)
      }
    })
  }
})

在 Vue 3.4 以後的 SFC 可以使用 defineModel 簡化

html
<script setup>
// defineModel(prop名, prop選項)
const title = defineModel('title', { required: true })
</script>

Event Bus

若需要子元件互傳,或是距離很遠的元件間傳遞資料,需使用 Event Bus
Vue 官方建議使用

TIP

  • Vue 2 可以另外建立一個 Vue 實例當作 Event Bus,但 Vue 3 不支援這種用法
  • Quasar Framework 有內建 Event Bus
js
const emitter = mitt()

app.component('component1', {
  setup () {
    const count = () => {
      emitter.emit('sayhi', 'hi')
    }
    return {
      count
    }
  }
})
app.component('component2', {
  setup () {
    onMounted(() => {
      emitter.on('sayhi', e => {
        console.log(e)
      })
    })
  }
})

provide/inject

provide/inject 可以讓上層元件提供資料給下層元件使用
不需要一層一層傳遞 props 或 emit,適合用在元件層級較深的情況

TIP

provide/inject 在元件比較多層時可能會難以追蹤資料來源,不建議使用

上層使用 provide

js
const { provide, ref } = Vue

setup () {
  const string = ref('abcd')
  provide('string', string.value)
}

子元件就能使用 inject 取得資料

js
const { inject, ref } = Vue

setup () {
  const message = inject('message')
  console.log(message)
}

插槽

插槽可以在元件內預留一部分的 HTML 給呼叫元件的地方使用

在元件內用 slot 標籤,讓該部分的 HTML 由呼叫元件的地方自訂
slot 標籤內的東西會在沒有使用插槽時顯示 ,也可以不放東西

html
<button>
  <slot>送出</slot>
</button>

使用元件時將內容放進元件標籤內即可

html
<my-component>按鈕</my-component>

使用多個 slot 時必須要給插槽名字

html
<button>
  <h1>
    <slot name="title">標題</slot>
  </h1>
  <p>
    <slot name="description">內文</slot>
  </p>
</button>

使用元件時將內容放進 template 標籤內即可

html
<my-component>
  <template v-slot:title>
    ABCDEFG
  </template>
  <!-- v-slot: 可以縮寫為 # -->
  <template #description>
    1234567
  </template>
</my-component>

若要在插槽內使用元件內的資料,必須將資料綁定到 slot 標籤

html
<button>
  <h1>
    <slot name="title" :data="someData" :data2="someData2"></slot>
  </h1>
</button>

使用時將資料解構出來

html
<my-component>
  <template #title="{data, data2}">
    ABCDEFG
  </template>
</my-component>

練習

練習

製作一個卡片收集頁

  • 卡片需用子元件製作,包含圖片、文字及按讚、收回讚按鈕
  • 需要有 5 張以上排列
  • 外層可以統計總共按了幾個讚