我的第一个Vue应用

24 阅读5分钟

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 全局构造函数即可使用。

image-20260516165254477.png

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 或数组变异方法(pushpopsplice 等)。本例中 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 的响应式系统会执行以下步骤:

  1. 触发 setterproduct.stock = newStock 触发 setter,通知依赖管理器。
  2. 标记组件为“脏” :组件级的 watcher 被标记为需要重新渲染。
  3. 异步队列:Vue 将更新推入一个异步队列,多个数据变化会在同一事件循环中合并,避免重复渲染。
  4. 创建新的虚拟 DOM:Vue 的渲染函数会基于新的数据生成一棵新的虚拟 DOM 树(Virtual DOM)。
  5. Diff 比较:新 VNode 与旧 VNode 进行深度比较(diff 算法),找出最小差异。
  6. 更新真实 DOM:只对需要更新的真实 DOM 节点执行 patch 操作,而不是重绘整个页面。

虚拟 DOM 的好处:减少了真实 DOM 操作的次数,提升了性能;同时也允许 Vue 实现跨平台渲染(如 Weex)。

注意:在 Vue 2 中,由于使用了 Object.defineProperty,每次数据变化都会触发完整的组件级重新渲染。Vue 3 采用了 Proxy,性能更好且能够检测新增属性。

7. 组件化思想:从根实例到组件

虽然本例只包含一个根 Vue 实例,但 Vue 的核心思想是组件化。任何一个 .vue 文件或使用 Vue.component 注册的组件,都是一个可复用的 Vue 实例,拥有自己的 datacomputedmethodstemplate 等。

  • 组件注册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 如何高效更新视图。