Vue.js 是一款轻量级、渐进式的 JavaScript 框架,凭借简洁的 API 和强大的响应式系统,成为前端开发者的入门首选。本文将通过一个简单的“商品管理demo”,带你从零搭建一个 Vue 2 应用,并深入理解其核心机制:实例创建、挂载、配置选项、数据绑定以及虚拟 DOM 的重建过程。
1. 准备工作:引入 Vue.js
要使用 Vue,首先需要在 HTML 中引入 Vue 库。本例使用 CDN 方式(./vue@2.js 仅为示意,实际可用 cdn.jsdelivr.net/npm/vue@2.7…)。将该代码文本下载到项目文件中,在项目中引入后,Vue 全局构造函数即可使用。
2. 创建第一个 Vue 实例
每个 Vue 应用都始于一个根 Vue 实例,通过 new Vue({...}) 创建。实例的配置选项(Options)定义了应用的行为。
new Vue({
el: '#app', // 挂载目标
data: { ... }, // 响应式数据
computed: { ... }, // 计算属性
methods: { ... } // 方法
});
2.1 el 选项:挂载目标
el 指定实例要控制的 DOM 元素,可以是 CSS 选择器字符串(如 '#app')或 DOM 元素本身。Vue 会接管该元素及其内部的所有内容,之后该元素内的所有模板语法都会被编译。
注入过程:Vue 会创建一个编译后的渲染函数,并将数据“注入”到模板中,完成初始渲染。el 实际上调用了 vm.$mount(el) 方法,将实例挂载到指定节点上。
注意:一个 Vue 实例只能挂载到一个元素上,且该元素本身(及其内部)会完全由 Vue 管理。如果挂载元素已经存在内容,Vue 会覆盖它们(但可通过 template 选项保留原有结构)。
2.2 data 选项:响应式数据大本营
data 是一个对象,存放所有需要在模板中使用的变量。这些变量是“响应式”的——当它们的值改变时,视图会自动更新。
data: {
products: [
{ name: "iphone", stock: 10 },
{ name: "ipad", stock: 5 },
{ name: "macbook", stock: 3 }
]
}
- 响应式原理:Vue 在初始化时,遍历
data的所有属性,通过Object.defineProperty(Vue 2)将它们转为 getter/setter。当读取属性时,会收集依赖(哪些组件用到了这个属性);当修改属性时,会通知所有依赖进行更新。 - 注意:直接通过下标修改数组(如
vm.products[0] = newVal)或新增属性(如vm.newProp = 123)无法触发视图更新。但本例中我们使用splice删除、直接赋值对象属性(product.stock = ...)都是响应式的,因为 Vue 对已有对象属性的赋值进行了劫持。
3. 模板与指令:让数据活起来
Vue 模板是基于 HTML 的扩展语法,使用 {{ }} 插值和 v- 指令绑定数据和 DOM。
3.1 插值表达式
{{ item.name }} 将数据对象的属性直接渲染为文本。当数据改变时,插值内容会自动更新。
{{ item.stock ? item.stock : "缺货" }} 使用了三元运算符:如果库存大于 0 则显示数字,否则显示“缺货”。注意:当 stock 为 0 时,布尔值为 false,因此显示“缺货”。
3.2 v-for 循环
v-for="(item, i) in products" 遍历 products 数组,生成多个 <li> 元素。第二个参数 i 是索引。
- 当数组变化时(如
splice删除、push添加),Vue 会智能地复用和移动已有 DOM 节点(通过key优化,本例未加但建议添加)。 - Vue 2 中,
v-for的渲染效率较高,但直接通过索引修改数组不会触发更新,需使用Vue.set或数组变异方法(push、pop、splice等)。本例中splice恰好是变异方法。
3.3 v-on 事件绑定
@click="changeStock(item, item.stock - 1)" 是 v-on:click 的简写。当点击按钮时,调用 methods 中定义的 changeStock 方法,并传入参数。
事件处理函数可以接受原生事件对象 $event,也可以传参。注意:在模板中直接写 item.stock - 1 这样的表达式也可以,但为了逻辑清晰,推荐放在方法中。
4. 计算属性 computed
计算属性是基于依赖数据进行缓存的复杂计算结果。它本质上是一个 getter 函数,但结果会被缓存,只有依赖改变时才重新计算。
computed: {
total() {
return this.products.reduce((sum, product) => sum + product.stock, 0);
}
}
total计算属性依赖于products中每个product.stock。如果任一商品库存变化,total会重新计算并更新视图。- 与方法的区别:方法每次调用都会执行,而计算属性只有在依赖变化时才会重新求值,性能更优。
- 在模板中直接使用
{{ total }}即可,不需要加括号。
5. 方法 methods
方法通过 methods 定义,通常用于处理事件或执行非缓存的逻辑。
changeStock(product, newStock):修改商品库存。如果newStock小于 0,则设为 0。直接修改对象属性是响应式的。remove(index):通过splice删除数组对应项,会触发视图更新。add(product):push一个新商品对象到数组末尾。
注意:add 方法添加的是 { name: '', stock: 0 },这会导致一个空名称的商品。实际中你可能需要用户输入,这里仅演示添加功能。
6. 响应式更新与虚拟 DOM 重建
当数据变化时(如点击 + 按钮 changeStock(item, item.stock + 1)),Vue 的响应式系统会执行以下步骤:
- 触发 setter:
product.stock = newStock触发 setter,通知依赖管理器。 - 标记组件为“脏” :组件级的 watcher 被标记为需要重新渲染。
- 异步队列:Vue 将更新推入一个异步队列,多个数据变化会在同一事件循环中合并,避免重复渲染。
- 创建新的虚拟 DOM:Vue 的渲染函数会基于新的数据生成一棵新的虚拟 DOM 树(Virtual DOM)。
- Diff 比较:新 VNode 与旧 VNode 进行深度比较(diff 算法),找出最小差异。
- 更新真实 DOM:只对需要更新的真实 DOM 节点执行 patch 操作,而不是重绘整个页面。
虚拟 DOM 的好处:减少了真实 DOM 操作的次数,提升了性能;同时也允许 Vue 实现跨平台渲染(如 Weex)。
注意:在 Vue 2 中,由于使用了 Object.defineProperty,每次数据变化都会触发完整的组件级重新渲染。Vue 3 采用了 Proxy,性能更好且能够检测新增属性。
7. 组件化思想:从根实例到组件
虽然本例只包含一个根 Vue 实例,但 Vue 的核心思想是组件化。任何一个 .vue 文件或使用 Vue.component 注册的组件,都是一个可复用的 Vue 实例,拥有自己的 data、computed、methods、template 等。
- 组件注册:
Vue.component('product-item', { ... })。 - 组件通信:父组件通过 props 向子组件传递数据,子组件通过 $emit 触发事件向父组件通信。
- 插槽:允许父组件向子组件插入内容。
将本示例中的商品列表项提取为 <product-item> 组件,可以让代码更整洁、更易维护。
8. 其他常见配置选项
watch:监听数据变化并执行异步或开销较大的操作。created/mounted等生命周期钩子:在实例不同阶段执行代码。filters:文本格式化(Vue 3 已移除)。components:注册局部组件。
9. 总结与下一步
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id = "app">
<h1>商品管理系统</h1>
<ul>
<li v-for = "(item,i) in products">商品名称:{{ item.name }},库存:
<button @click = "changeStock(item, item.stock - 1)">-</button>
{{ item.stock ? item.stock : "缺货" }}
<button @click = "changeStock(item, item.stock + 1)">+</button>
<button @click = "remove(i)">删除</button>
</li>
<button @click = "add({ name: '', stock: 0 })">添加</button>
</ul>
<p>总库存:{{ total }}</p>
</div>
<script src = "./vue@2.js"></script>
<script>
// Vue 实例的 配置选项(Options)
// 创建一个Vue实例(Vue 的应用入口)
new Vue({
// el选项指定了Vue实例要挂载的DOM元素,这个元素的id是app
el:"#app",
// 数据:存放页面要用到的所有数据(响应式数据)
data:{
products:[
{ name: "iphone",stock:10},
{ name: "ipad",stock:5},
{ name: "macbook",stock:3}
],
},
// 计算属性(computed)
computed: {
total(){
// .reduce(...)方法用于对数组中的所有元素进行累加或累乘等操作,这里是对products数组中的每个product的stock属性进行累加,初始值为0。
// 格式:数组.reduce( (累加变量, 当前项) => { ... }, 初始值 )
return this.products.reduce((sum, product) => sum + product.stock, 0);
}
},
methods: {
// changeStock方法用于修改商品的库存数量,接受两个参数:product表示要修改库存的商品对象,newStock表示新的库存数量。
changeStock(product, newStock){
if(newStock < 0){
newStock = 0;
}
product.stock = newStock;//数据响应式变化
},
remove(index){
this.products.splice(index , 1)
},
add(product){
this.products.push(product);
}
},
});
</script>
</body>
</html>
通过这个简单的商品管理demo,我们亲手创建了第一个 Vue 实例,理解了 el 挂载、data 响应式、computed 缓存、methods 事件处理,并明白了虚拟 DOM 如何高效更新视图。