# 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
### [<Transition>](https://vuejs.org/guide/built-ins/transition)
#### about
使用類似元件的方式控制轉場動畫
#### example
有 6 個預定的 CSS class,讓你去操控元件的進出場不同時段的 style

#### 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;
}
```
### [<TransitionGroup>](https://vuejs.org/guide/built-ins/transition-group)
#### property
+ `name` : 轉場名稱,預定的 CSS class 前面的 v 會換成你取的名稱
+ `tag` : 讓 `<TransitionGroup>` 元件有一個對應的 DOM 元素 (比如 `<ul>`),預設是不會有對應的
### [<KeepAlive>](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>
```
### [<Teleport>](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>
```
### [<Suspense>](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>
```