Skip to content

template.vue

文件信息

  • 📄 原文件:01_template.vue
  • 🔤 语言:vue

Script Setup 语法 <script setup> 是 Vue 3 的语法糖:

  • 更简洁的代码
  • 更好的运行时性能
  • 更好的 IDE 类型支持

完整代码

vue
<!--
============================================================
                    Vue 3 模板语法基础
============================================================
Vue 使用基于 HTML 的模板语法,可以声明式地将 DOM 绑定到组件实例的数据。
所有 Vue 模板都是语法有效的 HTML。
============================================================
-->

<template>
    <div class="tutorial">
        <h1>Vue 3 模板语法教程</h1>

        <!-- ============================================================
                              1. 文本插值
             ============================================================ -->

        <!--
        【文本插值】
        使用双花括号 {{ }} 进行文本插值
        - 会自动转义 HTML(防止 XSS)
        - 支持 JavaScript 表达式
        - 响应式:数据变化时自动更新
        -->

        <section>
            <h2>1. 文本插值</h2>

            <!-- 基本用法 -->
            <p>消息: {{ message }}</p>

            <!-- 表达式 -->
            <p>计算: {{ count + 1 }}</p>
            <p>三元: {{ isActive ? '激活' : '未激活' }}</p>
            <p>方法: {{ message.toUpperCase() }}</p>
            <p>模板: {{ `你好, ${name}!` }}</p>

            <!-- 注意:只能是单个表达式,不能是语句 -->
            <!-- ❌ 错误:{{ let x = 1 }} -->
            <!-- ❌ 错误:{{ if (ok) { return 'yes' } }} -->
        </section>


        <!-- ============================================================
                              2. 原始 HTML
             ============================================================ -->

        <!--
        【v-html 指令】
        当需要输出真正的 HTML 时使用 v-html
        ⚠️ 警告:只对可信内容使用,避免 XSS 攻击
        -->

        <section>
            <h2>2. 原始 HTML</h2>

            <!-- 自动转义 -->
            <p>转义输出: {{ rawHtml }}</p>

            <!-- 原始 HTML -->
            <p>HTML 输出: <span v-html="rawHtml"></span></p>
        </section>


        <!-- ============================================================
                              3. 属性绑定
             ============================================================ -->

        <!--
        【v-bind 指令】
        用于动态绑定 HTML 属性
        - 完整语法:v-bind:attr="value"
        - 简写语法::attr="value"
        -->

        <section>
            <h2>3. 属性绑定</h2>

            <!-- 动态 id -->
            <div v-bind:id="dynamicId">动态 ID</div>

            <!-- 简写形式(推荐) -->
            <div :id="dynamicId">简写形式</div>

            <!-- 动态 class -->
            <div :class="className">动态类名</div>

            <!-- 动态 src -->
            <img :src="imageUrl" :alt="imageAlt" />

            <!-- 动态 disabled -->
            <button :disabled="isDisabled">
                {{ isDisabled ? '禁用' : '可用' }}
            </button>

            <!-- 布尔属性:值为 falsy 时,属性会被移除 -->
            <input :disabled="false" />  <!-- 不会有 disabled 属性 -->

            <!-- 动态绑定多个属性 -->
            <div v-bind="objectOfAttrs">多属性绑定</div>
        </section>


        <!-- ============================================================
                              4. Class 绑定
             ============================================================ -->

        <!--
        【Class 绑定】
        Vue 对 class 绑定做了增强,支持:
        - 对象语法
        - 数组语法
        - 混合使用
        -->

        <section>
            <h2>4. Class 绑定</h2>

            <!-- 对象语法:根据条件添加类 -->
            <div :class="{ active: isActive, 'text-danger': hasError }">
                对象语法
            </div>

            <!-- 绑定计算属性 -->
            <div :class="classObject">计算属性</div>

            <!-- 数组语法:绑定多个类 -->
            <div :class="[activeClass, errorClass]">数组语法</div>

            <!-- 数组中使用三元表达式 -->
            <div :class="[isActive ? 'active' : '', errorClass]">
                数组 + 三元
            </div>

            <!-- 数组中使用对象 -->
            <div :class="[{ active: isActive }, errorClass]">
                数组 + 对象
            </div>

            <!-- 与普通 class 共存 -->
            <div class="static-class" :class="{ active: isActive }">
                静态 + 动态
            </div>
        </section>


        <!-- ============================================================
                              5. Style 绑定
             ============================================================ -->

        <!--
        【Style 绑定】
        支持对象语法和数组语法
        CSS 属性名使用 camelCase 或 kebab-case(需要引号)
        -->

        <section>
            <h2>5. Style 绑定</h2>

            <!-- 对象语法 -->
            <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
                对象语法
            </div>

            <!-- kebab-case 属性名 -->
            <div :style="{ 'font-size': fontSize + 'px' }">
                kebab-case
            </div>

            <!-- 绑定样式对象 -->
            <div :style="styleObject">样式对象</div>

            <!-- 数组语法:合并多个样式对象 -->
            <div :style="[baseStyles, overridingStyles]">
                数组语法
            </div>

            <!-- 自动前缀 -->
            <!-- Vue 会自动添加浏览器前缀 -->
            <div :style="{ display: 'flex' }">自动前缀</div>
        </section>


        <!-- ============================================================
                              6. 条件渲染
             ============================================================ -->

        <!--
        【v-if / v-else-if / v-else】
        条件渲染:根据条件决定是否渲染元素
        - 真正的条件渲染:切换时会销毁和重建元素
        - 适合不经常切换的场景
        -->

        <section>
            <h2>6. 条件渲染</h2>

            <!-- v-if -->
            <p v-if="type === 'A'">类型 A</p>
            <p v-else-if="type === 'B'">类型 B</p>
            <p v-else>其他类型</p>

            <!-- 在 template 上使用(不会渲染额外元素) -->
            <template v-if="showGroup">
                <h3>标题</h3>
                <p>段落 1</p>
                <p>段落 2</p>
            </template>

            <!--
            【v-show】
            通过 CSS display 控制显示/隐藏
            - 元素始终存在于 DOM 中
            - 适合频繁切换的场景
            -->
            <p v-show="isVisible">v-show 元素</p>

            <!--
            【v-if vs v-show】
            v-if:更高的切换开销(销毁/创建)
            v-show:更高的初始渲染开销(始终渲染)

            频繁切换用 v-show,运行时条件很少改变用 v-if
            -->
        </section>


        <!-- ============================================================
                              7. 列表渲染
             ============================================================ -->

        <!--
        【v-for 指令】
        用于遍历数组或对象渲染列表
        - 语法:v-for="item in items"
        - 必须提供 key 属性
        -->

        <section>
            <h2>7. 列表渲染</h2>

            <!-- 遍历数组 -->
            <ul>
                <li v-for="item in items" :key="item.id">
                    {{ item.name }}
                </li>
            </ul>

            <!-- 带索引 -->
            <ul>
                <li v-for="(item, index) in items" :key="item.id">
                    {{ index + 1 }}. {{ item.name }}
                </li>
            </ul>

            <!-- 遍历对象 -->
            <ul>
                <li v-for="(value, key) in userInfo" :key="key">
                    {{ key }}: {{ value }}
                </li>
            </ul>

            <!-- 遍历对象(带索引) -->
            <ul>
                <li v-for="(value, key, index) in userInfo" :key="key">
                    {{ index }}. {{ key }}: {{ value }}
                </li>
            </ul>

            <!-- 遍历数字范围 -->
            <span v-for="n in 5" :key="n">{{ n }} </span>

            <!-- 在 template 上使用 -->
            <ul>
                <template v-for="item in items" :key="item.id">
                    <li>{{ item.name }}</li>
                    <li class="divider"></li>
                </template>
            </ul>

            <!--
            【key 的作用】
            - 帮助 Vue 识别节点
            - 用于 diff 算法优化
            - 必须是唯一的字符串或数字
            - 不推荐用 index 作为 key(除非列表不会变)
            -->
        </section>


        <!-- ============================================================
                              8. 事件处理
             ============================================================ -->

        <!--
        【v-on 指令】
        用于监听 DOM 事件
        - 完整语法:v-on:event="handler"
        - 简写语法:@event="handler"
        -->

        <section>
            <h2>8. 事件处理</h2>

            <!-- 内联语句 -->
            <button @click="count++">点击次数: {{ count }}</button>

            <!-- 方法处理器 -->
            <button @click="handleClick">方法处理</button>

            <!-- 传递参数 -->
            <button @click="handleClickWithArg('hello')">传递参数</button>

            <!-- 访问原始事件对象 -->
            <button @click="handleClickWithEvent($event)">获取事件</button>

            <!-- 多事件处理器 -->
            <button @click="handleA(), handleB()">多处理器</button>

            <!--
            【事件修饰符】
            -->
            <!-- .stop - 阻止冒泡 -->
            <div @click="handleOuter">
                <button @click.stop="handleInner">阻止冒泡</button>
            </div>

            <!-- .prevent - 阻止默认行为 -->
            <form @submit.prevent="handleSubmit">
                <button type="submit">提交</button>
            </form>

            <!-- .self - 只有事件源是自身时触发 -->
            <div @click.self="handleSelf">
                <button>点击按钮不触发</button>
            </div>

            <!-- .once - 只触发一次 -->
            <button @click.once="handleOnce">只能点一次</button>

            <!-- .capture - 使用捕获模式 -->
            <div @click.capture="handleCapture">捕获</div>

            <!-- .passive - 不阻止默认行为(提升滚动性能) -->
            <div @scroll.passive="handleScroll">滚动</div>

            <!-- 修饰符链 -->
            <a @click.stop.prevent="handleLink">链接</a>

            <!--
            【按键修饰符】
            -->
            <!-- 监听特定按键 -->
            <input @keyup.enter="submitForm" placeholder="按回车提交" />
            <input @keyup.esc="clearInput" placeholder="按 ESC 清空" />

            <!-- 组合键 -->
            <input @keyup.ctrl.enter="send" placeholder="Ctrl+Enter 发送" />
            <input @keyup.alt.s="save" placeholder="Alt+S 保存" />

            <!-- 精确修饰符:只有指定的键按下时触发 -->
            <input @keyup.ctrl.exact="onlyCtrl" placeholder="只按 Ctrl" />

            <!--
            【鼠标按键修饰符】
            -->
            <button @click.left="handleLeft">左键</button>
            <button @click.right="handleRight">右键</button>
            <button @click.middle="handleMiddle">中键</button>
        </section>


        <!-- ============================================================
                              9. 表单输入绑定
             ============================================================ -->

        <!--
        【v-model 指令】
        实现表单元素的双向数据绑定
        本质是 :value + @input 的语法糖
        -->

        <section>
            <h2>9. 表单绑定</h2>

            <!-- 文本输入 -->
            <div>
                <input v-model="textInput" placeholder="文本输入" />
                <p>输入值: {{ textInput }}</p>
            </div>

            <!-- 多行文本 -->
            <div>
                <textarea v-model="textareaInput" placeholder="多行文本"></textarea>
                <p>内容: {{ textareaInput }}</p>
            </div>

            <!-- 复选框 - 单个(布尔值) -->
            <div>
                <input type="checkbox" v-model="checked" id="checkbox" />
                <label for="checkbox">同意条款: {{ checked }}</label>
            </div>

            <!-- 复选框 - 多个(数组) -->
            <div>
                <input type="checkbox" v-model="checkedNames" value="Jack" id="jack" />
                <label for="jack">Jack</label>
                <input type="checkbox" v-model="checkedNames" value="John" id="john" />
                <label for="john">John</label>
                <input type="checkbox" v-model="checkedNames" value="Mike" id="mike" />
                <label for="mike">Mike</label>
                <p>选中: {{ checkedNames }}</p>
            </div>

            <!-- 单选按钮 -->
            <div>
                <input type="radio" v-model="picked" value="One" id="one" />
                <label for="one">One</label>
                <input type="radio" v-model="picked" value="Two" id="two" />
                <label for="two">Two</label>
                <p>选中: {{ picked }}</p>
            </div>

            <!-- 下拉选择 -->
            <div>
                <select v-model="selected">
                    <option disabled value="">请选择</option>
                    <option value="A">选项 A</option>
                    <option value="B">选项 B</option>
                    <option value="C">选项 C</option>
                </select>
                <p>选中: {{ selected }}</p>
            </div>

            <!-- 多选下拉 -->
            <div>
                <select v-model="multiSelected" multiple>
                    <option value="A">选项 A</option>
                    <option value="B">选项 B</option>
                    <option value="C">选项 C</option>
                </select>
                <p>选中: {{ multiSelected }}</p>
            </div>

            <!--
            【v-model 修饰符】
            -->
            <!-- .lazy - 在 change 事件后更新(而非 input) -->
            <input v-model.lazy="lazyValue" placeholder=".lazy" />

            <!-- .number - 自动转为数字 -->
            <input v-model.number="age" type="number" placeholder=".number" />

            <!-- .trim - 自动去除首尾空格 -->
            <input v-model.trim="trimValue" placeholder=".trim" />
        </section>
    </div>
