# 🏅 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]()| -->