本文 AI 產出,尚未審核

Vue3 單元 – 指令(Directives)

主題:v-htmlv-text


簡介

在 Vue 3 中,指令(Directives) 是直接作用於 DOM 的特殊屬性,讓開發者可以在模板中以宣告式的方式改變元素的行為或內容。雖然 Vue 已提供了大量內建指令,最常被使用的還是 v-bindv-modelv-if 等,但在處理文字與 HTML 內容時,v-htmlv-text 扮演了不可或缺的角色。

  • v-html:把字串當作 HTML 解析並插入到元素內部。
  • v-text:把字串作為純文字插入,等同於使用 Mustache ({{ }}) 但不會產生 HTML 解析的副作用。

了解這兩個指令的差異與正確使用時機,能讓我們在 防止 XSS(跨站腳本)攻擊、提升渲染效能以及維持程式碼可讀性方面受益良多。


核心概念

1. v-text – 純文字渲染

v-text 會把綁定的資料 直接寫入元素的 textContent,不會解析其中的 HTML 標籤。它的行為與 {{ message }} 完全相同,只是寫法上更明確且在某些情況下可避免 Mustache 產生的空白節點。

<div id="app">
  <!-- 使用 Mustache -->
  <p>{{ msg }}</p>

  <!-- 使用 v-text -->
  <p v-text="msg"></p>
</div>
// main.js
import { createApp } from 'vue'

createApp({
  data() {
    return {
      msg: 'Hello <strong>Vue 3</strong>!'
    }
  }
}).mount('#app')

重點v-text 會把 <strong> 這類標籤視為純文字顯示,畫面上會看到 <strong>Vue 3</strong>,而不會加粗。


2. v-html – HTML 解析渲染

v-html 會把字串 作為 HTML 片段 插入到元素內部,等同於 innerHTML。這在需要顯示富文本(如 CMS 內容、Markdown 轉換結果)時非常便利。

<div id="app">
  <div v-html="htmlContent"></div>
</div>
createApp({
  data() {
    return {
      htmlContent: '<h2>Vue 3 教學</h2><p>使用 <em>v-html</em> 可以直接渲染 HTML。</p>'
    }
  }
}).mount('#app')

⚠️ 安全提醒v-html 直接把字串注入 DOM,若來源不可信,極易成為 XSS 攻擊的入口。務必在插入前做好 內容過濾或消毒(如使用 DOMPurify)。


3. 為何還需要 v-text

雖然 {{ }} 已能完成文字渲染,但在以下情況下 v-text 更具優勢:

場景 使用 {{ }} 使用 v-text
條件渲染 (v-ifv-show) 與文字同時出現時 可能產生空白節點 不會產生額外節點
大量文字更新(例如訊息列表) 每次更新都會重新解析 Mustache 直接更新 textContent,效能略佳
與其他指令混用(如 v-for + v-text 標記較繁雜 語意更清晰

4. 範例彙整

以下提供 5 個實用範例,涵蓋基本使用、動態更新、與安全過濾的完整流程。

範例 1:基本的 v-textv-html 切換

<div id="app">
  <button @click="useHtml = !useHtml">
    切換 {{ useHtml ? '文字' : 'HTML' }}
  </button>

  <p v-if="useHtml" v-html="content"></p>
  <p v-else v-text="content"></p>
</div>
createApp({
  data() {
    return {
      useHtml: false,
      content: '<span style="color:#42b983;">Vue 3 讓開發更快!</span>'
    }
  }
}).mount('#app')

說明:點擊按鈕切換顯示方式,能直觀感受 v-html 解析標籤的差異。


範例 2:從 API 取得富文字,使用 DOMPurify 防止 XSS

<div id="app">
  <h3>新聞內容</h3>
  <article v-html="safeHtml"></article>
</div>
import DOMPurify from 'dompurify'

createApp({
  data() {
    return {
      rawHtml: ''   // 來自後端的未處理 HTML
    }
  },
  computed: {
    // 透過 computed 只在 rawHtml 改變時重新過濾
    safeHtml() {
      return DOMPurify.sanitize(this.rawHtml)
    }
  },
  mounted() {
    // 假設從遠端取得資料
    fetch('https://api.example.com/news/123')
      .then(res => res.text())
      .then(html => {
        this.rawHtml = html
      })
  }
}).mount('#app')

關鍵DOMPurify.sanitize 能移除 <script>on* 事件等危險屬性,確保插入的 HTML 安全。


範例 3:使用 v-text 顯示即時搜尋結果

<div id="app">
  <input v-model="keyword" placeholder="輸入關鍵字" />
  <p>搜尋結果:<span v-text="result"></span></p>
</div>
createApp({
  data() {
    return {
      keyword: '',
      items: ['Vue', 'React', 'Angular', 'Svelte']
    }
  },
  computed: {
    result() {
      const found = this.items.filter(i => i.toLowerCase().includes(this.keyword.toLowerCase()))
      return found.length ? found.join(', ') : '無符合項目'
    }
  }
}).mount('#app')

說明v-text 直接更新文字,避免因 {{ }} 產生的空白節點干擾排版。


範例 4:在 v-for 中混用 v-htmlv-text

<ul id="app">
  <li v-for="(msg, idx) in messages" :key="idx">
    <!-- 若訊息中包含 <b>,使用 v-html 否則使用 v-text -->
    <span v-if="msg.includes('<b>')" v-html="msg"></span>
    <span v-else v-text="msg"></span>
  </li>
</ul>
createApp({
  data() {
    return {
      messages: [
        '普通文字',
        '含有 <b>粗體</b> 的訊息',
        '另一筆純文字'
      ]
    }
  }
}).mount('#app')

技巧:透過 v-if 判斷字串是否含有 HTML 標籤,彈性選擇渲染方式。


範例 5:在表單中使用 v-html 顯示錯誤訊息(HTML 格式)

<div id="app">
  <form @submit.prevent="handleSubmit">
    <input v-model="email" type="email" placeholder="Email" />
    <button type="submit">送出</button>
  </form>

  <!-- 錯誤訊息可能包含 <ul>、<li> 等結構 -->
  <div v-if="errorMsg" class="error" v-html="errorMsg"></div>
</div>
createApp({
  data() {
    return {
      email: '',
      errorMsg: ''
    }
  },
  methods: {
    handleSubmit() {
      // 簡易驗證示範
      if (!this.email.includes('@')) {
        this.errorMsg = `
          <p>郵件格式錯誤,請檢查以下項目:</p>
          <ul>
            <li>必須包含 @ 符號</li>
            <li>域名部分不能為空白</li>
          </ul>`
      } else {
        this.errorMsg = ''
        alert('表單送出成功!')
      }
    }
  }
}).mount('#app')

提示:錯誤訊息直接以 HTML 呈現,讓開發者可以自訂樣式與結構,提升使用者體驗。


常見陷阱與最佳實踐

陷阱 可能產生的問題 解決方案 / 最佳實踐
直接使用 v-html 注入未經過濾的字串 XSS 攻擊、資料外洩 使用 DOMPurifysanitize-html 等套件,或在後端先過濾
把大量 HTML 放在 data 造成 Vue 監控成本上升、效能下降 只在需要時才載入(例如懶加載),或使用 computed 產生
在同一元素同時使用 v-html 與其他屬性(如 v-bind:class 可能導致屬性被覆寫 確認 v-html 不會覆寫目標屬性,必要時分離成子元素
誤把 v-text 當作 v-html 標籤被當成文字顯示,失去樣式 先確認需求是 純文字 還是 富文字,再選擇指令
使用 {{ }} 直接顯示 HTML 會自動 escape,無法渲染 HTML 改用 v-html(並確保安全)

小技巧

  1. 封裝安全的 HTML 渲染元件

    // SafeHtml.vue
    <template><div v-html="sanitized"></div></template>
    <script>
    import DOMPurify from 'dompurify'
    export default {
      props: ['rawHtml'],
      computed: {
        sanitized() {
          return DOMPurify.sanitize(this.rawHtml)
        }
      }
    }
    </script>
    

    讓所有使用者只需傳入 rawHtml,即可自動過濾。

  2. 避免在 v-html 中使用 Vue 插值 ({{ }})
    Vue 只會在編譯階段處理模板,插入的 HTML 不會再被 Vue 解析,若想在 HTML 中使用資料,請先在 JavaScript 中組合字串。


實際應用場景

  1. 內容管理系統(CMS)
    從後端取得的文章內容往往是 HTML,使用 v-html 搭配過濾即可直接渲染。

  2. 即時聊天或評論功能
    使用者可能貼上簡易的 Markdown,先轉成 HTML 再用 v-html 顯示,同時必須過濾腳本。

  3. 表單驗證與錯誤提示
    錯誤訊息需要列表或強調字體,v-html 能讓錯誤樣式更彈性。

  4. 動態產生的 SEO 友好文字
    某些 SEO 需求需要在 meta 標籤內插入文字,使用 v-text 可避免不必要的 HTML 解析。

  5. 多語系(i18n)文字渲染
    若翻譯檔中包含 <strong><br> 等標籤,使用 v-html 可直接呈現,不必在程式碼中硬編碼。


總結

  • v-text:純文字渲染,等同於 Mustache,適合不需 HTML 解析的情況,能避免產生額外的空白節點,效能略佳。
  • v-html:把字串當作 HTML 直接插入,適合顯示富文本,但必須 嚴格過濾 來源以防 XSS。
  • 在實務開發中,先判斷資料性質(純文字 vs 富文字),再選擇正確的指令,並配合 安全過濾封裝元件 等最佳實踐,可讓 Vue 3 應用更安全、效能更佳、維護更容易。

掌握 v-htmlv-text 的差異與使用時機,將為你的 Vue 專案增添彈性與安全性,讓開發者在面對各式文字與 HTML 需求時,都能游刃有餘。祝你在 Vue 3 的旅程中寫出更乾淨、更安全的程式碼!