</template>

<script setup>
/**
 * ============================================================
 *                    Script Setup 语法
 * ============================================================
 * <script setup> 是 Vue 3 的语法糖:
 * - 更简洁的代码
 * - 更好的运行时性能
 * - 更好的 IDE 类型支持
 * ============================================================
 */

import { ref, reactive, computed } from 'vue';

// ============================================================
//                    响应式数据
// ============================================================

// --- ref: 用于基本类型 ---
const message = ref('Hello Vue!');
const count = ref(0);
const name = ref('Alice');
const isActive = ref(true);
const hasError = ref(false);
const isDisabled = ref(false);
const isVisible = ref(true);
const showGroup = ref(true);
const type = ref('A');

// --- reactive: 用于对象 ---
const userInfo = reactive({
    name: 'Alice',
    age: 25,
    email: 'alice@example.com',
});

// --- 数组 ---
const items = ref([
    { id: 1, name: '苹果' },
    { id: 2, name: '香蕉' },
    { id: 3, name: '橙子' },
]);

// --- 属性绑定相关 ---
const dynamicId = ref('my-element');
const className = ref('container');
const imageUrl = ref('/images/logo.png');
const imageAlt = ref('Logo');

// 绑定多个属性
const objectOfAttrs = reactive({
    id: 'container',
    class: 'wrapper',
    'data-test': 'value',
});

