pin_drop当前位置:知识文库 ❯ 图文

框架集成:09 Vue模板语法与HTML - 详细教程与实战指南

一、教程简介

Vue的模板语法是对HTML的扩展,它在标准HTML基础上增加了数据绑定、指令、修饰符等功能,让开发者能够声明式地将DOM与数据绑定。Vue模板最终会被编译为JavaScript渲染函数,在运行时高效地更新DOM。

本教程将详细介绍Vue的插值表达式、v-text/v-html指令、指令概述、修饰符系统、模板表达式限制、过滤器(Vue2)以及计算属性与方法的区别。


二、插值表达式

Vue使用双花括号{{ }}进行文本插值:

代码示例

<div id="app">
  <div class="panel">
    <h3>基本插值</h3>
    <div class="expr-card">
      <h4>变量插值</h4>
      <div class="expr-result">
        <p>姓名:{{ name }}</p>
        <p>年龄:{{ age }}</p>
        <p>邮箱:{{ email }}</p>
      </div>
    </div>
  </div>

  <div class="panel">
    <h3>表达式插值</h3>
    <div class="expr-card">
      <h4>JavaScript表达式</h4>
      <div class="expr-result">
        <p>算术运算:{{ age + 5 }}({{ age }} + 5)</p>
        <p>三元运算:{{ age >= 18 ? '成年' : '未成年' }}</p>
        <p>字符串操作:{{ name.split('').reverse().join('') }}</p>
        <p>数组操作:{{ skills.join(', ') }}</p>
        <p>对象访问:{{ user.address.city }}</p>
        <p>模板字符串:{{ `你好,${name}!` }}</p>
        <p>布尔取反:{{ !isActive ? '未激活' : '已激活' }}</p>
      </div>
    </div>
  </div>

  <div class="panel">
    <h3>插值的限制</h3>
    <p>双花括号中只能使用<strong>单个表达式</strong>,不能使用语句:</p>
    <pre>{{ `// 可以使用的:
{{ message }}              变量
{{ count + 1 }}            算术表达式
{{ ok ? 'YES' : 'NO' }}    三元表达式
{{ message.split('').reverse().join('') }}  方法调用

// 不能使用的:
{{ var a = 1 }}            语句(声明)
{{ if (ok) { return 'a' } }}  控制流语句` }}</pre>
  </div>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp, ref, reactive } = Vue;
  createApp({
    setup() {
      const name = ref('张三');
      const age = ref(25);
      const email = ref('zhangsan@example.com');
      const skills = ref(['HTML', 'CSS', 'JavaScript', 'Vue']);
      const isActive = ref(true);
      const user = reactive({
        address: { city: '北京', district: '海淀' }
      });
      return { name, age, email, skills, isActive, user };
    }
  }).mount('#app');
</script>

三、v-text与v-html

Vue提供了三种文本渲染方式:插值表达式v-textv-html,它们各有特点和使用场景。

代码示例

<div id="app">
  <div class="panel">
    <h3>三种文本渲染方式对比</h3>
    <div class="comparison">
      <div class="comp-card interp">
        <h4 style="color:#42b883;">{{ }} 插值</h4>
        <p>结果:{{ rawHtml }}</p>
        <p style="font-size:12px;color:#666;">原样输出HTML标签</p>
      </div>
      <div class="comp-card vtext">
        <h4 style="color:#3498db;">v-text 指令</h4>
        <p v-text="rawHtml"></p>
        <p style="font-size:12px;color:#666;">与插值等效,替换整个textContent</p>
      </div>
      <div class="comp-card vhtml">
        <h4 style="color:#e74c3c;">v-html 指令</h4>
        <p v-html="richContent"></p>
        <p style="font-size:12px;color:#666;">渲染为真实HTML元素</p>
      </div>
    </div>
  </div>

  <div class="panel">
    <h3>使用场景</h3>
    <pre>{{ `<!-- 1. 普通文本:推荐 {{ }} -->
<p>{{ message }}</p>

<!-- 2. 替换整个元素内容:v-text -->
<span v-text="fullName"></span>
<!-- 等同于 -->
<span>{{ fullName }}</span>

<!-- 3. 渲染HTML内容:v-html(谨慎使用)-->
<div v-html="articleContent"></div>

<!-- 4. 插值可以与固定文本组合,v-text不行 -->
<p>欢迎,{{ name }}!</p>
<!-- v-text会替换整个内容,不能组合 -->` }}</pre>
  </div>

  <div class="panel">
    <div class="warning">
      <strong>安全警告:</strong>v-html会直接渲染HTML内容,可能导致XSS攻击。只在信任的内容上使用v-html,绝不要将用户输入直接通过v-html渲染!
    </div>
    <h3>XSS防护演示</h3>
    <div style="margin-bottom:15px;">
      <input v-model="userInput" placeholder="输入内容(模拟用户输入)" />
    </div>
    <div class="comparison">
      <div class="comp-card interp">
        <h4>安全:{{ }}</h4>
        <p>{{ userInput }}</p>
        <p style="font-size:12px;color:#27ae60;">HTML标签被转义,安全</p>
      </div>
      <div class="comp-card vhtml">
        <h4>危险:v-html</h4>
        <p v-html="userInput"></p>
        <p style="font-size:12px;color:#e74c3c;">HTML被执行,可能XSS!</p>
      </div>
    </div>
    <p style="font-size:13px;color:#666;">试试输入: &lt;img src=x onerror="alert('XSS!')"&gt;</p>
  </div>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp, ref } = Vue;
  createApp({
    setup() {
      const rawHtml = ref('<strong style="color:red;">加粗红色文字</strong>');
      const richContent = ref('<div style="padding:10px;background:#f0faf5;border-radius:6px;border:1px solid #42b883;"><strong>富文本内容</strong> - 通过v-html渲染</div>');
      const userInput = ref('');
      return { rawHtml, richContent, userInput };
    }
  }).mount('#app');
