资讯动态

Vue 模板语法与指令实战:用笔记卡片吃透数据驱动视图

发布时间:2026/10/15 4:42:15 来源:尧图企业网站定制
如果现在让我回答 Vue 学习里最容易被低估的一环我会毫不犹豫地说模板语法和指令。这是 Vue 学习系列的第二篇我用这周刚写完的一个“笔记卡片”小例子把所有最核心的响应式绑定、常用指令和计算属性串起来讲。这一篇主要解决的问题是当你已经看完一遍官方教程知道{{ }}、v-model、v-for这些词但真要把一个静态页面变成可输入、可搜索、可删除的“活页面”时却不知道从哪下手。适合刚学完 HTML/CSS/JavaScript、准备进入框架阶段的人也适合那些用过 Vue 但偶尔被computed缓存坑一把的开发者。1. 设计思路为什么第二篇先啃模板与指令1.1 别急着学组件先把数据驱动视图的感觉找到在很多新手眼里Vue 学习路线的第一个大关卡是组件、路由、状态管理实际上从我的学习过程来看第二轮复习最有价值的反而是最基础的模板语法和指令。原生 JavaScript 时代我们要做交互通常得先document.querySelector拿到 DOM然后监听事件、更新属性、修改 innerHTML。Vue 把这套流程完全颠倒过来你只负责维护数据视图会根据数据的变化自动变化。这一套思想看着简单但真正落实到模板里就会遇到v-if和v-show怎么选、v-for的 key 到底放什么、v-model和v-bind看起来一样为什么不能混用这些很具体的问题。这些问题我都在实际项目里踩过所以第二篇想先集中解决这一层。为什么不是第一篇就讲因为第一篇通常是跑通环境、写一个Hello World注意力还在“怎么启动项目”上。到了第二篇工具链已经不那么陌生正是理解数据驱动视图的好时机。我强烈建议这一篇不要跳着看模板语法和指令是后面所有组件通信、表单验证、列表优化都要用的地基。地基不稳后面组件封装得越高级排查问题越痛苦。1.2 用一个“笔记卡片”示例串起知识点的理由单纯讲指令列表很枯燥所以我特意设计了一个非常小的场景一个笔记卡片管理页面。页面上有一个标题输入框、一个内容输入框、一个保存按钮保存后会生成一张卡片下面还有一个搜索框可以按关键词实时过滤卡片每张卡片右上角有一个删除按钮。就这么一个功能几乎把 Vue 入门最重要的知识点全用上了v-model负责输入框和数据的双向同步click负责新增和删除v-for负责列表渲染computed负责搜索过滤和字数统计再配合v-bind动态绑定类名和样式。选笔记而不是选复杂的管理系统是因为它不需要后端也能完整跑通数据和视图的联动看得见摸得着。写完这个 demo 之后我对指令的印象不再是单独的语法点而是可以组合起来解决真实需求的小工具。这也是我想通过这篇博文传递的知识点要放在一个能跑通的小场景里学比背指令列表管用得多。2. 必须吃透的模板、指令与计算属性细节2.1 模板插值从 mustache 到表达式所有 Vue 模板都从{{ }}开始。{{ content }}会把content这个变量的值渲染成文本它支持 JavaScript 表达式比如{{ content.length }}、{{ title.trim().length }}。但这里有一条我认为算是入门金律的原则模板里可以写简单表达式绝对不要写复杂逻辑。我之前为了偷懒在模板里写过一段长长的三目运算后来数据变化导致页面渲染变慢排查半天才发现问题出在模板表达式里每次渲染都要重新执行。复杂的格式化、过滤、统计应该交给computed属性处理模板只负责显示结果。另一个容易忽略的点是{{ }}默认输出纯文本即使content的值是一段html标签也会被当作普通字符串显示出来。这样做是出于安全考虑防止意外注入脚本。如果你确实需要渲染 HTML可以用v-html但使用前提是你非常确定内容来源可信。在自己的笔记 demo 里我建议不要碰v-html直接用插值就够了。2.2 从 v-bind 到 v-model三种指令形态要分清指令是模板里带v-前缀的特殊属性。常见的有几十种但入门阶段真正高频的是三个v-bind、v-on、v-model。先看v-bind它负责把数据绑定到 HTML 属性上比如:classisActive ? active : 、:titlenote.createdAt。注意这里的冒号就是v-bind:的缩写。它跟{{ }}的区别在于插值通常出现在标签内容里属性位置的动态绑定必须用v-bind。如果写成class{{ isActive }}部分属性也许能生效但一旦涉及到布尔属性和复杂绑定就会出问题干脆统一用冒号写法。再看v-on它负责监听 DOM 事件缩写是。clickaddNote表示点击后调用方法keyup.enteraddNote表示在输入框中按回车时触发。方法定义在methods里方法内部可以访问this此时this指向组件实例所以this.notes.push()才能修改数据并触发视图更新。使用事件时还有一个实用细节如果需要传参数和事件对象写成clickremoveNote(note.id, $event)模板里的$event就是原生事件对象可以传给方法。最后是v-model它看起来像绑定属性实际上是语法糖。以输入框为例v-modeltitle等价于:valuetitle加上inputtitle $event.target.value。它解决了表单场景里“数据到视图”和“视图到数据”两个方向都要同步的麻烦。值得注意的是不要在一个表单元素上同时使用v-model和手动绑定value那会造成数据来源混乱我在初学时就因为想覆盖默认值写了value导致输入框清空不了。2.3 计算属性为什么比 methods 更适合做派生数据在笔记 demo 里统计字数和过滤搜索我都会用computed而不是写在methods里让模板调用。原因很简单计算属性有缓存。methods里的方法每次页面重新渲染都会从头执行一遍哪怕方法依赖的数据根本没变。computed会追踪自己在计算过程中读取过的响应式数据只要这些数据没变再次访问直接返回上一次的缓存结果。用生活类比来说methods是每次有人问你都要重新算一遍的财务表computed是只有当收入或支出变动时才重算的报表数据没变哪怕问一百次都是秒回。使用计算属性时我建议遵守一个习惯不要在computed里做修改数据、发请求、写日志这类副作用操作。计算属性应该是一个纯函数输入一组响应式依赖输出一个新值。如果你发现自己需要“在数据变化后做某件事”比如数据更新后把值持久化到本地存储那应该用watch而不是在computed里偷偷处理。第二篇只需要掌握computed能算、watch能看后面的环节再深入也不迟。2.4 条件渲染v-if 和 v-show 的选择标准笔记 demo 里我用v-if来显示空状态。日常开发中条件渲染更常遇到的是v-if和v-show的取舍。v-if是真正的条件渲染条件为假时元素根本不会出现在 DOM 里v-show只是把元素的display设置为none元素始终在 DOM 中。所以如果是初始化时就确定大多数时候不需要显示的内容用v-if更省资源如果频繁切换显示隐藏用v-show性能更好。还有一个细节v-if搭配v-else时二者之间的元素必须紧密相邻中间不能插入其他节点否则v-else不生效。3. 手写一个笔记卡片双向绑定、字数统计与实时搜索实战3.1 搭建一个最小 Vue 应用先说明一下版本和写法。我这里以 Vue 3 为例但使用的模板语法和 Options API 的写法在 Vue 2 里也基本通用差别主要在于data必须是一个函数以及v-model对中文输入法处理上的细节。为了把注意力放在模板和指令上我建议先直接在本地新建一个 HTML 文件用script标签引入 Vue 的全局版文件来跑不需要考虑打包工具。关键的最小结构是这样的index.html里放一个挂载点div idapp然后是 Vue 脚本核心逻辑通过Vue.createApp创建应用并绑定到#app上。下面是一段可以直接运行的最小示例!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleVue 学习第二篇/title /head body div idapp p{{ message }}/p /div script srcvue.global.js/script script Vue.createApp({ data() { return { message: Hello Vue } } }).mount(#app) /script /body /html我再解释一下这段代码背后的执行顺序Vue.createApp接收一个选项对象在data函数里声明的数据会被 Vue 转化为响应式数据mount(#app)把模板里所有占位符和指令解析一遍渲染到页面上。页面加载之后message的值就被渲染到p里。后续只要数据变化Vue 会精确找到依赖它的 DOM 节点并更新不需要你手动操作。3.2 实现输入双向绑定和字数实时统计最小示例跑通之后我们把#app内部的模板替换成真正需要的笔记输入区。标题输入框、内容输入框、字数统计和保存按钮构成一个完整的表单区域代码如下div idapp div classform input v-modeltitle placeholder输入卡片标题 textarea v-modelcontent placeholder写点什么.../textarea p当前字数{{ wordCount }}/p button clickaddNote保存卡片/button /div /div这里v-model双向绑定了两个字段。wordCount是计算属性我们不直接用content.length是因为希望统计的是去掉首尾空格后的有效字数在模板里写content.trim().length虽然可行但每次都重新计算放到computed里具备缓存逻辑也更清晰。对应的data和computed如下data() { return { title: , content: , notes: [], nextId: 1 } }, computed: { wordCount() { return this.content.trim().length } }, methods: { addNote() { const title this.title.trim() const content this.content.trim() if (!title !content) { alert(标题和内容至少写一个) return } this.notes.push({ id: this.nextId, title, content }) this.title this.content } }保存按钮的逻辑很简单先去掉首尾空格判断空内容没问题就推入notes数组并清空两个输入框。这里必须注意一个细节notes里的每一项都有一个id这个id在渲染列表时会被用作key。如果没有唯一 id删除中间项后列表复用会出问题后面第 4 节会专门讲。3.3 用计算属性实现即时搜索现在把卡片列表和搜索框加进去。搜索框用v-modelkeyword绑定搜索词列表用v-for遍历filteredNotes这个方法返回的是根据keyword过滤后的列表。注意v-for的:key我用的是note.id而不是数组索引因为在删除元素或排序的场景下索引作为 key 可能让 Vue 复用错误的 DOM 节点。input v-modelkeyword placeholder搜索笔记 div v-iffilteredNotes.length 0 classempty 没有匹配的笔记 /div article v-fornote in filteredNotes classnote-card :keynote.id h3{{ note.title || 未命名 }}/h3 p{{ note.content }}/p button clickremoveNote(note.id)删除/button /article对应的computed和methods逻辑computed: { filteredNotes() { const kw this.keyword.trim().toLowerCase() if (!kw) return this.notes return this.notes.filter(note { const titleMatch note.title.toLowerCase().includes(kw) const contentMatch note.content.toLowerCase().includes(kw) return titleMatch || contentMatch }) } }, methods: { removeNote(id) { this.notes this.notes.filter(note note.id ! id) } }这段代码里的两个关键点第一个是toLowerCase()它让搜索不区分大小写中文场景下没有影响英文内容时能避免“搜索不到”的困惑第二个是filter方法会返回一个新数组并不会修改notes本身所以搜索前后原数据始终完整。v-if放在v-for外部用来处理列表为空的状态这样比在同一个元素上同时使用v-for和v-if更安全也更容易理解。3.4 动态 class让卡片支持选中态笔记卡片列表除了显示我还加了一个很轻量的交互点击卡片时给卡片添加一个selected类用于高亮当前选中的卡片。这个功能不需要额外存储复杂的样式状态只需在data中增加一个selectedId默认是null。模板里写:class{ selected: note.id selectedId }点击卡片时调用selectNote(note.id)修改后的代码如下article v-fornote in filteredNotes classnote-card :class{ selected: note.id selectedId } :keynote.id clickselectNote(note.id) h3{{ note.title || 未命名 }}/h3 p{{ note.content }}/p button click.stopremoveNote(note.id)删除/button /articledata() { return { selectedId: null } }, methods: { selectNote(id) { this.selectedId id } }这就是v-bind最常见的动态 class 用法:class支持对象语法键是类名值是布尔条件。当selectedId等于当前卡片的id时selected类就会被加上。这里有个容易被忽略的细节删除按钮的click.stop是事件修饰符作用是阻止点击删除按钮时继续冒泡到article上。如果不加.stop点击删除会先触发removeNote然后又触发selectNote选中状态就会被改写。把这个小交互加进笔记 demo 后:class的抽象理解会变得非常具体。4. 我踩过的 Vue 基础坑排查技巧速查表4.1 页面空白、数据没渲染的排查顺序我在第一次跑通这个 demo 时遇到的最大问题不是语法而是页面完全空白。后来我把排查顺序固定成下面这几步先看浏览器控制台有没有 JavaScript 报错再确认mount(#app)里面的 id 和 HTML 里的容器 id 完全一致然后检查data函数有没有写return漏了return的后果是模板里拿不到任何数据最后检查模板插值里的字段名和data里的字段名是否拼写一致。为了快速定位我会在模板里临时加一行{{ JSON.stringify(notes) }}输出调试信息看到实际数据比反复猜字段名有效得多。4.2 v-model 丢失响应性的常见错误很多刚接触v-model的人会困惑为什么我用它绑定了对象里的某个属性页面却不更新在 Vue 3 中对象属性的新增和修改默认都是响应式的不需要额外处理。但在 Vue 2 中如果属性在初始化时不存在之后动态添加可能导致丢失响应性当时需要使用额外方法手动处理。我建议大家学习时直接看 Vue 3 的官方文档同时知道 Vue 2 这个历史坑后面遇到老项目时不至于抓瞎。另一个容易搞混的是v-bind和v-model的区别。v-bind是单向的数据变了视图变但视图变了数据不变v-model是双向的专门给表单控件和自定义组件使用。如果在普通div上写v-model并不会像注册了input事件的自定义组件一样生效控制台还会给出语法警告。遇到这种情况先去检查你绑定的元素类型。4.3 计算属性缓存与方法的取舍计算属性最隐蔽的坑是依赖没变但它不会重新计算。这不是 bug而是缓存机制。有一次我发现搜索列表没有更新检查半天才发现我在filteredNotes里过滤时依赖的是notes和keyword但搜索后我手动修改了notes里的某个元素没有改变notes数组本身的引用计算属性认为依赖没变自然不更新。数组内部元素修改在 Vue 3 里是响应式的但如果你把computed的返回值直接拿来修改也会带来类似问题。computed应该只读不改变源数据。如果要在计算和副作用之间做选择我会用一句话概括能通过computed算出来的用computed需要在数据变化后执行异步或者额外操作的用watch。初学者最容易犯的错误是在computed里调用console.log或者修改另一个data字段这些都会让计算属性的纯函数性质变味调试起来非常折磨。4.4 列表 key 的坑给v-for加key几乎是所有 Vue 项目里出现频率最高的警告之一。原因在于 Vue 会利用key来确认哪些元素是同一实体以便在更新时尽量复用而不是重建。如果使用数组索引作为key当删除中间项或者做排序时索引会发生错位让 Vue 误以为某个元素没有变化最终导致状态错乱。比如一个列表项内部有输入框时使用索引作为 key删除第一项后第二项的输入值可能会保留在错误的节点上。规避方法很简单新增数据时用一个自增id或者用其他业务上不会重复的字段。我在笔记 demo 里用nextId累积虽然简单但已经足够示范。真正项目中如果从后端拿数据通常会有一个唯一主键网络请求返回后直接用主键作为key即可。4.5 高频问题速查表问题现象可能原因排查与解决办法页面空白容器 id 不匹配或 JS 报错打开控制台查看错误确认 mount 选择器和 HTML id 一致输入框不更新视图v-model 写到了非表单控件上或手动加了 value 属性检查元素类型去掉重复 value 绑定computed 不更新依赖的引用没变或内部依赖了非响应式数据确认依赖字段是否响应式必要时打日志观察列表顺序错乱key 使用了数组索引改成唯一 id 字段删除后 input 值残留列表项状态被复用确认 key 唯一避免 index key5. 学习复盘下一步应该优先补什么5.1 让我后知后觉的几个学习顺序学到这里你会发现Vue 基础知识的能量密度其实很高。第一篇解决了“跑起来”第二篇就该解决“数据怎么驱动视图”。我自己体会最深的是不要一上来就学集中状态管理库先把模板语法和指令练熟因为大部分日常页面都是数据加上列表和表单的组合一套基础语法足够覆盖。等遇到兄弟组件或跨页面共享数据时你才真正能理解状态管理解决的是什么问题。另外我建议每个新知识点都写一个最小 demo 并立刻改着玩。比如今天学到v-model可以先用它绑定一个输入框再给它加一个字数统计慢慢扩展到搜索过滤。亲手改一遍比看十遍教程印象深得多。我发现自己对这个笔记 demo 的完整度记住不是因为代码复杂而是因为每一步都是自己敲出来的。5.2 下一步可以怎么扩展这个例子笔记卡片现在还没有保存能力刷新页面就回到原始数据这是下一步最明显可以扩展的方向。先用watch监听notes的变化配合本地存储把数据持久化再加上一个标签字段让搜索能按标签过滤最后把单个卡片抽成子组件用props传标题和内容用$emit通知父组件删除。这样写下来第二篇的模板语法会成为组件那篇的天然铺垫。再分享一个小技巧调试computed的时候可以在 getter 内部打一个console.log然后看它依赖的数据。比如filteredNotes里访问了this.keyword把搜索框的关键字打印出来一眼就能看出依赖有没有触发。打完日志记得删掉别让调试代码留在最终的计算属性里。踩过几次坑之后我越来越确信Vue 的模板和指令看起来只是入门语法但实际项目中一半以上难以定位的渲染问题最后都出在这一层。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