# Vue3 讀書會
## 組件基礎和溝通
#### 分享者: Rafael
#### 日期: 2024/04/28
---
### 定義組件和使用
定義SFC檔案-樣板(template)、Script和style
composition API
```
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
```
---
Optional API(以邏輯來定義分類)
```
<script>
export default {
data() {
return {
count: 0
}
method() {
.... 用this 來呼叫組件實例內部方法
}
}
}
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
```
---
### 每個元件都是獨立的實例(instance)
每個元件(SFC編譯後)會呼叫createElement,return 一個新的帶有元件特徵的物件,彼此間的響應式資料等是封裝起來的。
```
export function render() {
return (_openBlock(), _createElementBlock(_Fragment, null, [
/* children */
], 64 /* STABLE_FRAGMENT */))
}
```
每一個組件間響應式資料都是獨立個體
```
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
```
---
### 元件間的的資料溝通和屬性傳遞
* props/emits
* 元件expose
* attrs
* slot
* provide/inject (稍微介紹)
---
### Props資料傳遞和驗證
* composition API
```
<script setup>
const props = defineProps(defineProps({
title: String,
likes: Number
}))
// 訪問props傳遞參數
console.log(props.foo)
</script>
```
* optional API
```
export default {
props: {
title: String,
likes: Number
},
setup(props) {
// setup() receives props as the first argument.
console.log(props.foo)
}
}
```
---
### 單向資料流,避免更動傳入的props
```
const props = defineProps(['foo'])
// ❌ warning, props are readonly!
props.foo = 'bar'
```
傳入props預設值進入組件的響應式資料ref/reactive,之後由它們去控制。
```
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
```
computed 衍伸props計算也是以此類推
```
const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
```
---
### 物件型態的props更動
* 傳入props為物件,應當避免mutate更動
* 原理為JS參數引入物件是參照原物件位置
-- 傳遞純值(primitive type)
-- 更動資料時使用emit拋到父層集中邏輯
[範例](https://play.vuejs.org/#eNqVVM9r2zAU/leELmmhsfsLBp4TaEcP7WEt3W7TDq4tt2oVSVhy1hICg1HaHQY9bYwVdtpxp0EZG/tvmrR/xp5kx3FKEgiEWHrf06dPet9TD28p5XVzigMc6jhjyiBNTa7aRLCOkplBPZTRFPVRmskOakBqo4K2pTwr455vJ5ap8ZwIImIptEFJZKKWXb/UIwKhI0jRwRs7dHOEWBKgtZViLKIODVDjMEojyl1uo0Si3JzIrMJGYWC3C9ZX1zebq/BbA2UW6Tu8tsP65A57NE2n81tkFvuzWewbk+xbIrmYzm6RGexrmyN2+/eWiP4yEaFfVARqARNDO4rDKpghFKr2w/Xd4PLD/d+7x19Xw89fQ9/WDJCco24zlVmLYKsCMeHK4LnLJxgFZ/SixDyWEOxWwTrOylGv5+R79jyo7wSNg8VhnkbtaarU0C+pQj/nBSfIHfy8mSrXeWghwShwSHNXpLKME+wDW+jX7givYKPBhSk79k61FGBwVzOCY9lRjNNsXxkGLiU4GFWT4Ihz+W7PxUyW07JYsOaExmdT4qf63MYIPsioplmXElxhJsqOqSngnVcv6TmMK7Ajk5xD9hzwkGrJc6uxSNvORQKya3lO7a7rRSaOX+udc0OFHh3KCh0blmDozRdzjj6Wu+Ftllbswy2O+nreC5HQlAl6kEmlJ14KeAqKh0A5qFVPrD0JtpKVEnOhoDP2j05pbEbNQtMo5yZAS8uo1UY9ZzQrD5pkTpuECeuimEdag03iE8aTZmGWwnmAtofffg9uPgUoZELlxu0NucaVA1wJhaAcAk5+YUir1bWG9VzoW5KK7f7f7eP7y4XZip6awje4+vPw8Xr45cfw9vvCrLYn65zjb71L+v8Bg90FrQ==)
---
### emit-拋出事件和參數到父層組件
* defineEmit/$emit
* 傳遞參數
* 參數validation
[範例](https://play.vuejs.org/#eNp9VMtuEzEU/RXjRSeRQmYBqzCNoFBEkXgIkNh4M525E9x67JHtSVtF8wEIJPgAFlRixZIFi/I7VST+gmt7ZpKQNjv7nuPjcx/2gj6qqvG8Bjqhick0rywxYOtqyiQvK6UtWRANBWlIoVVJIqRGPfTi4qC2VskWG8ddwClGD5hkMlPSWFKaGdl3OoPoGQihyHulRX4nGvaElMsnqU1bFqOMIpbEwRKawY2FshKpBdwRslh0R5rG7ZPey0NTH5fc7jM6yBEe7k/X1V2I0WnSW0W1JF6TpiNqDZoq+Gx8YpTEwizcBYxmqqy4AP2qshxNMzohHnFYikmdPfcxq2sYdfHsA2SnN8RPzLmLMfpagwE9B0Z7zKZ6BjbAh29fwjmue7BUeS2QvQN8A0aJ2nkMtINa5mh7jefdHvkecjl7Zw7PLUjTJeWMOmbj+YxiLx/vSH1l9974vj/HZINVXJ+FXdOVQ8ElHGLLzOj/ScMRCvNRKF22s+EvBmypmJAo8h6r1JgzpXMXwMv7qQIUdT1f3RBOxzFZ/rpcfvu4/H759+dnF8oEz04nRNZCoGRH8jAJA0Wurz5d//ntoBCYkMEiGBn1DkgzJDhwbXF4QQaeQPb2esqwQwmmY2st+4Jj6QgIAyuCS0MJGJ+lWg6iIzlPBc87PzAHaVH2Qqg0vKUN1SJFqVa2awtymCxqmbk2tjpPsbTB5SqN1qOr3yAKtGiEvdrOFkX947z9pSY5n/sFLk2VyqkXwQNu3ca5rGpL5ndxgEHgy3XtHnseo3FQiXuZLcHOzobmLaIdd1s3OW7/Dz8KyF8rz8rPaENliF/J8svX66sfSRyO+wIE4fVPpfkHBxzcpA==)
---
### slot 插槽
* 傳遞template至子元件 [範例](https://play.vuejs.org/#eNp9U9tu00AQ/ZXpVqggxQnNhUswldqqSOUBEPDoF8cem03Wu6u9tImqfANfwBsfwffwA/wCs+vGOBHtm+fMmZkzZ9Z37Fzr4Y1HNmepLQzXDiw6r88yyRutjIN3uSw2F945JaEyqoGT4aiHheKTjnx+i1Y1eF30yD3snpyO2lE0hAKHjRa5Q4oA0l7rCABcCl6soEGKMpf2Zz9UkVqdS7BuI/BtxgollJkXm1xm7GzX7AhIBLF2FX3ho7bvwaR01AllA3bgQLBvf49F61ghcmtJRBX4ycIFDSFPm1ihXBgVlmrZ+1OiOXEJ+hx2DeAu1LdLwXFVVW9CvMiLVW2Ul+UcBJeYm6Q2eclRuqeT01mJ9QCOp+Ny8noM49kTCl5MX2JVPWurlSmR2kklMQI6L0su6znM9BpOn+t1RJvc1FxGsFeWhDnezuHVPVx4Y4M4rbh0aAjbxpvHVci7gwcRvDtKEvCWJgIdDhu15LD01kGlDJQUg/ZGK4sWkiS41Jn0+8fPP7++79/GWepc8Xq4tEpS92hYeAeN5gLNR+24kjZj89bKkMuFULfvI+aMx8EOL75hsfoPvrTrgGXsk0GL5gYz1uUcuYSuTV99+YBr+u6SjSq9IPYjyc/kjvBBY0u7oKOS7B4vqr2OfxxZ9tVerR1Ku1sqCA3MbeRnjCy+fGT1f3Inw2mso3ux7V9TJ2PO)
```
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
```
```
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
```

---
### slot 插槽命名 [範例](https://play.vuejs.org/#eNp9U8Fu1DAQ/ZWRe9hLm1CBVmgJK7WoEiAECDjm4iaTxMWxLXuybKn235k4m91kS/cWv/dm3syL/SRunEs2HYqVyELhlSMISJ1b50a1znqCWxnwi3y0HUHlbQuLJD1CfekiN1k61HIVHwhbpyUhnwCyozieGRl5uGhQluj3ODPN9fojeoRW1Q3BPYIEJ2sEUqQxS5net0gnHs+6lljJTo92TLn1DffxsvbSNVBZD9SwiVQGCmsIDSVZ2q981JsSpLEs82ANTujz1pW1NF3IxX0WAYJtMZrJgkCZyr7QsT/OEpuw4lLMk+9/2ry2VBsotAzhfS6imzLoczE6neYdtCUwskWWDxxr+WcyfBhuVpP1oc3rT/UTRXYax9Rv4J77TWqylPeZZxAvWKBHPSw8iOFpKL63nke9IutWcO22HLpWJVwURfFuEBRWW7+Ci+VyuUcqDukqqL+4glfJW2wjvIs3Oppw5hQ4yUrVyUOwhiOPZn28rVMa/TdHypqQi9U4Ri6k1vbP54iR7/ByxIsGi9//wR/Ctsdy8d1jQL/BXBw4kr5GGui7n19xy98HsrVlp1l9hvyBHEPXzzjIbjtT8tgTXZz2U3ztytS/wt2Wn0QYl+oHjaFEfS743n04s/px3NfJm32YO7H7B0zEbR0=)
* 假設有一個彈窗元件,內容希望客製化
* 父元件以template帶入指定位置
```
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
```
---
### slot條件渲染(v-if) [範例](https://play.vuejs.org/#eNqFVD1v2zAQ/SsEWyBLIjVoJlcN0AYZ2qEt2oxaaOkkMaZIgqRcGYH/e4+kqFi26wAejvfevfu0XugXrbPtAHRFC1sZrh2x4AZ9X0rea2UceWCmJo1RPbnKcv/w9KtSFnnkIxMfDnotmAN8EVJ4WrDQTgh51wGrwUx+RLrb+6eOW4I/1wGJcJGjewrND1RP1Gpo2CB8+klOL9QqJR1IV+S+lbfVGqXcYW3QL9QiXOToPqPmn1PLCz+9ps5iIQ1vs2erJA75xbNLWqlecwHmp3ZcSVvSFQmIx5gQ6u/34HNmgOvkrzqoNmf8z3b0vpL+MmDBbKGkM+aYacFF+PHPDxjRnsFe1YNA9gXwN1glBl9jpH0dZI1lH/BCtd/CqXDZPtnHEcduU1O+UM/cB35J8XQeLrT+Wu7H7C7ElXKPU0xn5690Ofeab0klmLWfcUDIKmlakEe2N7xB4L0VytksHlhJFwE3yfu6e88mkvWAlDkmnxePwpN9kGkhOd3eieYbGstq48kdV5u856udY04zJevob1BYtxNxlplPkHaxVgb7XpFbPRI8AV6TtWDV5lNENatr3PaKfAgO3NIsMM1z1sGg1ig8G5yKUKhoN7u1GOBY6U6Pp1rTIJPYZXJs/v+JBW871xq2u5g6fNjCTOj+H/sTpqs=)
```
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div class="card-content">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
```
---
### slotProps-插槽傳遞資料至外層父組件
* slot預設是由父層傳遞樣板至子層渲染,資料是單向,無法觸及子層資料狀態
```
<!-- <MyComponent> 的模板 -->
// 子層元件定義slot,上面綁定元件自己的響應式資料
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
```
```
//父元件透過v-slot將子元件參數傳遞出來使用
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<script setup>
import { useSlots } from 'vue' //引入方式
const slotDefault = !!useSlots().slotProps; //使用方式
</script>
```
---
## slotProps深入
* 如果已經定義好多個插槽名稱,傳遞參數時可以依序在父層template解構使用
```
<template>
<MyComponent>
<!-- 使用显式的默认插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
</template>
```
---
### defineExpose (特例) [範例](https://play.vuejs.org/#eNqFUrtOw0AQ/JXTNXZE5BRQgRMJUAqQeAgorzH2JhjOd6d7hKAoPR3iH+ioaKj4ncBvsHc2JopISJXbmR3P7s6M7iuVTBzQXZqaXJfKEgPWqQETZaWktmRGNIy6RIoT6YSFgszJSMuKRNgVtazDm5IXDZD0wsvLRntM5FIYS/JA6HuxWDjOO0y0knHc6Q9mTBD8ebbkkHA5jqOvx/fF28vi+WPx9Bp1g0QyybiDRGQVdJhtepYAkz2cIhZ7EOE5fift1ZPhTPiwUCmeWcAXIWntG031GQ0yjA7S2j8S0t4Sm3apNWhvVI6TWyMFrix4xkZZqZKDPlO2RPuM7pJmGkYzzuX9cahZ7aD7U89vIL/7o35rpr7G6LkGA3oCjLaYzfQYbA0PL09hiv9bsJKF48jeAF4ArtZ5jzXtwIkCbS/xgtujcNJSjK/McGpBmJ+hvFHPnAc+o3jgww2j/9rdTnZCH94Dt9im45/QrSStzpE/fBMjtHudM4oXrqHm9IiuyROjn4/PGCX0tuV16shgPxorYFQKGE6VNBA3vZ7TrKbR9oHCSK9N1Gpk5t88XinA)
* 使用 <script setup> 语法糖的组件響應式資料通常是關閉的, ref等資料是預設不會暴露在setup外作用域。
* 使用上父層是透過ref,夾出元件dom屬性
---
### Vue Attributes 傳透特性(補充)
* “傳透 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器
* 最常见的例子就是靜態的 class、style 和 id。
```
<MyButton class="large" @clcik='submit'/>
```
---
### ticket
`* 如果在組件內對props進行操作會發生什麼事?`
資料難以追蹤,因為正常來說資料定義在哪,操作邏輯應當定義在該層組件上。
`* 如果一個組件接收過多props很複雜怎麼辦?`
可以分類成一包物件,但要確實遵守props不可mutate原則
`* slot內放入的資料變數是由父元件還是子元件控制?`
預設放進去的樣板的資料由父層控制,但是可以透過slotProps將子組件資料變數傳遞出來。
---
### 總結
* 每個組件都是獨立個體實例,資料是獨立的
* 組件溝通為單向流,props in/emit out
* Vue 採proxy設計響應式資料,props盡量保持原樣,不作mutate變動最恰當。
---
### Vue discussion相關
* props可以傳遞functions讓子組件操作嗎?
https://github.com/vuejs/core/discussions/10765
[範例](https://play.vuejs.org/#eNp9ks9uEzEQxl/F+LKJFHaF4BS2EVAVAQeoAMHFl2V3krj12pb/bINWewFx4cSLcEJCSLxO8x4d28k2larcPDPfeH72fD19rnXeeaBzWtracO2IBef1gkneamUc6YmBJRnI0qiWZCjNxtLpmotmV8iLGIW7sqdMMlkraR1p7YqchBsm2SsQQpHPyojmQTZlcull7biSxOumctBMJFx9qsS0Z5KEvryrhAfsTnkmBybLIkEiHgYOWi2wFSNCyvWjRd/HgcNQFhjFbGKcpxknjO6GMbooEzHKyuLgJjqjziL8kq/yC6sk/kwkYrRWreYCzDsdsC2jcxIroVbh267exJwzHmb7fL2G+vKe/IXdhByj5wYsmA4YHWuuMitwqXz24S1s8DwWW9V4geojxfdglfCBMcleeNkg9oEu0r6OS+Ry9dGebRxIu39UAA3KIeoZxZWeHnn6Le7j/Ensw1XhL45+OOItNMaMNLDkEs6N0vauzZKFcF/V3kPBNymrgxzTB82TiJSlDWcjofuqYU5e7ty2B8SLjpmp4V08ELL9/Wv74/v1/78pLLnU3pHuIf41CDRUwGO0iOqd5It3Dn39rBa8vkRJZM0T1yTop2i/638/t9/+lEUSL1JzWaTBdx053AAdRz+h)
{"title":"Vue元件溝通基礎","contributors":"[{\"id\":\"b4f9a779-b07f-49c7-a12e-dfaadee4d79f\",\"add\":12212,\"del\":2640}]","description":"type: slide"}