</script>

四、指令概述与修饰符

Vue指令是带有v-前缀的特殊属性,用于在模板中添加响应式行为。

Vue核心指令

指令 作用 示例
v-bind 动态绑定属性 v-bind:src="url" 或 :src="url"
v-on 绑定事件 v-on:click="fn" 或 @click="fn"
v-model 双向数据绑定 v-model="name"
v-if 条件渲染(销毁/创建) v-if="show"
v-else-if 条件链 v-else-if="type === 'A'"
v-else 否则 v-else
v-show 条件显示(CSS切换) v-show="visible"
v-for 列表渲染 v-for="item in items"
v-text 设置文本内容 v-text="message"
v-html 设置HTML内容 v-html="content"
v-slot 插槽 v-slot:name 或 #name
v-pre 跳过编译 v-pre
v-cloak 防止闪烁 v-cloak
v-once 只渲染一次 v-once

事件修饰符

代码示例

<div id="app">
  <div class="panel">
    <h3>事件修饰符</h3>
    <div class="modifier-demo">
      <h4>.stop - 阻止事件冒泡</h4>
      <div @click="log('外层div')" style="padding:15px;background:#f0f0f0;border-radius:6px;">
        <button @click.stop="log('按钮(已阻止冒泡)')">.stop</button>
        <button @click="log('按钮(冒泡到外层)')">无修饰符</button>
      </div>
    </div>

    <div class="modifier-demo">
      <h4>.prevent - 阻止默认行为</h4>
      <a href="https://vuejs.org" @click.prevent="log('链接点击被阻止')" style="color:#42b883;">
        点击此链接(默认跳转被阻止)
      </a>
    </div>

    <div class="modifier-demo">
      <h4>.capture - 使用捕获模式</h4>
      <div @click.capture="log('捕获:外层')" style="padding:15px;background:#f0f0f0;border-radius:6px;">
        <button @click="log('冒泡:按钮')">.capture</button>
      </div>
    </div>

    <div class="modifier-demo">
      <h4>.self - 只在自身触发</h4>
      <div @click.self="log('自身点击')" style="padding:20px;background:#f0f0f0;border-radius:6px;">
        <button @click="log('按钮点击')">点击按钮不会触发外层</button>
        <p style="font-size:12px;color:#999;">点击灰色区域才会触发self事件</p>
      </div>
    </div>

    <div class="modifier-demo">
      <h4>.once - 只触发一次</h4>
      <button @click.once="log('只触发一次!')">点击我(只能触发一次)</button>
    </div>

    <div class="modifier-demo">
      <h4>按键修饰符</h4>
      <input @keyup.enter="log('Enter键')" @keyup.esc="log('Esc键')" placeholder="按Enter或Esc" />
    </div>
  </div>

  <div class="panel">
    <h3>v-model修饰符</h3>
    <div class="modifier-demo">
      <h4>.lazy - 在change时而非input时更新</h4>
      <input v-model.lazy="lazyText" placeholder="失去焦点时更新" />
      <p>值:{{ lazyText }}</p>
    </div>
    <div class="modifier-demo">
      <h4>.number - 自动转为数字</h4>
      <input v-model.number="numValue" type="number" placeholder="输入数字" />
      <p>值:{{ numValue }},类型:{{ typeof numValue }}</p>
    </div>
    <div class="modifier-demo">
      <h4>.trim - 自动去除首尾空格</h4>
      <input v-model.trim="trimText" placeholder="输入带空格的文字" />
      <p>值:[{{ trimText }}]</p>
    </div>
  </div>

  <div class="panel">
    <h3>事件日志</h3>
    <div style="background:#1e1e1e;color:#0f0;padding:10px;border-radius:4px;font-family:monospace;font-size:12px;max-height:150px;overflow-y:auto;">
      <div v-for="(log, i) in logs" :key="i">{{ log }}</div>
      <div v-if="logs.length === 0" style="color:#888;">等待操作...</div>
    </div>
    <button @click="logs = []" style="background:#e74c3c;margin-top:10px;">清除日志</button>
  </div>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp, ref } = Vue;
  createApp({
    setup() {
      const logs = ref([]);
      const lazyText = ref('');
      const numValue = ref(0);
      const trimText = ref('');

      function addLog(msg) {
        logs.value.push(`[${new Date().toLocaleTimeString()}] ${msg}`);
      }

      return { logs, lazyText, numValue, trimText, log: addLog };
    }
  }).mount('#app');
