Skip to content
Vue.js 基本概念
从页面脚本到 Vue 实例
Vue 应用从创建一个应用实例开始。在 HTML 页面里引入 Vue 后,需要构造实例并挂载到页面上的某个 DOM 节点。这个过程在 Vue 2 和 Vue 3 中写法不同,但语义一致。
createApp(Vue 3)与 new Vue(Vue 2)
使用 Vue 3 的 CDN 版本时,createApp 挂载在全局 Vue 对象上。一个最小的应用如下:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">
</div>
<script>
const { createApp } = Vue;
const app = createApp({
data() {
return {
message: 'Hello Vue 3'
};
}
});
app.mount('#app');
</script>createApp 接收一个根组件对象作为参数,可以在该对象里声明 data、methods、生命周期钩子等选项。它返回一个应用实例,实例上的 mount 方法负责把应用渲染到指定的容器元素里。
Vue 2 使用 new Vue() 完成同样的工作:
html
<!-- Vue 2 CDN 示例 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<div id="app">
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue 2'
}
});
</script>两种写法的核心差异:
- Vue 3 的
data选项必须是一个返回对象的函数;Vue 2 在根实例中允许直接传入对象(组件内同样要求函数)。 - Vue 3 通过
createApp创建应用实例后再挂载;Vue 2 直接使用new Vue并在选项中声明el,或调用$mount。
后续示例均以 Vue 3 为准。
mount 与 el:将实例挂载到 DOM
mount 方法接受一个 CSS 选择器字符串,Vue 会在文档中寻找该选择器匹配的第一个元素作为挂载容器。执行挂载后,容器内的 HTML 被替换为 Vue 渲染出的 DOM 结构。
几个限制:
- 不能挂载到
<html>或<body>上,Vue 会直接报出警告,因为这类元素在运行时被替换会导致不可预期的行为。 mount返回根组件实例,如果后续要通过外部代码主动修改响应式数据,需要用到这个实例。
js
const vm = app.mount('#app');
// vm.message = 'changed'; 同样能触发视图更新Vue 2 的 el 选项和 $mount 方法完成的是同一件事,el 可以看作自动调用 $mount 的简写。
data 与 methods:选项式 API 的基本结构
选项式 API 通过 data、methods、computed、生命周期钩子等选项定义组件行为。data 返回的对象会被 Vue 转换为响应式数据,一旦某个属性的值发生变化,依赖该属性的模板就会被重新渲染。
methods 是挂载在实例上的普通函数,可以在模板中通过事件处理器调用,或者在其他方法中通过 this 访问。Vue 将这些方法里的 this 绑定到当前组件实例,因此可以直接读写 this.message 之类的数据。
html
<div id="app">
<button @click="increment">+1</button>
</div>
<script>
const { createApp } = Vue;
const app = createApp({
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
});
app.mount('#app');
</script>increment 通过 this.count++ 修改数据,视图自动更新,不需要手动操作任何 DOM 节点。
声明式渲染与响应式初体验
下面这个例子可以观察“响应式更新”的行为:
html
<div id="app">
</div>
<script>
const { createApp } = Vue;
const app = createApp({
data() {
return {
message: 'initial message'
};
}
});
const vm = app.mount('#app');
// 3 秒后修改数据
setTimeout(() => {
vm.message = 'updated message';
}, 3000);
</script>页面初始渲染 “initial message”,计时 3 秒后文字自动变成 “updated message”。这个过程中没有 document.querySelector 或 innerHTML 之类的手动操作,编写代码时只需要声明页面最终应该呈现什么,Vue 负责在数据变化时找到对应 DOM 并更新。
vm.message = 'updated message' 这条赋值语句触发了响应式系统。Vue 3 内部使用 Proxy 代理数据对象,数据被读取时收集依赖,被写入时通知依赖进行更新。整个链路是:模板依赖 message → 数据变更触发通知 → 重新渲染模板节点。相比命令式的 DOM 操作,这种模式在数据分散、交互复杂的页面中能降低维护成本。
模板语法:插值表达式
在模板中使用双花括号 {{ }} 可以直接输出数据中某个属性的值,这就是插值表达式。花括号里可以写入单个表达式,但不能出现语句。
html
<p>{{ user.name }}</p>
<p>{{ count + 1 }}</p>
<p>{{ ok ? 'YES' : 'NO' }}</p>表达式在所属组件的上下文中求值,可以直接访问 data、methods 等选项上暴露的属性。
当数据变化时,插值位置会自动更新,但只更新文本内容,不会解析 HTML 字符串。如果需要在某些位置输出原始 HTML,需要使用 v-html 指令,但这会绕过 Vue 的模板编译,容易引入 XSS 风险,只有确信内容安全时才使用。
指令基础:v-bind 与 v-on
指令是作用在模板标签上的特殊属性,以 v- 开头。它们会在渲染时为 DOM 节点施加特定行为。v-bind 和 v-on 是最基础的两个,分别负责属性绑定和事件监听。
v-bind 将某个属性值绑定到 JavaScript 表达式:
html
<button v-bind:title="tooltip">悬停此处</button>
<a v-bind:href="url">链接</a>
<!-- 简写 -->
<img :src="imageSrc" />这里 title、href、src 不再是静态字符串,而是从 data 中获取的动态值。
v-on 用于监听 DOM 事件:
html
<button v-on:click="handleClick">点击</button>
<input v-on:input="handleInput" />
<!-- 简写 -->
<button @click="handleClick">点击</button>handleClick 必须定义在 methods 里,事件触发时 Vue 会调用对应方法,原生事件对象会作为第一个参数传入。
一个同时使用 v-bind 和 v-on 的输入框绑定示例:
html
<div id="app">
<input :value="text" @input="updateText" />
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
text: 'hello'
};
},
methods: {
updateText(event) {
this.text = event.target.value;
}
}
}).mount('#app');
</script>input 的 value 属性通过 v-bind 绑定到 text,输入触发 input 事件时,updateText 用新值更新 text,于是 <p> 标签里的插值同步刷新。这个模式是 v-model 去掉语法糖之后的形态。
条件与列表渲染:v-if 与 v-for
页面结构经常需要根据数据动态变化,两个常用指令是 v-if(条件控制)和 v-for(列表生成)。
v-if 接受一个表达式,表达式为真时渲染对应元素:
html
<p v-if="isLoggedIn">欢迎回来</p>
<p v-else>请登录</p>v-else 必须紧跟在带 v-if 的元素之后,中间不能出现其他元素。v-if 是“真正”的条件渲染,会销毁或重建 DOM 节点及内部组件。
v-for 用来遍历数组或对象,生成一组元素:
html
<ul>
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</ul>v-for 必须搭配一个唯一的 :key 属性,帮助 Vue 识别每个节点,避免在数据顺序变化时出现渲染错乱或性能退化。这里 items 定义在 data 中,每一项包含 id 和 text。
两个指令可以组合使用,但不推荐直接在同一个元素上同时使用 v-if 和 v-for,因为 v-for 优先级更高,可能产生不必要的性能开销。正确的做法是在外层套一个 <template> 或先用计算属性过滤数据。
下面是一个同时展示条件与列表的完整例子:
html
<div id="app">
<button @click="toggleList">
{{ showList ? '隐藏' : '显示' }}列表
</button>
<ul v-if="showList">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</ul>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
showList: true,
items: [
{ id: 1, text: 'Vue 实例化' },
{ id: 2, text: '模板语法' },
{ id: 3, text: '指令' }
]
};
},
methods: {
toggleList() {
this.showList = !this.showList;
}
}
}).mount('#app');
</script>点击按钮会切换 showList,列表项随之出现或消失,每次切换时 v-if 都会重新创建或销毁整个 <ul> 和内部的 v-for 节点。
生命周期:实例的诞生与挂载
每个 Vue 实例从创建到销毁会经历一系列阶段,期间会依次调用对应的钩子函数。这些钩子可以在特定时刻执行自定义逻辑,比如发起网络请求、读写 DOM、清理定时器等。
选项式 API 下的常用钩子包括:
beforeCreate:实例初始化后、数据观测和事件配置之前调用。created:实例创建完成,data、methods等已可用,但尚未挂载 DOM。beforeMount:模板编译完成,还未渲染到容器中。mounted:实例被挂载到 DOM 上,this.$el已指向实际渲染出来的元素。beforeUpdate:数据变化后、重新渲染之前调用。updated:重新渲染并更新 DOM 后调用。beforeUnmount(Vue 3)/beforeDestroy(Vue 2):实例卸载前。unmounted/destroyed:实例完全卸载后,所有事件监听和子实例被移除。
created 与 mounted:关键钩子与触发时机
对于初期的数据初始化、接口请求等场景,created 和 mounted 是用得最多的两个钩子。
created执行时,组件实例已经创建,可以安全地访问this.message、调用this.fetchData()等方法,但无法对 DOM 做任何操作,因为组件还没挂载。mounted执行时,组件的根 DOM 元素已经插入到页面中,所有同步的子组件也都已经挂载完毕。此时可以获取元素尺寸、初始化第三方 DOM 库,或启动相关副作用。
一个简单的观察示例:
html
<div id="app">
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
message: 'before mount'
};
},
created() {
console.log('created:', this.message); // 输出 'before mount'
// this.$el 为 undefined,还拿不到 DOM
},
mounted() {
console.log('mounted:', this.$el); // 输出渲染后的 DOM 节点
// 此时可以安全读写 this.$refs.textNode 等
}
}).mount('#app');
</script>控制台会先看到 created: 日志,然后是 mounted: 日志,证明这两个阶段的前后关系。
综合示例:计数器与表单输入
下面把前面出现过的概念合成一个页面:同时展示计数器、输入框同步、列表渲染与条件控制。所有交互都基于选项式 API 的 data 和 methods。
html
<div id="app">
<!-- 计数器 -->
<button @click="count++">+1</button>
<button @click="count = 0">归零</button>
<!-- 输入同步 -->
<p>
输入内容:
<input :value="text" @input="text = $event.target.value" />
</p>
<!-- 列表与条件 -->
<button @click="showList = !showList">切换列表</button>
<ul v-if="showList">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</ul>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
count: 0,
text: 'hello',
showList: true,
items: [
{ id: 1, text: '创建实例' },
{ id: 2, text: '声明式渲染' },
{ id: 3, text: '指令与生命周期' }
]
};
}
}).mount('#app');
</script>在这个页面里:
- 点击 “+1” 按钮直接通过内联表达式
count++修改数据; - 输入框用
:value和@input完成双向绑定,$event.target.value是原生输入框的值; - 列表渲染使用
v-for并指定了:key; - 条件渲染通过
v-if控制整个列表的出现和消失。
这里没有引入构建工具,也没有用到 .vue 文件,所有代码放在一个 HTML 文件中就能运行。这个模式适合入门阶段理解 Vue 的核心工作机制。
注意点
data必须是函数:在 Vue 3 中,createApp传入的选项里data必须是一个返回对象的函数。如果直接传对象,Vue 会在控制台发出警告并阻止创建。- 不要用内部属性修改数据:
app._instance.data.message这类路径属于内部实现,任何教程或文档中都不应作为操作响应式数据的方式。正确做法是使用根组件实例vm.message = ...或通过methods内的方法修改。 - 挂载目标不能是
<html>或<body>:挂载后容器内容会被替换,如果挂载到整个页面会直接破坏文档结构,Vue 会拒绝执行。 v-for与:key:凡是使用v-for,都应当为每一项提供一个唯一且稳定的key。缺少key时 Vue 默认采用“就地更新”策略,在列表项顺序发生变化时可能产生遗留状态的 bug。- 生命周期钩子注意事项:
updated钩子在每次数据变化导致 DOM 更新后执行,在其内部再次修改响应式数据容易引发无限循环。在服务端渲染(SSR)场景下,mounted、beforeMount、updated等与 DOM 相关的钩子都不会被调用,初始数据的获取逻辑应当前移到created或其他通用阶段。 - 版本差异:Vue 2 中使用
new Vue({ el: '#app' })的写法在 Vue 3 中不再推荐,统一使用createApp。 - 表达式的限制:模板插值和指令值里只能写单个表达式,不能出现
if、for等语句。复杂逻辑应提取到methods或计算属性中。