// --- Class 绑定相关 ---
const activeClass = ref('active');
const errorClass = ref('text-danger');

// 计算属性返回 class 对象
const classObject = computed(() => ({
    active: isActive.value,
    'text-danger': hasError.value,
}));

// --- Style 绑定相关 ---
const activeColor = ref('red');
const fontSize = ref(16);

const styleObject = reactive({
    color: 'blue',
    fontSize: '18px',
    fontWeight: 'bold',
});

const baseStyles = reactive({
    color: 'green',
});

const overridingStyles = reactive({
    fontSize: '20px',
});

// --- HTML 相关 ---
const rawHtml = ref('<span style="color: red;">红色文字</span>');

// --- 表单相关 ---
const textInput = ref('');
const textareaInput = ref('');
const checked = ref(false);
const checkedNames = ref([]);
const picked = ref('');
const selected = ref('');
const multiSelected = ref([]);
const lazyValue = ref('');
const age = ref(18);
const trimValue = ref('');


// ============================================================
//                    事件处理函数
// ============================================================

/**
 * 基本点击处理
 */
function handleClick() {
    console.log('按钮被点击');
    message.value = '按钮被点击了!';
}

/**
 * 带参数的点击处理
 * @param {string} msg - 传入的消息
 */
function handleClickWithArg(msg) {
    console.log('收到参数:', msg);
}

