# 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> ``` ![圖片](https://hackmd.io/_uploads/HkFDbrjZ0.png) --- ### 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"}
    39 views