</script>

五、计算属性vs方法

计算属性和方法是Vue中处理派生数据的两种主要方式,它们有本质的区别。

代码示例

<div id="app">
  <div class="panel">
    <h3>核心区别</h3>
    <div class="comparison">
      <div class="comp-card computed-card">
        <h4 style="color:#42b883;">计算属性 (computed)</h4>
        <ul>
          <li>基于依赖缓存</li>
          <li>依赖不变不重新计算</li>
          <li>像属性一样访问</li>
          <li>适合派生数据</li>
          <li>有getter和setter</li>
        </ul>
        <pre>{{ `computed(() => {
  return items.filter(
    i => i.price > 100
  );
})

// 使用:无需括号
{{ expensiveItems }}` }}</pre>
      </div>
      <div class="comp-card method-card">
        <h4 style="color:#3498db;">方法 (methods)</h4>
        <ul>
          <li>每次调用都执行</li>
          <li>无缓存</li>
          <li>需要括号调用</li>
          <li>适合操作和事件</li>
          <li>可接受参数</li>
        </ul>
        <pre>{{ `function getExpensive() {
  return items.filter(
    i => i.price > 100
  );
}

// 使用:需要括号
{{ getExpensive() }}` }}</pre>
      </div>
    </div>
  </div>

  <div class="panel">
    <h3>实际对比:图书筛选</h3>
    <input v-model="keyword" placeholder="搜索书名..." />
    <input v-model.number="minPrice" type="number" placeholder="最低价格" style="margin-left:10px;width:120px;" />

    <div class="comparison" style="margin-top:15px;">
      <div class="comp-card computed-card">
        <h4>计算属性结果(有缓存)</h4>
        <p style="font-size:13px;color:#666;">计算次数:{{ computedCount }}</p>
        <ul class="book-list">
          <li v-for="book in filteredBooks" :key="book.id" class="book-item">
            <span>{{ book.name }}</span>
            <span class="price">¥{{ book.price }}</span>
          </li>
        </ul>
        <p v-if="filteredBooks.length === 0" style="color:#999;text-align:center;">无匹配结果</p>
      </div>
      <div class="comp-card method-card">
        <h4>方法结果(无缓存)</h4>
        <p style="font-size:13px;color:#666;">调用次数:{{ methodCount }}</p>
        <ul class="book-list">
          <li v-for="book in getFilteredBooks()" :key="book.id" class="book-item">
            <span>{{ book.name }}</span>
            <span class="price">¥{{ book.price }}</span>
          </li>
        </ul>
        <p v-if="getFilteredBooks().length === 0" style="color:#999;text-align:center;">无匹配结果</p>
      </div>
    </div>

    <div style="margin-top:15px;padding:15px;background:#fff3cd;border-radius:8px;">
      <strong>提示:</strong>多次渲染页面时,计算属性因为缓存不会重新计算,而方法每次都会执行。在模板中多次使用同一个计算属性只计算一次。
    </div>
  </div>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp, ref, computed } = Vue;
  createApp({
    setup() {
      const keyword = ref('');
      const minPrice = ref(0);
      const computedCount = ref(0);
      const methodCount = ref(0);

      const books = ref([
        { id: 1, name: 'HTML入门', price: 49.9 },
        { id: 2, name: 'CSS权威指南', price: 89.9 },
        { id: 3, name: 'JavaScript高级', price: 129.9 },
        { id: 4, name: 'Vue实战', price: 79.9 },
        { id: 5, name: 'React进阶', price: 99.9 },
        { id: 6, name: 'Node.js开发', price: 109.9 }
      ]);

      const filteredBooks = computed(() => {
        computedCount.value++;
        return books.value.filter(book => {
          const matchKeyword = !keyword.value || book.name.includes(keyword.value);
          const matchPrice = book.price >= minPrice.value;
          return matchKeyword && matchPrice;
        });
      });

      function getFilteredBooks() {
        methodCount.value++;
        return books.value.filter(book => {
          const matchKeyword = !keyword.value || book.name.includes(keyword.value);
          const matchPrice = book.price >= minPrice.value;
          return matchKeyword && matchPrice;
        });
      }

      return { keyword, minPrice, books, filteredBooks, getFilteredBooks, computedCount, methodCount };
    }
  }).mount('#app');
