---
# System prepended metadata

title: Vue
tags: [Vue]

---

# 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>
    ```