/**
 * 带事件对象的处理
 * @param {Event} event - 原始事件对象
 */
function handleClickWithEvent(event) {
    console.log('事件类型:', event.type);
    console.log('目标元素:', event.target);
}

// 多处理器
function handleA() {
    console.log('处理器 A');
}

function handleB() {
    console.log('处理器 B');
}

// 事件修饰符相关
function handleOuter() {
    console.log('外层点击');
}

function handleInner() {
    console.log('内层点击');
}

function handleSubmit() {
    console.log('表单提交');
}

function handleSelf() {
    console.log('只有点击自身才触发');
}

function handleOnce() {
    console.log('只触发一次');
}

function handleCapture() {
    console.log('捕获阶段触发');
}

function handleScroll() {
    console.log('滚动');
}

function handleLink() {
    console.log('链接点击');
}

// 按键相关
function submitForm() {
    console.log('提交表单');
}

function clearInput() {
    textInput.value = '';
}

function send() {
    console.log('发送');
}

function save() {
    console.log('保存');
}

function onlyCtrl() {
    console.log('只按了 Ctrl');
}

// 鼠标按键
function handleLeft() {
    console.log('左键点击');
}

function handleRight() {
    console.log('右键点击');
}

function handleMiddle() {
    console.log('中键点击');
}
</script>

<style scoped>
/*
【scoped 样式】
scoped 属性使样式只作用于当前组件
*/

.tutorial {
    padding: 20px;
    max-width: 800px;
    margin: 0 auto;
}

section {
    margin-bottom: 30px;
    padding: 15px;
    border: 1px solid #eee;
    border-radius: 8px;
}

h2 {
    color: #42b883;
    border-bottom: 2px solid #42b883;
    padding-bottom: 10px;
}

/* Class 绑定示例样式 */
.active {
    color: green;
    font-weight: bold;
}

.text-danger {
    color: red;
}

.static-class {
    padding: 10px;
    background: #f5f5f5;
}

/* 其他样式 */
button {
    margin: 5px;
    padding: 8px 16px;
    cursor: pointer;
}

input, select, textarea {
    margin: 5px;
    padding: 8px;
}

.divider {
    height: 1px;
    background: #eee;
    list-style: none;
}
</style>

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布