第一次接触Vue2,很多朋友是被脚手架劝退的。装了Node、配了npm源,一创建项目又跳出一堆文件,还没写几行代码,先被webpack配置和目录结构搞晕了。我自己入门前端时也是这样,直到某天接手一个用CDN引入vue.js的旧页面,才发现Vue2的入门门槛完全被工具链拔高了——真正理解“创建实例”和“指令”,根本不需要脚手架,一个HTML文件加一个script标签就够了。
这篇文章就是写给零基础入门前端、或者刚上手Vue2但总觉得虚的朋友。我会从最原始的CDN方式开始,带你亲手创建一个Vue实例,再把Vue2最常用的内置指令逐个拆解一遍,讲清楚每个指令背后的原理、适用场景和容易踩的坑。文章里的所有代码都可以直接复制到本地运行,建议你边看边敲,十分钟就能跑通第一个Vue2应用。
1. 先别急着上脚手架:CDN方式跑通第一个Vue2实例
1.1 为什么入门阶段强烈推荐CDN方式
脚手架解决的是工程化问题,而不是框架理解问题。Vue CLI或者Vite创建出来的项目,本质上是帮我们把编译、打包、热更新这些事提前配置好了,但对于一个还没搞懂data和methods是什么关系的新手来说,这些反而是噪音。你写一个简单的计数器页面,脚手架模式下可能要去src/views里建文件、改路由、注意import语法,任何一个环节报错都够你排查半天。
CDN方式就直白多了。一个HTML文件,引入vue.js的远程地址,然后直接写模板和脚本。浏览器打开就能看到效果,没有任何中间环节。这让我想起学游泳,脚手架像是直接把你扔进深水区,而CDN方式是在浅水区先让你扶着池壁来回走几趟,先把水性练出来。
另外别小看CDN方式,实际情况里很多存量系统和营销页面都是用这种方式写Vue2的。有些老项目至今还在用vue@2.6.x的CDN链接做页面交互,所以你学会这种方式,不仅是为了入门,它本身就是一个能实战的技能。
1.2 一个最基础的Vue实例完整拆解
先在本地建一个HTML文件,比如叫vue2-demo.html,把下面这段代码粘进去,浏览器打开试试。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Vue2 初体验</title> </head> <body> <div id="app"> <h1>{{ message }}</h1> <p>计数结果:{{ count }}</p> <button @click="count += 1">点击加一</button> </div> <!-- 引入 Vue2 核心库 --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { message: '你好,Vue2', count: 0 } }); </script> </body> </html>打开页面,你会看到“你好,Vue2”和“计数结果:0”,点一下按钮,数字就变成1。就这么简单,你的第一个Vue实例已经跑起来了。接下来我们对这个实例做一次“解剖”。
new Vue({...})是创建Vue实例的入口,本质上就是一个构造函数调用。它接收一个对象作为参数,这个对象被称为“选项对象”,Vue几乎所有能力都是通过这个对象里的不同字段配置出来的。最常见的基础配置就三个:el、data、methods。
el用来告诉Vue“我要接管页面上的哪一部分”,它接收一个选择器字符串。这里写'#app',意味着Vue会找到id为app的那个div,然后把这个div内部的所有内容变成自己的模板。一个页面上可以创建多个Vue实例,分别接管不同的区域,不过实际开发中通常一个页面只创建一个根实例,因为多个实例之间传值很麻烦。
data用来声明这个实例的状态数据。上面代码里定义了message和count两个字段,模板里用双花括号{{ message }}的插值语法引用它。
有个关键点必须说清楚:data里的字段一旦被Vue接管,后续你在控制台输入app.count = 5,页面数字会立刻变成5。这背后就是Vue2的响应式系统在工作,后面专门用一节讲它。
methods用来声明实例的方法。注意构造函数里的methods是一个对象,对象的值是函数,比如:
new Vue({ el: '#app', data: { count: 0 }, methods: { addCount() { this.count += 1; } } });模板里改成<button @click="addCount">点击加一</button>,效果一样,但逻辑更清晰。在methods的内部函数里,this指向当前Vue实例,所以你可以放心地用this.count访问data里的字段。
1.3 el和$mount:两种挂载方式取舍
上面代码里用的是el选项,它相当于创建实例时立刻执行挂载。还有一种等价的写法是用$mount方法手动挂载:
new Vue({ data: { message: '你好,Vue2' } }).$mount('#app');区别在于挂载时机。用el时,Vue在内部初始化完成后马上执行挂载;用$mount时,你可以延迟决定挂到哪去,比如等某个异步条件成立后再挂载某个指定容器。日常开发里更常用el,但有前后端分离场景下如果DOM是在异步渲染完成后才存在,$mount就灵活很多。
把上面的HTML在浏览器里跑通后,你自己稍微改一改data里的字段、加一两个method试试。最快的学习方式,就是先破坏它再修复它——把el: '#app'改成el: '#app2',控制台会报错提示找不到元素,这个报错能帮你加深记忆。
2. 响应式系统:为什么改data页面会跟着变
2.1 这个“魔法”的底层机制
很多新手第一次用Vue时,会被“改了数据,视图自动更新”这个现象迷住,觉得像魔法。但它其实是基于一个很朴素的原理:数据劫持加上发布订阅模式。
Vue2在实例初始化的时候,会遍历data对象里的每一个属性,然后通过Object.defineProperty给这些属性重新定义getter和setter。当你在模板中使用某个属性时,就会触发它的getter,Vue趁机把这个属性记录下来——这个过程叫“依赖收集”。当你在代码里给这个属性赋新值时,会触发它的setter,Vue收到通知后,就会去通知所有依赖这个属性的视图位置执行更新——这个过程叫“派发更新”。
用生活化的方式理解:data里的每个属性就像一个通知栏,getter是“有人来看我了”,setter是“有人改我了”。Vue相当于一个物业管理员,在通知栏上贴了一张登记表,谁来看过就登记谁,谁改了就挨个通知登记过的住户。
2.2 用原生JS模拟这个原理
如果你对Object.defineProperty不熟,我们自己写一段极简代码模拟Vue2的响应式,你会发现原理真的不神秘。
function observe(obj) { Object.keys(obj).forEach(key => { let value = obj[key]; const dep = []; Object.defineProperty(obj, key, { get() { if (window.currentWatcher) { dep.push(window.currentWatcher); } return value; }, set(newVal) { value = newVal; dep.forEach(watcher => watcher()); } }); }); } const data = { message: 'hello' }; observe(data); // 模拟一个渲染函数订阅了 message window.currentWatcher = () => { console.log('视图更新:', data.message); }; console.log(data.message); // 触发 get,完成订阅 window.currentWatcher = null; data.message = 'world'; // 触发 set,打印“视图更新: world”这段代码已经能看出Vue2响应式的基本轮廓:读取时收集依赖,修改时触发更新。Vue只是把维护依赖这件事做得更精细、更自动,还加了一层Watcher来对应每个组件实例,最终把变更精确推送到DOM操作上。
2.3 数据代理:为什么data里的字段能直接用this访问
在Vue实例里,你写this.count就能拿到data里声明的count,这看起来挺自然的,但背后有一层“数据代理”。Vue初始化时,在实例上创建了一个与data字段同名的代理属性,它的getter和setter内部实际上访问的是this.$data里对应的属性。
new Vue({ data: { message: 'hello' }, created() { // 两者等价 console.log(this.message); console.log(this.$data.message); } });这样做的好处是模板和methods代码里不用写繁琐的this.$data.count,直接this.count就够了。理解这一层,再看Vue官方文档里关于$data、$el这些实例属性的说明就会顺畅很多。
2.4 Vue2响应式最著名的三个坑
既然用Vue2,必须知道它响应式的边界,这些边界是面试常问、实战常踩的。
第一个坑:直接给data对象新增一个不存在的字段,视图不会更新。比如data里只有name,你在代码里执行this.age = 18,然后模板里写{{ age }},页面不会显示18。原因在于Object.defineProperty只能对已存在的属性做劫持,新增属性没有经过setter定义,Vue感知不到。解决办法是用Vue.set方法:
Vue.set(this.obj, 'age', 18);或者在组件里用this.$set,两者作用相同。
第二个坑:通过下标直接修改数组元素不响应。比如this.items[0] = 'a',视图不会更新。Vue2为了性能,没有对数组的每个下标做数据劫持,而是选择了重写数组的七个方法(push、pop、shift、unshift、splice、sort、reverse)来触发更新。所以直接改下标是没用的,应该用splice:
this.items.splice(0, 1, 'a');或者用Vue.set(this.items, 0, 'a'),本质一样。
第三个坑:Object.assign覆盖属性时,如果新对象里没有带上所有原字段,丢失的字段不会响应。很多人喜欢直接Object.assign(this.form, res.data),如果接口返回的字段比本地form少,那form里原来有而返回值里没有的字段,之后改了也不会触发视图更新。更稳妥的做法是手动逐个字段赋值,或者先遍历接口数据this.$set,再整体替换。
这三个坑说着简单,实战里几乎每个人都会遇到至少一次。入门阶段提前知道,后面能省下好多排查时间。
3. 指令拆解:Vue2内置指令逐个过一遍
3.1 插值语法与v-text、v-html的区别
模板里最基础的输出方式是双花括号插值{{ message }},它相当于在页面里插入一段文本。和它功能类似的还有v-text和v-html两个指令。
<p>{{ text }}</p> <p v-text="text"></p> <p v-html="htmlContent"></p>v-text的作用和插值一样,都是输出纯文本,区别在于如果你希望整个元素的文本内容都由某个变量控制,不掺杂其他静态文字,用v-text更直观。比如<p v-text="tip"></p>,tip的值会完全覆盖p标签的原始内容。
v-html则会真正解析HTML字符串,渲染出对应的DOM节点。比如data里有一串'<strong>加粗</strong>',用v-html就能渲染出加粗效果。这里必须提醒:千万不要把用户输入的内容直接丢给v-html,这会带来跨站脚本攻击(XSS)风险。用户提交的富文本内容,如果不在服务端做安全过滤就直接用v-html渲染,等于把执行任意脚本的机会拱手送人。我见过某后台系统的活动备注字段被人贴了一段恶意脚本,差点出事。实战原则是:能用插值绝不用v-html,必须用的时候,内容要么来自可信的富文本编辑器输出,要么经过服务端白名单过滤。
3.2 v-bind:单向绑定让属性动起来
插值语法只能写在元素标签内部的内容区域,如果要绑定HTML属性,比如src、href、class、style,就得用v-bind。它的作用是把标签的一个属性与Vue实例里的数据单向绑定起来,数据变化时属性自动更新。
<img v-bind:src="imgUrl" alt="图片"> <a v-bind:href="linkUrl">跳转链接</a>v-bind有个非常常用的简写语法,直接用一个英文冒号代替v-bind::
<img :src="imgUrl" alt="图片">绑定的属性值不仅仅可以是简单数据,还可以是表达式、三元判断、方法返回值。比如绑定class时经常用对象语法或数组语法:
<!-- 对象语法:值为true时类名才生效 --> <div :class="{ active: isActive, 'text-danger': hasError }">状态容器</div> <!-- 数组语法:可以同时混入多个类和条件类 --> <div :class="[baseClass, isActive ? 'active' : '']">混合类</div>绑定style时用对象语法,属性名用驼峰式:
<div :style="{ color: fontColor, fontSize: fontSize + 'px' }">文字样式</div>注意那是fontSize: fontSize + 'px',因为style对象属性已经是数字或字符串,这里用加号拼接了单位。很多人刚学时容易忘记给单位,页面样式就没生效。
3.3 v-on:事件绑定和它的简写
v-on用来监听DOM事件并执行方法,最典型的是监听按钮点击:
<button v-on:click="handleClick">点击</button> <!-- 简写形式 --> <button @click="handleClick">点击</button>在HTML原生世界里,你需要document.getElementById(...).addEventListener(...)一堆代码,而Vue把这件事统一进了模板,直接用指令绑定就行。v-on:click可以简写成@click,这个简写在Vue生态里几乎成了标配,你看到的绝大多数Vue2代码片段里都是@click。
事件绑定还支持内联语句,适合做一些轻量操作:
<button @click="count += 1">加一</button> <button @click="handleClick(2, $event)">传参</button>第二个按钮里$event是Vue提供的特殊变量,代表原始浏览器事件对象。如果方法里需要同时接收业务参数和事件对象,就可以像这样显式传入。
3.4 v-model:表单双向绑定的语法糖
表单处理是前端开发里最繁琐的事情之一,Vue2用v-model把这件事极大地简化了。它最典型的用法是绑定输入框的值:
<input v-model="username"> <p>你输入的是:{{ username }}</p>用户在输入框里打字,username这个数据会实时变化;反过来如果代码里修改了username,输入框里的值也会跟着变。这种“数据变视图变、视图变数据变”的效果就是双向绑定。
理解它的语法糖本质很重要:v-model其实是v-bind:value和v-on:input的组合。上面那个输入框,等价于:
<input v-bind:value="username" v-on:input="username = $event.target.value">v-bind:value负责从数据到视图这一路,v-on:input负责从视图到数据这一路。搞清楚这一点,你就明白为什么v-model可以拆开使用,以及在自定义组件上实现v-model时要怎么处理value和input事件。
v-model在不同表单元素上的表现略有差异。文本框和文本域用的是value属性加input事件,复选框和单选框用的是checked属性加change事件,下拉框用的则是value加change事件。这些细节不需要背,用的时候观察到效果不对,回来对照这个原理就能定位问题。
3.5 v-if与v-show:两个条件渲染指令的选择
条件渲染的指令有v-if和v-show,它们都能控制元素的显示和隐藏,但背后的机制截然不同。
v-if是“真·条件渲染”,值为false时,对应元素根本不会被渲染到DOM里,你检查页面源码都找不到它。它可以是惰性的:如果初始条件为false,同期什么事情都不做,直到条件第一次变为true时才真正渲染元素。
v-show则完全不同,它的内部机制是切换元素的display样式。不管条件是true还是false,元素始终渲染在DOM里,条件为false时不过是加一行style="display: none;"把元素藏起来。
选择策略很简单:如果元素需要频繁切换显示状态,比如弹窗的开合、折叠面板的展开收起,用v-show性能更好,因为它避免了一次次的重新创建和销毁。如果元素很少切换、甚至只在某种场景下才出现一次,用v-if更合适,因为初始渲染时少占一些DOM节点。要注意一个误用场景:某后端管理系统里用户角色为管理员时才显示“配置”按钮,这个按钮几乎不会切换状态,这种情况应该用v-if,让非管理员用户从DOM层面就看不到这个元素,更符合权限语义。
3.6 v-for:列表渲染和三件套注意事项
v-for用来渲染数组或对象列表,最常见的用法:
<ul> <li v-for="(item, index) in list" :key="index">{{ index }} - {{ item.name }}</li> </ul>item是数组元素,index是下标,两个参数的位置是固定的。如果你遍历的是对象,参数顺序会变成(value, key, index),这和对象遍历的习惯有关。
v-for必须配合:key使用,这是Vue2里最容易被低估的一条规则。key的作用是给每个节点一个稳定唯一的身份标识,Vue在更新列表时,通过对比key来判定哪些节点可以复用、哪些需要移动或删除,这直接关系到列表渲染的性能和正确性。如果key写得不合适,最典型的问题是列表顺序变化时,输入框的值错位、组件状态错乱。
关于key的完整细节,我会在第五章重点展开,这里你先记住一个原则:永远不要下意识用index当key,除非你百分百确认列表是静态不动的。
4. 事件处理与表单绑定的实战细节
4.1 事件修饰符:让模板更清爽
Vue2为v-on提供了一批修饰符,用点号跟在事件名后面。它们的本质是把原本要在方法里写的事件操作,在模板层面直接声明出来,让代码更纯粹。下面几个是我在实际项目里用得比较多的。
.stop用来阻止事件冒泡。比如一个卡片内部有个关闭按钮,点击按钮不希望在监听click的父容器上触发事件,就可以写@click.stop="close"。没有它,你就要在方法里手动调event.stopPropagation()。
.prevent用来阻止浏览器默认行为。表单提交场景最典型,原生form提交会刷新页面,绑定了@submit.prevent="handleSubmit"之后,页面就不会刷新了。同样不用在方法里手动preventDefault()。
.once表示事件只触发一次。注意它是修饰符,而不是方法内部的逻辑判断。一个按钮只想允许点击一次,比如“提交申请”按钮防止重复提交,@click.once="submit"就能用。
.self是只有事件目标是当前元素本身时才触发回调。结合冒泡机制来理解:点击子元素不会触发父元素上的带有.self的处理函数。
还有个组合用法值得单独说明:@click.stop.prevent可以同时接多个修饰符,停止冒泡的同时阻止默认行为。这也是模板声明式写法的优势:看一眼就知道事件被处理成什么效果,不用翻方法体。
4.2 按键修饰符:键盘交互的简化
键盘相关的事件在原生JS里要读event.keyCode再判断,代码写起来又丑又啰嗦。Vue2直接提供了按键修饰符。
<input @keyup.enter="search"> <input @keyup.esc="reset">@keyup.enter表示回车键弹起时触发,@keyup.esc表示按Esc时触发。对代码而言,阅读成本极低。除了Vue预置的几个常见按键别名,自定义按键也别担心,Vue.config.keyCodes可以扩展:
Vue.config.keyCodes.f2 = 113; // 给F2键起个别名之后模板里就能写@keydown.f2="handleF2"。不过说句实话,自定义按键别名在日常业务里用得非常少,了解有这功能就行。
4.3 v-model修饰符:处理输入值边界
v-model虽然好用,但它天生有个特点:所有表单控件拿到的值默认都是字符串。比如你给一个数字输入框绑定了age,用户输入18,data里的age是字符串'18',而不是数字18。这在做年龄比较、数学运算时会造成隐式类型转换的坑。
解决办法是加.number修饰符:<input v-model.number="age">,Vue会在输入事件处理时尝试用parseFloat转换,如果转换成功就存储为数字类型。加了这个修饰符后,像age * 2这种运算才能得到符合预期的结果。
.trim修饰符用来过滤首尾空格,适用于用户名、密码、搜索关键词等场景。用户不小心在用户名后面打了个空格,如果不处理,后续接口返回的提示大概率是“用户名不存在”,排查起来还很难发现。<input v-model.trim="username">直接帮你消掉这个隐患。
.lazy修饰符改变触发时机的。默认v-model在input事件时同步更新,意味着用户每敲一个字符数据就变一次。加了.lazy后,改为在change事件时更新,即输入框失去焦点或敲回车时才把值同步到data。这个修饰符适合搜索框和表单提交这种“最终值”更重要的场景,可以减少无谓的中间态更新带来的重渲染开销。
4.4 表单场景里的复选框、单选框和下拉框
v-model应用在不同控件时,接收的值类型有讲究。
复选框用同一个data字段绑多个选项时,data字段应该是一个数组:
<input type="checkbox" value="篮球" v-model="hobbies"> 篮球 <input type="checkbox" value="足球" v-model="hobbies"> 足球 <input type="checkbox" value="羽毛球" v-model="hobbies"> 羽毛球data里声明hobbies: [],用户勾选时,Vue会自动把选中的value值推进这个数组。这个数组存的是value值,不是布尔。
单选框用同一个data字段绑多个选项时,data字段是字符串:
<input type="radio" value="男" v-model="gender"> 男 <input type="radio" value="女" v-model="gender"> 女gender的值就是被选中单选项的value。
下拉框的绑定值是option的value:
<select v-model="selectedCity"> <option value="北京">北京</option> <option value="上海">上海</option> </select>如果option上没有写value属性,那么选中的值是option的文本内容。这类细节建议多摆弄几遍,等高阶到表单验证库时,你会发现正确初始化和类型匹配是静态验证和动态提交能顺畅工作的前提。
4.5 方法里的this指向和防抖实践
在methods里定义函数时,有一个容易被忽略的规则:普通函数的this指向Vue实例,但如果用箭头函数定义methods方法,this就不再指向Vue实例了。这是ES6箭头函数自身的绑定规则导致的。所以:
methods: { // 正确写法:普通函数 add() { console.log(this.count); }, // 错误写法:this不指向Vue实例 add: () => { console.log(this.count); // 这里会报错 } }实际写代码时统一用简写方法定义(上面的add()),就不会踩这个坑。
再分享一个实战场:输入框防抖。直接给输入框绑定@input="handleInput",方法是直接请求接口,每次输入都发请求,性能不好。简单做法是用lodash.debounce包一层,或者自己写一个setTimeout包装:
methods: { handleInput(e) { clearTimeout(this.timer); this.timer = setTimeout(() => { // 发送搜索请求 this.searchKeyword = e.target.value; }, 300); } }用this.timer保存定时器句柄。因为methods里的普通函数this指向Vue实例,而箭头函数内部又能捕获外层函数作用域的this,所以定时器回调里依然能访问this.searchKeyword,这也是箭头函数在方法内部回调里频繁出现的正当理由。
5. 条件渲染与列表渲染:前端视图逻辑的两个主战场
5.1 v-if和v-show的实际选择策略
我在第三章简单提过两者的底层差异,这里补充一个具体的业务判断方法。先问自己:这个元素的显示状态在生命周期内会切换几次?如果多于十次,果断用v-show;如果少于十次、或者几乎不切换,用v-if更划算。
为什么频繁切换要用v-show?因为v-if每次切换都涉及DOM的创建和销毁,这个过程的性能开销比切换一个CSS样式大得多。反过来,v-show的代价是首次渲染时元素一样要渲染出来,哪怕初始状态是隐藏的,DOM节点依然存在。如果一个元素永远不会显示,但写在v-show里,它还是白白占用了DOM节点。某项目里有一个接口权限控制的高级筛选面板,绝大多数用户都看不见,原本用v-show,我改成v-if后页面初始渲染的DOM节点少了十几个,虽然是微优化,但方向是对的。
有个组合技巧:遇到“初始加载时需要请求数据、加载完才显示内容”的场景,可以用一个loading标志配合v-if,而不是在模板里写一堆v-show加延时。数据没回来时模板直接不渲染,减少不必要的中断操作。
5.2 key的作用:为什么不能用index打天下
v-for渲染列表时,:key是给每个节点打的唯一标签。Vue在更新列表时,会对比旧节点列表和新节点列表的key,确定哪些节点是新增的、哪些是删除的、哪些只是位置发生变化。有了稳定的key,Vue可以最大程度复用已有DOM节点,只做必要的移动和更新,性能好、状态保留也正确。
如果拿index当key,问题在列表顺序被改变时暴露得特别明显。举个真实案例:某个后台管理列表,每一行有一个输入框,输入了内容,然后点击“按评分排序”按钮,列表顺序变了,输入框里的内容却被带到了错误的行上。原因就是排序后列表每个位置的key还是0、1、2,Vue以为行节点没变,只是内容变了,于是复用了原来的DOM,输入框里的值还停留在原位置。但用户的输入状态是跟着DOM走的,就错位了。
正确的key应该是列表中元素的唯一编号,比如接口返回的id:
<li v-for="item in list" :key="item.id">{{ item.name }}</li>什么情况下可以用index当key呢?唯一允许的场景是列表完全静态、不会增删排序,或者你确定每个条目在生命周期内都不会发生变化。但是实际开发中,需求不断迭代,今天静态的列表明天就可能加个删除按钮,所以我的建议是:纯展示列表偶尔用index可以接受,但任何有交互的表单型列表,坚决用业务id。
5.3 v-for和v-if同时用在一个元素上的坑
Vue2官方文档明确指出,永远不要把v-for和v-if放在同一个元素上。原因在于Vue2中v-for的优先级比v-if高,也就是说每一次渲染,v-if都先跑在循环内部,没法提前中止循环。
实际后果是下面这种看似合理的代码:
<li v-for="item in list" v-if="item.isActive" :key="item.id">{{ item.name }}</li>它确实能实现过滤效果——只显示isActive为true的项——但性能非常差。它的执行逻辑是:遍历全部list,每遍历一个就判断一次是否渲染。如果list有10000项,哪怕只有1项要显示,Vue也要循环10000次。而且因为v-if在v-for内部,它无法访问到循环外层的任何变量,看起来很奇怪但确实是这样。
正确的做法是用计算属性先过滤,再把过滤后的数组交给v-for:
computed: { activeList() { return this.list.filter(item => item.isActive); } }模板里写:
<li v-for="item in activeList" :key="item.id">{{ item.name }}</li>这样既过滤得干干净净,循环次数也只在list真正变化时才重新执行。如果你是在做一个页面内的小范围过滤,计算属性这种方式还自带缓存,效率明显更高。
5.4 v-for遍历对象、范围数字和分组渲染
除了数组,v-for还可以遍历对象和数字。遍历对象时,第一个参数是值,第二个是键,第三个是索引:
<div v-for="(value, key, index) in userInfo" :key="key"> {{ index }}. {{ key }}: {{ value }} </div>这种写法适合做简单对象信息的展示面板,不用手工维护字段对应关系。
遍历数字范围也是一个少见但会遇到的用法:
<span v-for="n in 10" :key="n">{{ n }}</span>注意这个n从1开始,到10结束。如果你需要从0开始,就自己写n-1计算。这个用法在做分页条、星级评分这类组件时很实用。
还有分组成员渲染的场景。如果你拿到了扁平数组,想按部门分组展示,别在模板里做复杂逻辑,应该用计算属性返回一个分组后的二维数组,再两层v-for渲染。
6. 入门到进阶之间,别忘了亲手写几个小Demo
这篇文章从创建实例、响应式原理到底层指令逐条拆解,覆盖了Vue2最核心的知识骨架。说句实在话,指令背得再熟,不如亲手写一遍。我第一次接触Vue2时,在某个旧项目里用CDN方式维护过一个活动页,当时的任务是把一组静态数据改成按条件渲染、按关键词筛选。现在回头看,那个页面用到的指令不超过五个,但正因为亲手改过,我对computed、v-if和key的理解比读十遍文档都深刻。
有一个建议:你完全可以自己动手做一个极简任务清单页面,要求是输入框用v-model,添加按钮用@click,列表用v-for加业务id作key,完成状态用v-if或v-show切换,再来一个筛选下拉框验证计算属性。这几个功能做完,Vue2的基础指令体系你就真正打通了。
再提醒一次容易遭坑的细节:给列表用index作key的问题、v-html渲染用户内容的安全隐患、数组下标直接赋值导致不响应。这三件事我工作这些年至少帮别人排查过不下十次,每次的现场都在提醒我,Vue2的原理部分值得在入门阶段就扎扎实实看明白。如果某个指令用起来效果和你预期不同,别急着怀疑框架,回头按“模板语法 — 指令机制 — 响应式边界”这个顺序查一遍,绝大多数问题都能定位到。