</script>

六、浏览器兼容性

特性 Chrome Firefox Safari Edge 说明
模板语法 60+ 60+ 12+ 79+ Vue 3运行时
v-html 全部 全部 全部 全部 需注意XSS
事件修饰符 全部 全部 全部 全部 Vue内部处理
按键修饰符 全部 全部 全部 全部 Vue内部处理
computed缓存 全部 全部 全部 全部 Vue内部机制

七、注意事项与最佳实践

  • 优先使用插值语法{{ }}比v-text更灵活,可以与固定文本组合

  • 谨慎使用v-html:只在信任内容上使用,防止XSS攻击

  • 计算属性用于派生数据:需要基于响应式数据计算的新数据用computed

  • 方法用于操作:需要传参或有副作用的操作用methods

  • 修饰符可以链式使用@click.stop.prevent="handler"

  • v-pre跳过编译:包含大量静态内容的节点使用v-pre可提升性能

  • v-cloak防止闪烁:配合CSS [v-cloak] { display: none } 隐藏未编译模板


八、代码规范示例

代码示例

<!-- 不推荐:在插值中使用复杂表达式 -->
<p>{{ items.filter(i => i.active).sort((a,b) => a.id - b.id).map(i => i.name).join(', ') }}</p>

<!-- 推荐:使用计算属性 -->
<p>{{ activeItemNames }}</p>
<!-- setup中 -->
const activeItemNames = computed(() =>
  items.filter(i => i.active).sort((a,b) => a.id - b.id).map(i => i.name).join(', ')
);

<!-- 不推荐:对用户输入使用v-html -->
<div v-html="userComment"></div>

<!-- 推荐:使用插值或文本渲染 -->
<div>{{ userComment }}</div>

<!-- 不推荐:v-if和v-for混用 -->
<li v-for="item in items" v-if="item.active" :key="item.id">

<!-- 推荐:先过滤再渲染,或使用计算属性 -->
<li v-for="item in activeItems" :key="item.id">

九、常见问题与解决方案

常见问题

Q1:{{ }}和v-text有什么区别?

功能上基本相同,但{{ }}可以与固定文本组合使用(如Hello, {{ name }}!),而v-text会替换元素的整个textContent。{{ }}更灵活,是首选方式。

Q2:计算属性和侦听器(watch)怎么选?

需要从现有数据派生新数据时用computed;需要在数据变化时执行异步操作或较大开销操作时用watch。能用computed解决的优先用computed。

Q3:v-if和v-show的区别?

v-if是"真正的"条件渲染,会销毁和重建DOM元素;v-show只是CSS的display切换。v-if有更高的切换开销,v-show有更高的初始渲染开销。频繁切换用v-show,条件很少变化用v-if。

Q4:修饰符的执行顺序是什么?

使用链式修饰符时,从左到右执行。如@click.prevent.stop先执行prevent再执行stop。

Q5:Vue 3移除了过滤器,如何替代?

Vue 3移除了过滤器语法,推荐使用computed或methods替代。全局过滤器可以用app.config.globalProperties注册方法替代。


十、总结

本教程详细介绍了Vue模板语法与HTML的关系。插值表达式{{ }}是最常用的数据绑定方式,支持JavaScript表达式但不支持语句。v-text和v-html分别用于文本和HTML内容渲染,v-html需注意XSS安全。

Vue提供了丰富的指令系统(v-bind、v-on、v-model、v-if、v-for等)和修饰符(.stop、.prevent、.lazy、.number等)来增强HTML的能力。计算属性基于依赖缓存,适合派生数据;方法每次调用都执行,适合操作和事件处理。理解这些模板语法是高效使用Vue的基础。

小贴士

Vue模板编译过程:模板字符串 → AST抽象语法树 → 渲染函数 → 虚拟DOM → 真实DOM。Vue 3默认使用运行时编译,支持模板字符串动态编译。如果需要更好的性能,可以使用构建工具在构建时编译模板为渲染函数。

标签: Vue模板语法 插值表达式 v-text v-html 指令修饰符 计算属性 XSS防护

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 框架集成:08 HTML与Vue集成基础 - 详细教程与实战指南 下一篇: 框架集成:10 Vue指令与HTML属性 - 详细教程与实战指南

poll相关推荐