---
# System prepended metadata

title: "\U0001F3C5 Day 17"
tags: [2024 Nuxt 直播班]

---

# 🏅 Day 17 - Nuxt3 插件 ( Plugins ) - directive

## 今日學習目標
- 學習在 Nuxt3 使用插件註冊自定義的 Vue 指令


## 前言

在 Vue3 除了使用內建的指令 ( 如 `v-if` 和 `v-bind` ) 以外，還可以在 Vue 實體中建立自定義指令。自定義指令主要用於封裝重複使用的 DOM 操作邏輯，例如綁定事件或元素屬性的修改，不涉及有狀態（如 `ref` 或 `computed`）的邏輯。如果要處理有狀態的邏輯，建議使用 [Vue 元件](https://vuejs.org/guide/essentials/component-basics.html) 或是  [Composable](https://vuejs.org/guide/reusability/composables) 。

在 Nuxt3 ，我們可以利用內建的插件功能來訪問 Vue3 的 [application 實體](https://vuejs.org/api/application.html#createapp)，並在實體中註冊自定義指令，支持在伺服器端和客戶端的渲染。接下來將**快速回顧**如何在 Vue3 中建立自定義指令，然後介紹如何在 Nuxt3 環境新增和使用指令。


## Vue3 自定義指令

Vue3 能夠透過 **全域註冊** 或 **區域註冊** 來自定義指令。全域註冊的指令可在所有元件中使用，而區域註冊的指令則僅限於元件內使用。

### 全域註冊

在建立 Vue 實體之後，可以通過 `createApp()`  提供的 `directive()` 方法註冊全域指令。第一個參數是指令名稱（字串），第二個參數是設定指令的物件，包括指令的生命週期函式以及參數，例如 `mounted()`、`updated()` 等。關於生命週期函式參數可以閱讀 [官方文件](https://vuejs.org/guide/reusability/custom-directives#hook-arguments) 的說明。

```jsx
const app = createApp({}); // 建立 Vue 實體
app.directive('指令的名稱', {
  // 當元素掛載後執行的操作
  mounted(el, binding) { ... },
  // 當指令綁定的元素或其綁定值更新後執行的操作
  updated(el, binding) { ... },
  // el: 指令綁定到 DOM，可用於直接操作 DOM
  // binding : 提供指令詳細資訊的物件，例如: 
  //   value => 傳入給指令的值，ex: v-warning="'123'" 的字串值 '123'
  //   arg   => 傳入給指令的參數，ex: v-warning:message="'123'" 冒號後面的 message
})
```

### 區域註冊

在元件內註冊自定義指令時，可以根據元件的定義方式選擇不同的寫法：

#### `export default` 寫法

當使用 `export default` 方式建立元件，可以透過 `directives` 屬性來註冊區域指令。例如下方範例定義了名稱為 focus 的指令，實作自動聚焦到 input 輸入框的功能。定義之後在模板中以 `v-focus` 來使用指令。

```jsx
/* export default 寫法 */
<script>
export default {
  setup() {
    // 其他邏輯...
  },
  directives: {
    'focus': {  // 指令的名稱
      mounted(el) {
        // 指令綁定到的元素。可用於直接操作 DOM
        // 當元素掛載後自動聚焦
        el.focus();  
      }
    }
  }
}
</script>

<template>
  <input v-focus />
</template>
```

#### `<script setup>` 寫法
當使用 `<script setup>` 方式建立元件，可以透過變數自定義指令，並在模板中直接套用。變數名稱需要以 `v` 前綴開頭並使用小駝峰命名法。在模板中，指令名稱需要轉換為 `kebab-case` 形式使用。例如下方範例使用  `v` 前綴開頭，建立了名稱為 `vFocus`的指令，在模板中會將 `vFocus` 寫作 `v-focus`。

```jsx
/* <script setup> 寫法 */
<script setup>
const vFocus = { // 指令的名稱
  mounted(el) {
    // 指令綁定到的元素。可用於直接操作 DOM
    // 當元素掛載後自動聚焦
    el.focus(); 
  }
}
</script>

<template>
  <input v-focus />  
</template>
```


## Nuxt3 的自定義指令

在 Nuxt3 中，可以透過插件系統取得 Vue 的實體並註冊自定義指令。指令的名稱、生命週期以及參數（如 `el` 和 `binding`）的用法與 Vue3 相同，使開發更直觀與便捷。這些在插件中註冊的指令會全域生效，允許在任何元件的模板中使用。

### 建立自定義指令

#### 步驟一.  新增插件檔案

使用 `npx nuxi add plugin <檔案名稱>` 指令自動生成插件檔案，或是手動在 `/plugins` 資料夾中新增檔案，並匯出 `defineNuxtPlugin()` 函式。例如使用  `npx nuxi add plugin clipboard`指令生成 `/plugins/clipboard.js`。

```jsx
// /plugins/clipboard.js 

export default defineNuxtPlugin((nuxtApp) => {})
```

#### 步驟二.  使用 nuxtApp 參數建立指令

在 `defineNuxtPlugin()` 中傳入帶有 `nuxtApp` 參數的函式。我們可以透過 `nuxtApp.vueApp` 取得 Vue 實體，並訪問 `directive()` 方法來全域註冊自定義指令。以下方範例為例，在 `/plugins/clipboard.js`  中使用   `nuxtApp.vueApp.directive()`，於第一個參數傳入 ‘copy’ 建立名稱為  `copy` 的指令，第二個物件參數加入 `mounted` 生命週期，在元素掛載後對 el ( 綁定到的 DOM 元素 ) 註冊點擊事件，點擊後將元素的文字內容儲存至**剪貼簿 (** 瀏覽器的 [Clipboard 物件](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard) ) 。

```jsx
// /plugins/clipboard.js 

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.directive("copy", {
    mounted(el) {
      // 實作點擊複製功能
      el.addEventListener("click", (e) => {
        navigator.clipboard.writeText(e.target.innerText);
        alert("成功複製內容");
      });
    },
  });
});
```

### 使用自定義指令

這些全域指令在 Nuxt 運行時會自動掛載至 Vue 實體，因此不需額外使用 [useNuxtApp()](https://nuxt.com/docs/api/composables/use-nuxt-app) 載入，可以直接在模板使用。例如先前建立的 `v-copy` 指令使用方式如下 : 

```jsx
<template>
  <p v-copy>請透過點擊複製這一段文字</p>
</template>
```

### 伺服器端渲染與 getSSRProps

自定義指令的生命週期函式（如 `mounted()` 和 `updated()`）僅在客戶端渲染時執行，不會在伺服器端渲染時觸發。如果需要在伺服器端渲染時操作 DOM 寫入屬性，可以使用 [**`getSSRProps()`**](https://cn.vuejs.org/guide/scaling-up/ssr#custom-directives) 。例如在 v-copy 指令中加入 `getSSRProps()`  並使用 binding.value 將  v-copy 指令傳入的值取出，在伺服器端渲染時寫入 title 屬性。 

```jsx
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.directive("copy", {
    mounted(el) {
      // 實作點擊複製功能
      el.addEventListener("click", (e) => {
        navigator.clipboard.writeText(e.target.innerText);
        alert("成功複製內容");
      });
    },
    getSSRProps(binding) {
      // 在伺服器端執行
      // 只有接收 binding 參數
      // binding.value 是 v-copy 指令傳入的值
      // 例如 v-copy="'複製這一段文字'" 會把  '複製這一段文字' 傳遞給  binding.value
      return {
        // 寫入元素的 title 屬性
        title: binding.value,
      };
    },
  });
});

```

> ❗ 需注意 : `getSSRProps()` 僅在伺服器端渲染時執行，不會在客戶端運作

<br>

假設 `v-copy` 指令傳入的值為 `'複製這一段文字'`，則在伺服器端渲染時，這段文字會被渲染至 `p` 元素的 `title` 屬性中。

```html
<template>
  <p v-copy="'複製這一段文字'">請透過點擊複製這一段文字</p>
</template>

<!-- p 元素的渲染結果-->
<p title="複製這一段文字">請透過點擊複製這一段文字</p>
```
<br>

> 今日學習的[範例 Code - 資料夾: day17-plugin-directive-example](https://github.com/hexschool/nuxt-daily-tasks-2024) 

## 題目
請 fork 這一份 [模板](https://github.com/jasonlu0525/nuxt3-live-question/tree/day17-plugin-directive) ，在 Nuxt3 中完成以下自定義指令的功能 :

- 在 `plugins/textformat.js` 中作答，建立名稱為 `textformat` 的指令，允許提供修飾符 `:uppercase` 和 `:lowercase`  實作大寫或小寫字母的轉換。
    - 當使用 `:uppercase` 時，將文字轉換為全大寫。
    - 當使用 `:lowercase` 時，將文字轉換為全小寫。
    
    文字字串以及元素已於 `pages/index.vue` 中提供，如下
    

```html
<script setup>
const message = ref("A1B2c3deFGhijk");
</script>

<template>
  <h2>自訂英文文字大小寫轉換指令</h2>
  <!-- 使用 plugins/textformat.js 建立的指令，將變數 message 的字串帶入 -->

  <!-- 大寫轉小寫格式之後，將結果寫入元素 -->
  <p></p>
  <!-- 小寫轉大寫格式之後，將結果寫入元素 -->
  <p></p>

</template>
```

- 在 `plugins/timeformat.js` 中作答，建立名稱為 `timeformat`的時間格式轉換指令，把傳入的時間戳轉換為 `yyyy-mm-dd hh:mm:ss`格式並顯示在頁面上。時間戳以及元素已於 `pages/index.vue` 中提供，如下
```html

<script setup>
const time = ref(1730427600000);
</script>

<template>
  <h2>自訂時間轉換指令</h2>
  <!-- 使用 plugins/timeformat.js 建立的指令，將變數 time 的時間戳帶入 -->
  <!-- 轉換成 yyyy-mm-dd hh:mm:ss 格式之後將結果寫入元素 -->
  <!-- 1730427600000 => 轉換成 yyyy-mm-dd hh:mm:ss  -->
  <p></p>
</template>
```

## 回報流程

將答案上傳至 GitHub 並複製 GitHub  repo 連結貼至底下回報就算完成了喔 !
解答位置請參考下圖（需打開程式碼的部分觀看）

![](https://i.imgur.com/vftL5i0.png)

<!--   

解答 :  https://github.com/jasonlu0525/nuxt3-live-answer/tree/day17-plugin-directive

-->




回報區
---
| #   | Discord | Github / 答案 |
| --- | ----- | ----- |
| 1 | 眼睛 |[Github](https://github.com/Thrizzacode/nuxt3-live-question/tree/day17-plugin-directive)|
| 2 | kevinhes |[Github](https://github.com/kevinhes/nuxt-daily-mission/tree/day17)|
| 3 | Steven |[Github](https://github.com/y7516552/nuxt3-live-question/tree/day17)|
| 4 | MY |[Github](https://github.com/ahmomoz/nuxt3-live-question/tree/day17-plugin-directive-hw)|
| 5 | dragon |[Github](https://github.com/peterlife0617/2024-nuxt-training-homework01/tree/feature/day17)|
| 6 | Rocky |[Github](https://github.com/WuRocky/Nuxt-Day17-Plugins-directive.git)|
| 7 | hsin yu |[Github](https://github.com/dogwantfly/nuxt3-daily-task-live-question/tree/day17-plugin-directive)|
| 8 | LinaChen |[Github](https://github.com/Lina-SHU/nuxt3-live-question)|
| 9 | Jim Lin |[Github](https://github.com/junhoulin/Nuxt3-hw-day-after10/tree/day17)|
| 10 | Johnson |[Github](https://github.com/tttom3669/2024_hex_nuxt_daily/tree/day17-plugin-directive)|
| 11 | Ariel |[Github](https://github.com/Ariel0508/nuxt3-hw/tree/day17-plugin-directive)|
| 12 | tanuki狸 |[Github](https://github.com/tanukili/Nuxt-2024-week01-2/tree/day17-plugin-directive)|

<!-- 
|---|---|[Github]()|
 -->