# Vue ## Reference + 🔗 [**Vue**](https://vuejs.org/) + 🔗 [**TypeScript with Composition API**](https://vuejs.org/guide/typescript/composition-api) ## `shims-vue.d.ts` 救命,這鬼東西到底是什麼 ## static / public folder [Are you storing your Vue assets correctly?](https://youtu.be/PkgUm_rlJgI) ## Style + 樣式中可以使用 `v-bind` ```html <script setup> import { ref } from 'vue' const theme = ref({ color: 'red', }) </script> <template> <p>hello</p> </template> <style scoped> p { color: v-bind('theme.color'); } </style> ``` ## Composition API ### `ref()` <mark>響應式物件</mark>。泛型可指定型別。 ```ts const products = ref<Product[]>([]); ``` ### `PropType()` <mark>讓 props 的 type 可使用你的自訂型別</mark>。 ```ts import type { PropType } from 'vue' interface Book { title: string author: string year: number } export default { props: { book: { // provide more specific type to `Object` type: Object as PropType<Book>, required: true } } } ``` ### `useTemplateRef()` <mark>ref 綁定 DOM 元素更好的方法</mark>。詳見[此文](https://www.cnblogs.com/heavenYJJ/p/18395547)。 ```html <template> <input type="text" ref="inputRef" /> <button @click="setInputValue">Assignment</button> </template> <script setup lang="ts"> import { useTemplateRef } from "vue"; const inputEl = useTemplateRef<HTMLInputElement>("inputRef"); function setInputValue() { if (inputEl.value) { inputEl.value.value = "Hello, world!"; } } </script> ``` ### `defineAsyncComponent()` <mark>異步元件</mark>。詳細請參考[此文](https://ithelp.ithome.com.tw/articles/10332865)。 + **很胖的元件** 通常會選擇讓很胖的元件作為異步元件。 所謂很胖的元件,是指 <u>依賴大量 JS 腳本的元件</u>,例如圖表庫、編輯器或其它重型工具。 + **同步加載** 在同步加載的情況下,browser 會一次加載所有必要的資源 (HTML、CSS 和 JS)。\ 若頁面依賴的 JS 文件過於龐大 (例如包含「很胖的元件」),\ 會增加白畫面時間 (FCP),從而影響使用者體驗。 + **異步加載** 異步加載指的是,<u>僅在需要使用該元件時,才從 server 加載其 JS 腳本</u>,\ 而異步元件就是使用異步加載的元件。\ 在打包時,異步元件也會被獨立為一個 chunk 腳本。 異步加載不會阻塞渲染,所以能有效減少白畫面的時間。 + **需要使用該元件時** 所謂的<u>需要使用該元件時</u>,是指元件被掛載時。 以下是常見的推遲元件掛載的方法: + **條件渲染** (conditional rendering) 只有滿足某條件時,才掛載元件 ```html <template> <div> <button @click="showComponent = true">顯示元件</button> <AsyncComponent v-if="showComponent" /> </div> </template> ``` + **懶加載** (lazy loading) 當元件進入視窗可見區域時,才掛載元件\ (Intersection Observer API) ```html <template> <LazyComponent v-intersect /> </template> ``` + **全域註冊** 你可以全域註冊元件為異步元件。如此一來該元件將全域 ```ts // In src/main.ts app.component('CanvasCard', defineAsyncComponent(() => import('./components/canvas-card/CanvasCard.vue'), )) ``` + **Example** ```ts const AsyncFatComp = defineAsyncComponent({ loader: () => import('../components/FatComp.vue'), loadingComponent: LoadingComp, delay: 0, }) ``` + **Note** |🔮 <span class="important">IMPORTANT</span> : 異步元件的 import 流程| |:---| |`loader` 執行開始 -> `loader` 執行結束 -><br />`Promise` 收到結果 `Component` (import 完畢) -><br />`Component` 開始 `setup` -> `Component` 結束 `setup`| |🚨 <span class="caution">CAUTION</span>| |:---| |`loader` 需要給定一個回傳 `Promise` 物件 (期望結果是收到一個 `Component`) 的函數,<br />而 `import` statement 本身回傳的是 `Promise` 物件。| |🚨 <span class="caution">CAUTION</span>| |:---| |從 `loader` 執行開始,若 `delay` 時限結束時還<mark>未 import 完畢</mark>,就會觸發顯示 `loadingComponent` 的<mark>加載元件</mark>,這個加載元件將<mark>持續顯示直到 `Component` 結束 `setup`</mark>。| |`delay` 是一個小緩衝,避免極短時間產生 flicker,如果設為 0 就表示即刻觸發加載元件。| |🚨 <span class="caution">CAUTION</span> : Nuxt| |:---| |在 Nuxt 中,不知為何 `loadingComponent` 無作用,<br />建議配合 `<Suspense>` 的 `#fallback` 服用。| |🚨 <span class="caution">CAUTION</span> : Nuxt| |:---| |在 Nuxt 中,若不需要 SSR 渲染,或是使用到 browser 全域物件,<br />記得外面包一層 `<ClientOnly>`。| ### `effectScope()` 一般我們所見的響應式 API (副作用),比如 `watch`, `computed`, `watchEffect`,\ 通常與元件的生命週期強綁定。 在 setup 時建立,在 onUnmounted 清除。 但實際上,這套響應式系統是可以被剝離出來的,詳見[此文](https://stackoverflow.com/questions/70493794/how-to-understand-the-effectscope-in-vue)。 ```ts const counter = ref(3) const scope = effectScope() scope.run(() => { const doubled = computed(() => counter.value * 2) watch(doubled, () => console.log(doubled.value)) watchEffect(() => console.log('Count: ', doubled.value)) }) scope.stop() ``` ## Built-in Components ### [&lt;Transition&gt;](https://vuejs.org/guide/built-ins/transition) #### about 使用類似元件的方式控制轉場動畫 #### example 有 6 個預定的 CSS class,讓你去操控元件的進出場不同時段的 style ![](https://vuejs.org/assets/transition-classes.DYG5-69l.png) #### property + `name` : 轉場名稱,預定的 CSS class 前面的 v 會換成你取的名稱 ```html <Transition name="fade"> ... </Transition> ``` ```css .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } ``` ### [&lt;TransitionGroup&gt;](https://vuejs.org/guide/built-ins/transition-group) #### property + `name` : 轉場名稱,預定的 CSS class 前面的 v 會換成你取的名稱 + `tag` : 讓 `<TransitionGroup>` 元件有一個對應的 DOM 元素 (比如 `<ul>`),預設是不會有對應的 ### [&lt;KeepAlive&gt;](https://vuejs.org/guide/built-ins/keep-alive) #### about <mark>將 dynamic component 的內部狀態快取起來</mark>,以避免 dynamic component 切換時,因為 unmount 而導致內部狀態遺失的情況。 #### property + `include` / `exclude` : 指定只快取某些元件 / 只不快取某些元件 ```html <KeepAlive include="a,b"> <component :is="view" /> </KeepAlive> ``` + `max` : 最多只快取多少元件 (剔除策略:LRU) ```html <KeepAlive :max="10"> <component :is="activeComponent" /> </KeepAlive> ``` ### [&lt;Teleport&gt;](https://vuejs.org/guide/built-ins/teleport) #### about <mark>當條件渲染的條件達成時,直接將 DOM 元素傳送到指定的 CSS selector 位置。</mark> #### property + `to` : 用來指定 CSS selector + `disable` : 用來指定是否讓 `<Teleport>` 失效 (比如說你不想在手機端 teleport) #### example ```html <button @click="open = true">Open Modal</button> <Teleport to="body"> <div v-if="open" class="modal"> <p>Hello from the modal!</p> <button @click="open = false">Close</button> </div> </Teleport> ``` ### [&lt;Suspense&gt;](https://vuejs.org/guide/built-ins/suspense) #### references + 📑 [**Vue - Async Components**](https://vuejs.org/guide/components/async) + 📑 [**Vue - Suspense**](https://vuejs.org/guide/built-ins/suspense) + 🔗 [**鱈魚 - Vue 元件太肥,切換頁面卡卡怎麼辦?讓 Suspense 登場吧!**](https://codlin.me/blog-vue/use-suspense-to-load-components-asynchronously) + 🔗 [**稀土掘金 - 只为用户体验而生的组件:Suspense 组件!**](https://juejin.cn/post/7418132091629535243) #### about <mark>統一管理一個樹狀關係的異步依賴,統一表示他們的載入進度。</mark> #### slot 等待 default slot 底下樹狀關係的所有異步依賴 setup 完成後,\ 才顯示 default slot 的內容,否則顯示 fallback slot 的內容。 ```html <Suspense> <template #default> <FatComp /> </template> <template #fallback> 加載中... </template> </Suspense> ``` #### async dependencies |🚨 <span class="caution">CAUTION</span>| |:---| |有 async setup function 的 component,外面一定要包 `<Suspense>`| `<Suspense>` 允許兩種 async dependencies 1. async component 使用 [defineAsyncComponent()](#defineAsyncComponent) 定義的元件 2. async setup function a. 明確指定 setup hook 是 async 函數 ```ts export default { async setup() { const res = await fetch(...) const posts = await res.json() return { posts } } } ``` b. script setup 最頂層出現 await 敘述 ```html <script setup lang="ts"> const res = await fetch(...) const posts = await res.json() </script> ```