Vue.js 2 核心概念解析:入门介绍
Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。与 Angular 和 React 等其他框架相比,Vue.js 的设计理念是“渐进式”——这意味着你可以逐步地将它集成到你的项目中。你可以只使用它的一部分来增强现有的 HTML 结构,也可以用它来构建一个完整的、复杂的单页应用(SPA)。
Vue.js 2 版本是其发展历程中的一个重要里程碑,它凭借其简洁的 API、卓越的性能和完善的文档,赢得了全球开发者的喜爱,并在许多企业级项目中得到广泛应用。尽管 Vue 3 已经发布,但 Vue 2 仍然在大量现有项目中活跃,理解其核心概念对于维护和开发这些项目至关重要,也是理解 Vue 3 基础的坚实一步。
本文将深入浅出地介绍 Vue.js 2 的核心概念,帮助初学者快速入门,并为进一步学习打下坚实的基础。我们将从最基础的 Vue 实例开始,逐步探索模板语法、数据绑定、指令、事件处理、计算属性、侦听器、组件系统以及生命周期钩子等关键知识点。
1. 初识 Vue.js:一个简单的开始
Vue.js 的一个主要优势是它的易用性。你可以通过一个简单的 <script>
标签将其引入到你的 HTML 页面中,然后立即开始使用。
“`html
{{ message }}
“`
将上面的代码保存为一个 HTML 文件并在浏览器中打开,你会看到一个标题显示着“Hello, Vue 2!”。这段简单的代码展示了 Vue 的几个核心概念:
- Vue 库的引入: 通过
<script src="...">
标签引入 Vue 库。 - 挂载元素:
<div id="app">
是 Vue 应用将被挂载的 DOM 元素。 - 创建 Vue 实例:
new Vue({...})
创建了一个 Vue 应用的根实例。 el
选项: 指定了 Vue 实例要挂载的 DOM 元素的选择器(#app
)。data
选项: 包含了应用的状态数据。这里的message
是一个数据属性。- 模板语法 (
{{ }}
): 在 HTML 中使用双大括号 ({{ }}
) 来显示 Vue 实例中的数据。当message
数据发生变化时,页面上的内容会自动更新,这就是 Vue 的响应式特性。
这是 Vue 应用的基石。接下来,我们将更详细地分解这些概念。
2. Vue 实例 (The Vue Instance)
每个 Vue 应用都是通过创建一个新的 Vue 实例来启动的:
javascript
var vm = new Vue({
// 选项对象
})
这里的 vm
通常被称为 ViewModel,代表着 Vue 实例。Vue 实例选项对象包含了一系列配置选项,用来定义 Vue 应用的行为和数据,其中最基础的是 el
和 data
:
-
el
(Element):- 类型:
string | Element
- 描述: 提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。Vue 实例将完全控制这个 DOM 元素及其子元素。这个元素通常是一个容器
<div>
。在上面例子中,el: '#app'
告诉 Vue 将实例挂载到 ID 为app
的元素上。
- 类型:
-
data
:- 类型:
Object | Function
(通常在组件中使用 Function) - 描述: Vue 实例的数据对象。Vue 会将
data
对象的所有属性代理到 Vue 实例上。这意味着你可以通过vm.message
来访问data.message
。更重要的是,这些属性是响应式的。当data
对象的属性值发生变化时,所有使用了这些属性的模板部分都会自动更新。
“`javascript
var vm = new Vue({
el: ‘#app’,
data: {
firstName: ‘张’,
lastName: ‘三’
}
})// 可以通过 vm.firstName 和 vm.lastName 访问数据
console.log(vm.firstName); // 输出: 张// 改变数据会触发视图更新(如果模板使用了这些数据)
vm.firstName = ‘李’;
“` - 类型:
除了 el
和 data
,Vue 实例还有很多其他重要的选项,比如 methods
、computed
、watch
、components
、template
等等,我们将在后续章节中逐步介绍。
3. 模板语法 (Template Syntax)
Vue 使用一种基于 HTML 的模板语法,允许你声明式地将 DOM 绑定至底层 Vue 实例的数据。所有的 Vue 模板都是合法的 HTML,可以被符合规范的浏览器及 HTML 解析器解析。
3.1 插值 (Interpolations)
最基本的数据绑定形式是使用“Mustache”语法(双大括号):
html
<span>Message: {{ message }}</span>
双大括号标签会将 data
属性 message
的值作为普通文本插入到它们所在的位置。如果 message
属性发生变化,它也会同步更新。
除了文本,双大括号也可以用来插入 JavaScript 表达式:
“`html
{{ number + 1 }}
{{ message.split(”).reverse().join(”) }}
“`
但是,表达式必须是单个表达式,不能包含控制流语句(如 if
、for
)或声明语句(如 var
、const
)。
3.2 指令 (Directives)
指令是带有 v-
前缀的特殊特性。它们的作用是当表达式的值改变时,将一些行为应用到 DOM 上。
我们已经看到一个简单的例子:
“`html
现在你看到我了
“`
这里的 v-if
就是一个指令。它根据表达式 seen
的值的真假来决定是否渲染这个 <p>
元素。
一些指令可以接收一个“参数”,在指令名称之后以冒号表示:
html
<a v-bind:href="url">...</a>
这里 href
是 v-bind
指令的参数,它指示 Vue 将元素的 href
属性与 Vue 实例的 url
数据绑定。
另一个常见的指令是 v-on
,用于监听 DOM 事件:
html
<button v-on:click="doSomething">按钮</button>
这里 click
是 v-on
指令的参数,它指示 Vue 监听按钮的点击事件,并在事件发生时执行 doSomething
方法。
为了方便,Vue 为 v-bind
和 v-on
这两个最常用的指令提供了缩写:
v-bind:
缩写为:
html
<a :href="url">...</a>v-on:
缩写为@
html
<button @click="doSomething">按钮</button>
使用缩写可以使模板更简洁。在后续例子中,我们将优先使用缩写形式。
4. 数据绑定 (Data Binding)
数据绑定是 Vue 核心功能之一,它使得数据和 DOM 保持同步。主要通过以下方式实现:
- 文本插值 (
{{ }}
): 将数据显示为文本。 - 属性绑定 (
v-bind
或:
): 将数据绑定到 HTML 元素的属性上。
“`html
“`
在上面的例子中:
* :href
将链接的 href
属性动态绑定到 linkUrl
数据。
* :src
和 :alt
分别将图片路径和 alt 文本绑定到对应数据。
* :class
绑定可以是一个对象,对象的键是 class 名称,值是布尔值,决定该 class 是否存在。
* :style
绑定可以是一个对象,对象的键是 CSS 属性名(支持驼峰命名或 kebab-case),值是对应的样式值。
5. 常用指令详解
Vue 提供了许多内置指令来帮助你操作 DOM。除了前面提到的 v-if
、v-bind
、v-on
,以下是一些非常常用的指令:
5.1 条件渲染 (Conditional Rendering)
-
v-if
: 根据表达式的值,决定是否渲染元素。
html
<p v-if="score > 60">及格了</p>
<p v-else>继续努力</p> <!-- 与 v-if 或 v-else-if 配对使用 -->
Vue 也提供了v-else-if
指令来实现“else if”块。v-if
是“惰性”的,如果条件为假,元素及其子元素不会被渲染。 -
v-show
: 根据表达式的值,决定元素的显示或隐藏(通过 CSS 的display
属性)。
html
<p v-show="isLoading">正在加载...</p>
v-show
的元素会始终被渲染并保留在 DOM 中,只是简单地切换其 CSSdisplay
属性。如果需要频繁切换元素的显示状态,v-show
通常比v-if
性能更好。
5.2 列表渲染 (List Rendering)
-
v-for
: 基于源数据多次渲染元素或模板块。
“`html- {{ item.text }}
- {{ index }} – {{ key }}: {{ value }}
{{ n }}
``
v-for的语法是
item in items或
(item, index) in items。对于对象,语法是
(value, key) in object或
(value, key, index) in object`。注意
key
的使用: 当 Vue 正在更新使用v-for
渲染的元素列表时,它默认使用“就地更新”策略。这意味着如果数据项的顺序改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是修改现有元素。这效率很高,但可能会有问题,特别是当列表渲染包含状态的子组件或临时 DOM 状态(如表单输入值)时。为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每个项提供一个唯一的key
属性。key
通常是数据项的唯一 ID。使用v-for
时强烈推荐总是加上key
。
5.3 双向绑定 (Two-way Binding)
-
v-model
: 在表单输入框或组件上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素:<input type="text">
和<textarea>
元素使用value
属性和input
事件;<input type="checkbox">
和<input type="radio">
使用checked
属性和change
事件;<select>
使用value
属性和change
事件。
“`html输入的内容是: {{ inputText }}
选中的值是: {{ selected }}
``
v-model实际上是
:value和
@input的语法糖。例如,在文本输入框上,
v-model=”inputText”等价于
:value=”inputText” @input=”inputText = $event.target.value”`。修饰符:
v-model
还有一些有用的修饰符:
*.lazy
: 将输入事件改为change
事件,从而实现失去焦点或回车时才更新数据。
*.number
: 将用户的输入值自动转为数字类型。
*.trim
: 自动过滤用户输入的首尾空白字符。html
<input v-model.lazy="message">
<input v-model.number="age" type="number">
<input v-model.trim="text">
6. 事件处理 (Event Handling)
使用 v-on
指令(或其缩写 @
)可以监听 DOM 事件,并在触发时运行 JavaScript 代码。
“`html
计数器: {{ counter }}
“`
6.1 方法事件处理器
在上面的例子中,我们看到了两种处理事件的方式:
1. 直接在 @click
中写 JavaScript 表达式 (counter++
)。这适用于简单的表达式。
2. 绑定到一个方法名 (greet
或 warn
)。这是更常见的用法,尤其是当逻辑复杂时,将逻辑放在 methods
选项中定义的方法里更清晰。
在方法事件处理器中,你可以通过 $event
访问原生的 DOM 事件对象。
6.2 事件修饰符 (Event Modifiers)
Vue 提供了事件修饰符来处理一些常见的事件细节,而无需在方法中手动处理。修饰符通过在指令后加 .modiferName
的方式使用:
.stop
: 阻止事件继续传播 (调用event.stopPropagation()
)。.prevent
: 阻止元素的默认行为 (调用event.preventDefault()
)。.capture
: 使用捕获模式添加事件监听器。.self
: 只当事件是从侦听器绑定的元素本身触发时才触发回调。.once
: 只触发一次回调。.passive
: 提高移动端滚动的性能。
“`html
“`
7. 计算属性 (Computed Properties)
在模板中直接使用复杂的表达式会使模板变得臃肿难以维护。例如:
html
{{ message.split('').reverse().join('') }}
如果这个表达式在多个地方使用,或者逻辑更复杂,维护起来就会很麻烦。计算属性正是为了解决这个问题而生。
计算属性是基于它们的响应式依赖进行缓存的。只有当其依赖发生改变时才会重新求值。
“`html
原始字符串: {{ rawString }}
反转字符串 (使用方法): {{ reverseStringMethod() }}
反转字符串 (使用计算属性): {{ reverseStringComputed }}
“`
在上面的例子中,reverseStringComputed
是一个计算属性。它的值依赖于 rawString
。当你修改 rawString
时,reverseStringComputed
会重新计算并更新视图。而 reverseStringMethod
是一个普通方法。虽然它们实现了同样的功能,但使用方式和行为有所不同:
- 使用方式: 计算属性像数据属性一样使用 (
{{ reverseStringComputed }}
),而方法需要调用 ({{ reverseStringMethod() }}
)。 - 缓存: 计算属性会缓存其结果。只要其依赖(
rawString
)没有改变,即使多次访问reverseStringComputed
,它也不会重新执行 getter 函数。而方法每次被调用时都会重新执行。在上面的例子中,如果你多次访问reverseStringMethod()
,你会看到多次“方法执行”日志,而只有当rawString
改变时,你才会看到“计算属性求值”的日志(第一次渲染时也会执行)。 - 适用场景: 计算属性适用于那些依赖于其他数据并需要缓存结果的场景。方法适用于那些需要响应事件或执行副作用(如修改数据、发出请求等)的场景。
计算属性默认只有 getter,但你也可以提供 setter:
javascript
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName;
},
// setter
set: function (newValue) {
var names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
}
}
现在当你运行 vmComputed.fullName = '李 四'
时,setter 会被调用,vmComputed.firstName
和 vmComputed.lastName
会相应更新。
8. 侦听器 (Watchers)
虽然计算属性在大多数需要响应数据变化的场景下是首选,但有时你需要执行一些“副作用”,例如异步操作或开销较大的操作,以响应数据的变化。这时,侦听器 (Watchers) 就派上用场了。
你可以使用 watch
选项来侦听 Vue 实例上的数据变化,并在数据变化时执行自定义的逻辑。
“`html
{{ answer }}
“`
在上面的例子中,我们侦听 question
数据属性的变化。当 question
改变时,watch handler 函数会被调用,从而更新 answer
的状态并模拟一个异步请求。
computed vs watch:
- Computed: 适用于一个或多个数据属性的组合计算,并且需要缓存结果。它是同步的,用于生成新的数据属性。
- Watch: 适用于在数据变化时执行副作用,如异步操作、昂贵的计算、DOM 操作等。它是异步或同步的,主要用于观察数据的变化并执行其他任务。
简单来说,如果你需要根据现有数据生成一个新数据并显示或使用,用 Computed;如果你需要在数据变化时执行某个操作(比如网络请求、日志记录等),用 Watch。
9. 组件系统 (The Component System)
组件是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在大型应用中,组件系统是必不可少的。
组件是拥有预定义选项的 Vue 实例。一个应用可以被组织成一个嵌套的组件树:
根实例 (Root)
├── Header
├── Sidebar
└── Main
├── PostList
│ ├── PostItem
│ └── PostItem
└── Paginator
9.1 定义和注册组件
你可以使用 Vue.component()
方法全局注册一个组件:
javascript
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">你点击了我 {{ count }} 次</button>'
})
这里的 data
必须是一个函数,这样每个组件实例都会有自己独立的数据副本,而不是共享同一个数据对象。template
定义了组件的 HTML 结构。
全局注册的组件可以在任何 Vue 实例或组件的模板中使用:
“`html
“`
每个 <button-counter>
都是一个独立的组件实例,拥有自己的 count
数据。点击一个按钮不会影响其他按钮的计数。
除了全局注册,你也可以局部注册组件(通常在组件内部的 components
选项中注册)。这使得组件只在你需要的地方可用,避免了全局命名冲突,并且通常是更推荐的做法,尤其是在使用构建工具和单文件组件时。
9.2 通过 Props 向子组件传递数据
组件通常需要父子组件之间的数据传递。父组件可以使用 props 向子组件传递数据。子组件需要在 props
选项中声明它接受的 props:
javascript
Vue.component('blog-post', {
props: ['title'], // 声明接受一个名为 title 的 prop
template: '<h3>{{ title }}</h3>'
})
然后,在父组件的模板中,通过 v-bind
将数据传递给子组件:
“`html
“`
Props 是单向数据流:父组件的属性变化会传递到子组件,但子组件不能直接修改父组件传递下来的 prop 值。如果子组件需要修改数据,应该通过事件通知父组件。
9.3 通过事件向父组件通信
子组件可以通过触发事件来与父组件进行通信。子组件可以使用 $emit
方法触发一个自定义事件:
javascript
Vue.component('custom-button', {
template: '<button @click="handleClick">点击我</button>',
methods: {
handleClick: function() {
// 触发一个名为 'button-clicked' 的自定义事件
this.$emit('button-clicked');
}
}
});
父组件则可以使用 v-on
(或 @
)监听这个自定义事件:
“`html
总点击次数: {{ totalClicks }}
“`
在上面的例子中,每当一个 custom-button
被点击时,它会触发 button-clicked
事件。父组件监听这个事件,并调用 incrementTotal
方法来更新 totalClicks
数据。
Prop down, event up (单向数据流和事件通信) 是 Vue 组件通信的基本原则。
10. 生命周期钩子 (Lifecycle Hooks)
每个 Vue 实例在创建时都经历一系列初始化步骤。例如,它需要设置数据侦听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM。同时,它也会运行一些叫做生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
最常用的生命周期钩子包括:
beforeCreate
: 实例刚在内存中被创建出来,此时,还没有初始化 data 和 event。created
: 实例已经创建完成,data
和methods
已经初始化,但 DOM 还没有生成。可以在这里进行数据初始化、异步请求等。beforeMount
: 在挂载之前调用:相关的render
函数首次被调用。模板已经编译好,但尚未挂载到页面上。mounted
: 实例已经挂载到 DOM 上,此时可以通过this.$el
访问到真实的 DOM 元素。可以在这里进行 DOM 操作、集成第三方库等。beforeUpdate
: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在这里访问更新前的 DOM。updated
: 虚拟 DOM 重新渲染和打补丁之后调用。组件的 DOM 已经更新。可以在这里进行 DOM 操作,但要注意避免死循环。beforeDestroy
: 实例销毁之前调用。此时实例仍然完全可用。可以在这里进行清理工作,如清除定时器、解绑事件监听器等。destroyed
: 实例销毁之后调用。Vue 实例的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
这是一个简单的示例,展示了 created
和 mounted
钩子:
“`html
{{ message }}
“`
理解生命周期钩子对于在正确的时间执行逻辑(例如数据加载、DOM 操作、清理资源)至关重要。
11. 响应式原理概述 (Reactivity Overview)
Vue 最引人注目的特性之一是其非侵入性的响应式系统。当 Vue 实例被创建时,它会遍历 data
对象的所有属性,并使用 Object.defineProperty
(在 Vue 2 中)将它们转换为 getter/setter。
当这些属性被访问时,getter 会被调用,Vue 会“记住”哪个组件或指令正在使用这个数据。这被称为“依赖收集”。当属性被设置时,setter 会被调用,Vue 会通知所有“记住”的依赖,触发视图的更新。
“`javascript
var obj = {};
var value = ‘hello’;
Object.defineProperty(obj, ‘message’, {
get: function() {
console.log(‘getter 调用’);
return value;
},
set: function(newValue) {
console.log(‘setter 调用:’, newValue);
value = newValue;
// 在 Vue 内部,这里会通知依赖进行更新
}
});
console.log(obj.message); // 输出 “getter 调用” 和 “hello”
obj.message = ‘world’; // 输出 “setter 调用: world”
console.log(obj.message); // 输出 “getter 调用” 和 “world”
“`
这就是 Vue 响应式系统的基本工作原理。它使得你只需要修改数据,Vue 会自动处理视图的更新。
注意: 由于 Object.defineProperty
的限制,Vue 2 在侦测数组和对象的变更时有一些注意事项,例如直接通过索引修改数组项 (arr[index] = newValue
) 或直接给对象添加新属性 (obj.newProp = value
) 可能不是响应式的。Vue 提供了一些变通方法,如 Vue.set(object, propertyName, value)
、Vue.delete(object, propertyName)
或使用数组变异方法(push
, pop
, shift
, unshift
, splice
, sort
, reverse
)。理解这一点在开发中非常重要。
12. 总结与下一步
恭喜你!你已经了解了 Vue.js 2 的大部分核心概念:
- 如何创建 Vue 实例并管理数据 (
el
,data
)。 - 如何使用模板语法绑定数据和表达式 (
{{ }}
,v-bind
)。 - 如何使用常用指令控制 DOM (
v-if
,v-for
,v-on
,v-model
)。 - 如何使用计算属性处理复杂逻辑和缓存 (
computed
)。 - 如何使用侦听器响应数据变化并执行副作用 (
watch
)。 - 如何使用组件构建可复用的 UI 模块 (
Vue.component
,props
,$emit
)。 - 如何利用生命周期钩子在特定阶段执行代码 (
created
,mounted
, etc.)。 - 对 Vue 的响应式原理有了初步了解。
这只是 Vue.js 丰富世界的冰山一角。在掌握了这些基础之后,你可以继续深入学习:
- 单文件组件 (Single-File Components – SFCs): 使用
.vue
文件将模板、脚本和样式封装在一起,这是 Vue 开发中最常见的组织方式。 - Vue CLI: 官方提供的命令行工具,用于快速搭建现代化的 Vue 项目。
- Vue Router: 官方路由管理器,用于构建单页应用。
- Vuex: 官方状态管理模式,用于管理大型应用中的共享状态。
- Mixins, Custom Directives, Plugins, Render Functions… 更多高级特性。
Vue.js 凭借其易学易用的特性和强大的功能,是现代前端开发中一个优秀的框架选择。希望本文能为你打开 Vue 世界的大门,并激发你进一步探索和学习的兴趣!祝你 Vue 之旅愉快!