主题
Vue介绍与Vue的特点(Lesson1 2/4)
本页对应 Lesson01,共 6 段视频,约 1 小时 14 分。
学习目标
- 能够创建两个独立 Vue 实例并分别挂载到不同 id 的 DOM 元素,验证数据互不影响;能说明同一 Vue 实例挂载到多个匹配元素时仅第一个元素生效。
- 能够在模板中使用双大括号插值渲染字符串、数字、布尔值、数组、对象、方法返回值及三元表达式,并正确说明
undefined、null、布尔值的渲染表现。 - 能够使用
v-text与v-html完成文本和 HTML 内容渲染,指出二者在 HTML 标签解析上的差异,并正确使用属性值引用实例数据或输出字面量字符串。 - 能够使用
v-if、v-else-if、v-else实现多条件渲染,说明条件指令必须连续相邻使用的要求,并验证条件不成立时元素不渲染。 - 能够使用
template包裹多个元素配合v-if/v-else进行条件渲染,说明template本身不渲染且不能与v-html/v-text结合使用。 - 能够使用
v-show控制元素显示隐藏,比较v-show与v-if在 DOM 渲染和display样式控制上的差异,并正确判断空字符串、0、null、undefined、非空字符串、数字1作为条件值时的显示结果。
知识点
直接引入:先把 Vue 跑起来
为什么需要:学习阶段最怕环境没配好就先被工程化工具拦住。直接引入适合先验证“Vue 能不能用”,也方便对照官方文档理解最基础的引入方式。
怎么想:先拿到 vue.js 文件,再用 <script> 引入页面。转写稿里也是先引入 Vue 的 JS,再写 new Vue 做挂载验证。注意转写稿中的“will”“vi”“we”都是口误或同音转写,实际指 Vue;“EL”应写作 el,“井号 root”就是选择器 #root。
怎么做:按笔记给出的下载地址进入官方安装页,选择直接下载引入。
shell
# 下载地址
https://v2.cn.vuejs.org/v2/guide/installation.html下载后在 HTML 中用 script 标签引入本地文件,再写挂载代码验证。转写稿中的验证思路是:准备一个容器,例如 id="root",然后写 new Vue({ el: '#root', data: { ... } }),页面中通过双花括号把数据显示出来。
易错点:el 的值是选择器字符串,#root 的 # 不能丢;不要写成 EL;同一个 Vue 实例如果选择器匹配到多个元素,转写稿中演示的结论是只会把第一个满足条件的元素作为挂载元素,所以不要误以为一个实例能同时挂载多个容器。不同实例分别挂载不同元素是可以的。
CDN 引入:不下载文件,直接用脚本地址
为什么需要:只想快速做一个小页面、验证模板语法时,CDN 比下载文件更省事,复制脚本地址即可运行。
怎么想:CDN 引入的关键是区分开发版和生产版。笔记注释已经说明:开发环境版本包含完整警告和调试模式;生产环境版本删除了警告。学习调试应优先用开发版,因为警告能帮助定位问题。
怎么做:把笔记中的两段 CDN 代码按需选一个放入 HTML。
html
<!-- 开发环境版本,包含完整的警告和调试模式 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,删除了警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>引入后仍然用 new Vue 挂载容器,用插值表达式显示 data 中的数据,确认页面能随数据变化而更新。转写稿中反复强调“数据驱动视图”:数据变了,视图会跟着变;这就是配置成功后最直观的验证。
易错点:不要同时引入开发版和生产版,否则容易造成重复引入和警告混乱;学习阶段不要为了“干净”直接用生产版,否则调试信息少;CDN 地址要完整,vue@2/dist/vue.js 与 vue@2 不是同一个文件。
NPM 安装:进入工程化项目的入口
为什么需要:当项目不再是单个 HTML 文件,而是有多个模块、需要构建和依赖管理时,就要用 NPM 安装 Vue。笔记给出的就是最基础的安装命令。
怎么想:NPM 方式不是“装完就自动生效”,它表示把 Vue 作为项目依赖安装到当前工程中。学习时先记住命令和“最新稳定版”的含义,后续再结合具体工程结构使用。
怎么做:在项目目录中执行笔记命令。
shell
# 最新稳定版
$ npm install vue安装后按项目已有的模块方式引入 Vue,再写 new Vue 挂载。转写稿中出现的“new will”“new vi”都应纠正为 new Vue;data 中的属性可以通过插值表达式放到模板中,例如 {{ name }}。这也是验证安装是否可用的最小闭环。
易错点:命令要在项目目录执行,不要在不相关目录乱装;笔记写的是“最新稳定版”,不要自行改成不确定的版本号;不要把 NPM 安装和 CDN 引入混在同一个练习里,否则排查问题时不知道用的是哪一份 Vue。
配置完成后的最小验证:挂载、模板与调试
为什么需要:环境配置不是“代码复制完就结束”,要能证明 Vue 已经接管模板并完成渲染。
怎么想:转写稿中的调试过程很典型:先引入 Vue,再准备 id="root" 的容器,然后 new Vue({ el: '#root', data: { ... } }),最后在容器内写 {{ 属性名 }}。如果页面能显示数据,说明引入、挂载、模板编译这条链路通了。
怎么做:最小结构可以写成:容器 + 引入 Vue + new Vue + 插值。转写稿中还演示了用开发者工具修改数据,页面随之更新,这能帮助理解“响应式”。注意转写稿里的“差值表达式”应纠正为“插值表达式”,“微钢”“违刚”等是 v- 指令的转写误差,本节先不展开。
易错点:挂载容器要写在 new Vue 能选到的位置;el: '#root' 不要漏引号;插值表达式写在被挂载容器内部才由 Vue 编译;如果页面原样显示 {{ name }},优先检查 Vue 是否成功引入、el 是否写对、容器 id 是否一致。转写稿中同一个实例匹配多个元素只取第一个的调试结论,也要在验证时留意。
本节目标与整体思路
这一节围绕“Vue 如何操作 DOM”展开:先看 Vue 实例如何挂载到元素,再看模板中的插值表达式,随后学习 v-html、v-text、v-if、v-else-if、v-else、template 和 v-show。转写稿里的“will / vi / 微钢 / 威刚”等同音字,实际分别指 Vue、Vue 实例、v- 指令。核心结论是:Vue 不主张直接手写 DOM 操作,而是用模板语法把数据“声明式”地绑定到视图;数据变,视图跟着变。
Vue 实例挂载:一个实例只能挂一个满足条件的元素
为什么需要:要先明确 Vue 实例的作用范围,否则后续指令写在哪个容器里、数据从哪里来都会混乱。
怎么想:el 指定挂载容器,#root 表示 id 为 root 的元素。转写稿中先尝试“同一个 Vue 实例挂到不同元素”,结果发现只会把第一个满足条件的元素作为挂载元素,做不到一个实例挂多个容器。
怎么做:
html
<div id="root">
<h1>{{ str }}</h1>
<p>{{ str }}</p>
</div>
<script>
new Vue({
el: '#root',
data: { str: '万' }
})
</script>如果要挂到不同元素,应使用不同 Vue 实例:
html
<div id="one"><h1>{{ str }}</h1></div>
<div id="two"><h1>{{ str }}</h1></div>
<script>
new Vue({ el: '#one', data: { str: 'one' } })
new Vue({ el: '#two', data: { str: 'two' } })
</script>易错点:el: 'root' 少了 # 会选不中;同一个实例不要幻想同时挂多个容器,它只取第一个满足条件的元素。
插值表达式:把实例数据放进模板
为什么需要:模板里要显示数据,不能每次都手动 innerHTML。
怎么想:{{ }} 是 Vue 模板的插值语法,里面的名字会到 Vue 实例的 data、方法等属性中查找。转写稿演示了字符串、数字、布尔值、方法、三元表达式、函数调用,甚至数组和对象。
怎么做:
html
<div id="root">
<p>{{ name }}</p>
<p>{{ 1 }}</p>
<p>{{ fn() }}</p>
<p>{{ sex === 1 ? '男' : '女' }}</p>
<p>{{ str.split('').reverse().join('') }}</p>
</div>
<script>
new Vue({
el: '#root',
data: {
name: '张三',
sex: 1,
str: '超级魔法师',
arr: [1, 2, 3, 4],
obj: { userName: '张三' }
},
methods: {
fn() { return 200 }
}
})
</script>易错点:undefined、null 不会输出;数组不会像 React 那样直接展开,Vue 里数组仍按原样呈现;逻辑复杂或会被重复调用时,建议写方法,不要把所有逻辑塞进模板表达式。
v-text 与 v-html:文本输出和 HTML 输出
为什么需要:有时要把实例属性直接写到元素里,并区分“当文本”还是“当 HTML 解析”。
怎么想:v-text 输出文本,不识别标签;v-html 会把字符串当 HTML 解析。转写稿用“百度”链接演示:v-text 显示标签文本,v-html 能生成可点击的 <a>。
怎么做:
html
<p v-text="userName"></p>
<p v-html="userName"></p>
<script>
new Vue({
el: '#root',
data: {
userName: '<a href="http://baidu.com">百度</a>'
}
})
</script>易错点:v-text="userName" 里的 userName 是实例属性名,不是随便写的字符串;如果写成 v-text="'userName'",输出的就是字符串 userName。v-html 会覆盖元素原有内容,且不要用于不可信内容。
v-if / v-else-if / v-else:条件渲染
为什么需要:页面经常要根据登录状态、年龄、权限等决定某块 DOM 是否渲染。
怎么想:v-if 为真才渲染所在元素;v-else-if 继续判断;v-else 兜底。转写稿用年龄分段演示,条件满足后下面分支不再看。
怎么做:
html
<div id="root">
<p v-if="age > 80">老年</p>
<p v-else-if="age > 40">中年</p>
<p v-else-if="age > 18">青年</p>
<p v-else>少年或儿童</p>
</div>
<script>
new Vue({ el: '#root', data: { age: 100 } })
</script>易错点:v-else-if、v-else 必须和前面的 v-if 紧邻使用,中间不要插入其他没有指令的元素;转写稿中插入一个普通元素后,控制台出现编译异常。若把 v-else 去掉,多个条件可能同时成立,于是“老年、中年、壮年”等会一起显示。
template:只做包裹,不渲染自身
为什么需要:多个元素要共享同一个条件,但又不想额外多一层 div。
怎么想:template 是包裹标签,本身不渲染,可与 v-if、v-show 结合。转写稿中把外层 div 改成 template 后,DOM 里不会多出包裹层。
怎么做:
html
<template v-if="isLogin">
<input placeholder="请输入管理员账号">
<input placeholder="请输入管理员密码">
</template>
<p v-else>未登录</p>易错点:template 不能和 v-html、v-text 结合使用;它适合包裹条件内容,不要把它当成普通可见容器。
v-show:用 display 控制显示隐藏
为什么需要:有些内容要频繁切换显示,但不希望反复销毁、重建 DOM。
怎么想:v-if 控制“是否渲染”,v-show 控制“是否显示”,底层通过 display: none 隐藏。转写稿对比后指出:v-show 为真显示,为假隐藏。
怎么做:
html
<p v-if="isShow">v-if 内容</p>
<p v-show="isShow">v-show 内容</p>
<script>
new Vue({ el: '#root', data: { isShow: true } })
</script>易错点:v-show="isShow" 中 isShow 是实例属性;如果写成 v-show="'isShow'",它会把字符串 isShow 当条件。空字符串、0、undefined 作为判断条件相当于假;非空字符串、数字 1 相当于真。v-show 不会把元素从 DOM 中移除,只是加 display: none。
模板语法与挂载:先理解 Vue 在做什么
为什么需要:Vue 要让我们用声明式方式把 DOM 绑定到实例数据,避免手动操作 DOM。怎么想:模板仍是合法 HTML,浏览器能解析;Vue 在底层把模板编译成虚拟 DOM 渲染函数,再结合响应系统计算最少需要更新什么。怎么做:先引入 lib 下的 vue.js,准备挂载容器,再创建实例。易错点:el 要写选择器字符串,ID 选择器必须带 #。
html
<div id="root">
<h1>{{ str }}</h1>
</div>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#root',
data: { str: '万' }
})
</script>同一实例与多个实例的挂载
为什么需要:要弄清“实例”和“挂载元素”是不是一对一。怎么想:转写稿里先尝试同一个实例挂多个同 ID 元素,结果只渲染第一个满足条件的元素;再试不同实例挂不同元素,可以成功。怎么做:同一实例写 el: '#root' 时,页面中即使有多个 id="root",Vue 也只把第一个作为挂载元素;不同实例分别写 el: '#one'、el: '#two',各管各的容器。易错点:不要以为一个实例能同时挂载多个同选择器元素;重复 ID 本身也不规范。
html
<div id="one"><h1>{{ str }}</h1></div>
<div id="two"><h1>{{ str }}</h1></div>
<script>
new Vue({ el: '#one', data: { str: 'one' } })
new Vue({ el: '#two', data: { str: 'two' } })
</script>插值表达式:把实例数据放进模板
为什么需要:模板里要显示数据。怎么想:双花括号 {{ }} 是模板语法,会被 Vue 编译;里面可以写实例属性、字符串、数字、对象、方法调用和简单表达式。怎么做:data 里的属性可直接用属性名访问;也可写 {{ 1 }}、{{ 'abc' }}、三元表达式、fn()。易错点:undefined、null 不输出;数组不会像 React 那样直接展开,仍按数组形态显示;对象可以输出;逻辑复杂或重复调用时建议用方法,简单表达式可直接写。
html
<p>{{ name }}</p>
<p>{{ 1 }}</p>
<p>{{ flag ? '男' : '女' }}</p>
<p>{{ fn() }}</p>转写稿中还演示了方法内 this 指向 Vue 实例,因此 this.str 能取到数据;例如把字符串反转后返回,模板里写 {{ fn() }} 即可渲染结果。
v-text 与 v-html:文本输出和 HTML 解析
为什么需要:有时要输出纯文本,有时要输出带标签的 HTML。怎么想:指令是特殊属性,以 v- 开头;v-text 输出文本,v-html 会把字符串当 HTML 解析。怎么做:v-text="userName" 会去实例中找 userName;v-html="userName" 同样取值,但标签会被浏览器识别。易错点:v-text 会覆盖元素原有内容;值写属性名时不要乱写未定义属性,否则控制台报“属性或方法未定义”;双引号里再套单引号、或用反引号,表示直接输出这个字符串,而不是取实例属性。
html
<p v-text="userName"></p>
<p v-html="userName"></p>v-if / v-else-if / v-else:条件渲染
为什么需要:根据条件决定元素是否渲染。怎么想:v-if 为真渲染所在元素,为假不渲染;多条件按顺序判断,满足后下面分支不再看。怎么做:可写 v-if、v-else-if、v-else;多条件也可用函数返回结果。易错点:v-else-if、v-else 必须与 v-if 结构上紧邻,中间不要插入没有 v-if 的其他元素,否则编译会异常;多个独立 v-if 会同时判断,可能同时显示多个结果。
html
<p v-if="age > 80">老年</p>
<p v-else-if="age > 40">中年</p>
<p v-else-if="age > 18">青年</p>
<p v-else>少年</p>template 包裹与 v-show
为什么需要:条件渲染多个元素时不想多一层真实 DOM;只想切换显示隐藏时又不想销毁元素。怎么想:template 是包裹标签,不渲染外层;v-show 通过 display 控制显示隐藏。怎么做:<template v-if="isLogin">...</template> 可包多个元素;v-show="isShow" 为真显示,为假隐藏。易错点:template 不能和 v-html、v-text 结合使用;v-show 的空字符串、0、null、undefined 作为判断条件相当于假;v-if 是渲染/不渲染,v-show 是显示/隐藏。转写稿还提到 Vue 1.0 有 v-hide,2.0 已移除,可用 v-show 取反实现隐藏。
html
<template v-if="isLogin">
<input placeholder="请输入管理员账号">
<input placeholder="请输入管理员密码">
</template>
<p v-show="isShow">显示或隐藏</p>从构造函数到实例:先引入 Vue
为什么需要:要创建 Vue 实例,必须先让页面拿到 Vue 构造函数;否则 new Vue 不存在。
怎么想:笔记里先通过 <script src="lib/vue.js"></script> 引入核心模块,它提供构造函数 Vue。
怎么做:在脚本中写 const vm = new Vue({ ... }),vm 就是 Vue 实例,构造函数接收一个配置对象。
易错点:路径要写对,笔记中是 lib/vue.js;不要把 Vue 写成普通函数调用,必须用 new。转写稿里“will/lab”是口误,实际是 Vue 和 lib。
配置对象:data 与 methods
为什么需要:实例需要保存数据和可复用逻辑,配置对象就是入口。
怎么想:data 放实例数据,类型是对象;methods 放方法,类型也是对象。
怎么做:笔记中示例为:
js
const vm = new Vue({
data: {
a: 1,
b: 2,
c: { userName: "zhangsan" },
d: [1, 2, 3]
},
methods: {
fn() {}
}
});易错点:data 中对象的属性会作为 vm 的实例属性,例如 vm.a、vm.c.userName;方法建议统一放 methods,不要随意挂到实例外部。
data 与实例属性的同步关系
为什么需要:很多初学者以为 vm.a 和 data.a 是两份数据,实际它们是同一份数据的代理访问。
怎么想:Vue 会把 data 对象的属性代理到实例上,所以改 data.a 会影响 vm.a,改 vm.a 也会影响 data.a。
怎么做:笔记中的调试代码是:
js
const data = { a: 1 };
const vm = new Vue({ data });
data.a = 100;
vm.a = 200;
console.log(vm.a, data.a);推导过程:先执行 data.a = 100,此时 vm.a 也变为 100;再执行 vm.a = 200,因为访问的是同一份数据,所以 data.a 也变为 200。最终控制台输出 200 200。
易错点:不要误以为最后输出 100 200;同步的关键是“属性代理”,不是复制。转写稿里“想水”应理解为“响应式”:数据变化会驱动视图变化,但本节先抓住实例属性同步。
挂载与多实例:el 的选择
为什么需要:实例要知道把模板渲染到页面哪个容器里。
怎么想:配置项 el 用来指定挂载元素,#root 中的 # 表示 ID 选择器。
怎么做:new Vue({ el: '#root', data: { ... } })。转写稿中做过调试:两个 div 分别设 id="one"、id="two",两个实例分别挂载 #one、#two,页面可以分别渲染,这是“多个实例挂载不同元素”。
易错点:同一个实例如果写 el: 'div',而页面有多个 div,只会把第一个满足条件的元素作为挂载元素,不会同时挂载到所有 div。这就是转写稿中“只会将第一个满足条件的元素作为挂载元素”的结论。
插值与指令初识
为什么需要:数据要显示到模板中,条件要控制元素是否出现。
怎么想:{{ name }} 是插值表达式;v-text 输出文本,v-html 解析 HTML;v-if 控制是否渲染,v-show 用 display:none 隐藏。
怎么做:转写稿中把布尔值从 true 改成 false 观察:v-if 对应元素会从 DOM 中消失;v-show 元素还在,但样式变为隐藏。v-else-if、v-else 必须和 v-if 结构配合使用。
易错点:v-text 不会把字符串当 HTML 解析,v-html 才会;v-else-if/v-else 所在元素之间不要插入无关元素,否则结构容易出错。template 是包裹标签,本身不渲染,转写稿中验证过它不能和 v-html/v-text 直接结合使用,但常和 v-if 等配合。
完整示例代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--1- 引入核心模块,提供了构造函数Vue -->
<script src="lib/vue.js"></script>
</head>
<body>
</body>
<script>
// 1- vm即是Vue实例。
// 2- Vue构造函数接收一个配置对象。
// 3- 配置对象中可以设置data属性(类型是一个对象)
// 4- data对象的属性会作为vm的实例属性(记住)。
// 5- 建议将方法放置到methods属性中(类型也是一个对象)
// const vm = new Vue({
// // 实例中的数据
// data:{
// a:1,
// b:2,
// c:{
// userName:"zhangsan"
// },
// d:[1,2,3]
// },
// // 定义方法
// methods:{
// fn(){
//
// }
// }
// });
// console.log(vm);
// 关于data的特点:
// 1- data中的属性会作为Vue实例中的属性
// 2- data的属性值发生改变,那么Vue实例中对应的属性值也会发生改变
// 3- Vue实例中的属性值发生改变,那么data中的属性值也会发生改变。
const data = {
a:1
}
const vm = new Vue({
data
});
data.a = 100;
vm.a = 200;
console.log(vm.a,data.a);
</script>
</html>挂载到底在解决什么问题
为什么需要:new Vue({...}) 只是创建了一个 Vue 实例,但页面上哪一块 DOM 归它管,必须明确指定。这个“指定作用范围”的动作就是挂载。没有挂载,模板里的 {{str}} 不会被 Vue 编译,数据也不会驱动视图。
怎么想:把 Vue 实例想成一个“编译器 + 数据源”,el 告诉它从哪里开始编译;挂载到的元素及其内部内容,此后就可以称为 Vue 模板。
怎么做:在实例配置里写 el。推荐用 ID 选择器:
js
const vm = new Vue({
el: "#root",
data: {
str: "我现在开始学习Vue,第一步要进行挂载"
}
});易错点:el:"body"、el:"html" 不允许;el:".root"、el:"div" 虽然可能选中元素,但不建议,因为选择范围不明确,容易挂错。
el 选择器与“第一个匹配元素”
为什么需要:同一个页面可能有多个 div 或多个同类名元素,Vue 必须知道到底挂哪一个。
怎么想:转写稿里反复调试得出一个关键结论:同一个 Vue 实例写 el:"div" 时,只会把第一个满足条件的元素作为挂载元素,而不是把所有 div 都接管。
怎么做:优先写唯一 ID,例如 el:"#root";也可以用 el:document.querySelector("#root") 传入 DOM 元素。不同 Vue 实例可以分别挂到不同元素上,例如一个挂 #one,另一个挂 #two,这是可以的。
易错点:不要指望“一个实例挂多个元素”。同一个实例挂多个同类元素做不到;需要多个实例分别挂载。转写稿里的“相同 Vue 实例挂载到不同元素”实验,最后验证的就是“只取第一个满足条件的元素”。
模板中为什么能直接写属性、this 和 vm
为什么需要:模板里要显示数据,如果每次都写很长的实例路径会很麻烦。
怎么想:Vue 会把 data 中的属性代理到实例上,模板作用域里可以直接访问这些属性;同时模板中的 this 指向 Vue 实例。转写稿里的“模块中可以使用 this”应纠正为“模板中可以使用 this”,但不建议依赖它。
怎么做:笔记代码中三种写法:
html
<h1>{{str}}</h1>
<h1>{{this.str}}</h1>
<h1>{{vm.str}}</h1>前两种分别直接访问 data.str 和通过模板上下文 this.str 访问。注意:代码的 data 里又定义了一个 vm:{str:"vm"},所以 {{vm.str}} 实际取到的是 data.vm.str,输出 vm,并不是外部 const vm 实例。这里要按代码实际结构理解,不能想当然。
易错点:模板里能用 this 不代表推荐写 this;日常直接写 {{str}} 最清晰。若在 data 中定义了与外部实例同名的 vm,模板里的 vm 会优先指向 data.vm,容易混淆。
完整代码与调试过程
转写稿里的调试路径是:先写 el:"#root",再在模板里写 {{str}},刷新看到数据;然后把 el 改成 div 并复制多个 div,发现只有第一个满足条件的元素生效;再分别建两个实例挂 #one、#two,验证“不同实例挂不同元素可以”。这比只背结论更能理解挂载边界。
本节笔记代码如下,保留原代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div class="root" id="root">
<h1>{{str}}</h1>
<h1>{{this.str}}</h1>
<h1>{{vm.str}}</h1>
</div>
</body>
<script>
// 注意重点:在Vue模板中可以直接使用Vue实例中的属性或方法(不需要使用this,不需要使用this)
// 注意重点:模块中可以使用this(不建议),说明模块所处的环境上下文中的this指向的是Vue实例。
// 挂载(Vue实例挂载):指定Vue实例应用的范围(元素)
const vm = new Vue({
// 将ID为root的元素作为Vue实例挂载的元素(挂载的元素自此可以称为Vue模板)
// el:document.querySelector("#root"),
// 将ID为root的元素作为Vue实例挂载的元素(挂载的元素自此可以称为Vue模板)
// el:"#root",
// 将class为root的元素作为Vue实例挂载的元素( 不建议)
// el:".root",
// 将标签名字为div的元素作为Vue实例挂载的元素( 不建议)
// el:"div",
// el:"body",// 不允许
// el:"html",// 不允许
data:{
str:"我现在开始学习Vue,第一步要进行挂载",
vm:{
str:"vm"
}
}
})
</script>
</html>易错点清单
el写#root不要漏掉#,转写稿里“井号代表 ID”就是提醒这一点。- 不要把
el:"body"、el:"html"当作挂载点。 - 一个实例只接管第一个匹配元素,不要用同类选择器做“批量挂载”。
- 模板里直接写
{{str}}最稳;{{this.str}}能工作但不建议;{{vm.str}}要看清data中是否已有vm对象。 - 转写稿中的“差值表达式”应纠正为“插值表达式”,“微钢/违装衣服”等是口误或同音字,实际是
v-指令、v-if等,本节先牢牢掌握挂载边界。
本节要解决的问题
为什么需要:初学 Vue 时,很容易把 el 当成“批量选择器”,以为写 el:"div" 就能让页面上所有 div 都变成 Vue 的挂载点,于是两个 div 里的 {{str}} 都应该显示同一个数据。本节就是要澄清:一个 Vue 实例到底能不能同时挂载到多个元素上。
怎么想:el 本质上是给 Vue 指定“根挂载元素”的选择器。Vue 会按选择器去找元素,但一个实例只认一个根挂载点;当选择器匹配到多个元素时,它只会取第一个满足条件的元素,而不是把匹配到的元素全部接管。
怎么做:先看本节笔记中的原始示例:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div>
<h1>{{str}}</h1>
</div>
<div>
<h1>{{str}}</h1>
</div>
</body>
<script>
// 只会将第一个符合条件的元素作为挂载元素
new Vue({
el:"div",
data:{
str:"one"
}
})
</script>
</html>易错点:不要把 el:"div" 理解成“所有 div 都挂载”。这里的 div 是选择器,页面中有两个 div,Vue 只把第一个当作挂载元素。第二个 div 不会被这个实例编译,里面的 {{str}} 也不会按这个实例的数据渲染。
复现现象:两个 div 只渲染第一个
为什么需要:只看结论容易记混,必须通过调试看到“只生效第一个”的现象,才能理解实例和挂载点是一对一关系。
怎么想:转写稿中的调试思路是:先写一个容器,例如 id="root",用 el:"#root" 能正常渲染;然后把容器复制一份,再把 el 改成 "div",刷新页面观察两个 div 的表现。此时两个 div 都写了 {{str}},但只有第一个被 Vue 接管并显示 one,第二个仍然是模板原样或未被该实例渲染。
怎么做:按笔记代码运行后,重点看两个 div 中的 h1。第一个会显示 one;第二个不会跟着变成 one,因为它不是这个 Vue 实例的挂载根元素。控制台通常不会因为这个写法直接报错,所以不能只靠“有没有报错”判断挂载是否成功。
易错点:看到第二个没渲染,不要先怀疑 data 写错。这里 data 没有问题,问题在 el 匹配到了多个元素,而实例只取第一个。调试时应先检查“这个实例到底挂到了哪个元素上”。
为什么相同实例不能挂载多个元素
为什么需要:理解 Vue 实例的边界。一个 new Vue({...}) 创建的是一个实例,el 指定的是它的根挂载元素;实例的模板编译、数据绑定都围绕这个根展开。
怎么想:转写稿里明确总结:“只会将第一个满足条件的元素作为你的挂载元素”。也就是说,相同 Vue 实例挂载到不同元素这件事做不到。不是说数据不能共享,而是“一个实例同时接管多个根元素”这个动作不成立。
怎么做:如果业务上确实有两个区域都要受 Vue 控制,正确方向不是让一个实例硬挂多个元素,而是拆成两个实例,或者后续用组件组织。就本节而言,先掌握“一个实例一个挂载根元素”。
易错点:不要复制多个 div 后仍写 el:"div",然后以为两个都会生效。也不要因为第二个没生效就去改 data 里的 str,那不会改变挂载点的选择结果。
正确做法:不同实例挂载不同元素
为什么需要:当页面上确实有多个独立区域时,需要让每个区域有明确的挂载点,避免选择器匹配到多个元素后只取第一个。
怎么想:转写稿中的做法是给两个 div 分别加唯一 id,例如一个 id="one",一个 id="two";然后写两个 Vue 实例,分别用 el:"#one" 和 el:"#two" 挂载。这样两个实例各管一个根元素,数据也可以各自独立。
怎么做:可写成下面的结构:
html
<div id="one">
<h1>{{str}}</h1>
</div>
<div id="two">
<h1>{{str}}</h1>
</div>
<script>
new Vue({
el: "#one",
data: {
str: "one"
}
})
new Vue({
el: "#two",
data: {
str: "two"
}
})
</script>运行后,上面显示 one,下面显示 two。这就验证了:相同实例不能挂多个元素,但不同实例可以分别挂载不同元素。
易错点:el 里的 ID 选择器要写 #;页面中 id 要唯一;两个实例的 data 要分别写清楚。不要把 el:"one" 当成 ID 选择器,ID 选择器应写 "#one"。
本节结论与检查方法
为什么需要:把现象上升为可复用的判断规则,后面写多个 Vue 区域时才不会反复踩坑。
怎么想:记住两条:第一,el:"div" 这类选择器匹配到多个元素时,只把第一个满足条件的元素作为挂载元素;第二,同一个 Vue 实例不能同时挂载到多个不同元素上,多个区域应使用多个实例或组件化组织。
怎么做:检查时先看 el 写的是什么选择器,再看页面上匹配到了几个元素,最后确认实际渲染的是不是第一个。若需要两个区域都渲染,就给不同根元素不同 id,分别 new Vue。
易错点:把“第一个满足条件”误记成“全部满足条件”;把“不同实例挂不同元素”误写成“相同实例挂不同元素”;选择器写错导致根本没挂上。
需求:同一页面里让两块内容各自显示不同数据
为什么需要:实际页面经常不是只有一个根容器,比如左侧导航和右侧内容区、两个独立卡片、两个互不影响的计数器。如果所有内容都塞进同一个 Vue 实例,数据和模板会耦合在一起;把不同区域交给不同 Vue 实例管理,结构更清楚。
怎么想:先明确“实例”和“挂载元素”是一对一关系。一个 new Vue({ el: ... }) 只负责一个 el 指定的容器;如果页面上有两个容器,就准备两个实例。每个实例有自己的 data,模板里写 {{ str }} 时,会到“当前实例”的 data 里找 str。
怎么做:下面保留本节笔记代码。注意转写稿里“will / lab / EL”等是口述谐音或大小写误写,实际应写 Vue、lib/vue.js、el。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="one">
<h1>{{str}}</h1>
</div>
<div id="two">
<h1>{{str}}</h1>
</div>
</body>
<script>
// 同一个页面,支持多个实例挂载至不同元素中。
new Vue({
el:"#one",
data:{
str:"one"
}
})
new Vue({
el:"#two",
data:{
str:"two"
}
})
</script>
</html>易错点:el:"#one" 里的 # 不能漏,# 表示 id 选择器;两个容器 id 不能重复;两个实例的 data 是彼此独立的,上面实例的 str 是 "one",下面实例的 str 是 "two",不会互相覆盖。
挂载容器:el 指向谁,模板范围就是谁
为什么需要:Vue 需要知道“从哪一块 DOM 开始接管”。el 就是挂载点。转写稿中反复强调“指定一个元素作为挂载的容器”“井号代表 id”,目的就是让实例知道自己编译哪一块模板。
怎么想:先写容器,再写实例。容器里放 {{ str }},实例里用 el 找到容器,用 data 提供 str。浏览器先解析 HTML,Vue 再把 el 范围内的插值编译成数据。
怎么做:先写 <div id="one"><h1>{{str}}</h1></div>,再写 new Vue({ el:"#one", data:{ str:"one" } })。刷新后,#one 里的 h1 显示 one。同理复制一份,把 id 改成 two,把 el 改成 "#two",把 data.str 改成 "two",下面显示 two。转写稿的调试过程就是:先做一个容器,验证能显示;再复制容器和实例,分别改 id、el、data,刷新确认上下都生效。
易错点:模板插值必须写在 el 挂载的容器内部;el 写错 id,页面不会按预期渲染;data 里没有 str,模板里的 {{str}} 就取不到值。不要把 el 写成 "one",那会被当作标签名选择器,而不是 id。
关键结论:不同实例可以挂不同元素,同一实例不要试图挂多个元素
为什么需要:初学者容易把“多个容器”误解成“一个实例写多个 el”或“一个实例同时管两块”。转写稿专门做了对照实验:同一个实例换到不同元素,做不到;不同实例挂到不同元素,可以。
怎么想:Vue 实例的挂载点是单值配置,不是数组。转写稿里说,如果让选择器满足多个元素,“只会将第一个满足条件的元素作为挂载元素”。所以正确思路不是让一个实例管两个容器,而是“一个容器一个实例”。
怎么做:按笔记写两个 new Vue,分别 el:"#one" 和 el:"#two"。运行后检查:第一个 h1 是 one,第二个 h1 是 two。转写稿中的验证话术是“上面这个 H1 呈现出来的是 one,下面这个 H1 呈现出来的是 two”,说明两个实例各自工作。
易错点:不要写两个 el 字段,也不要把 el 写成 "#one,#two" 期待一个实例管两块;不要复制实例后忘记改 data.str,否则两个区域可能显示同一个值;不要把两个容器写成相同 id,否则选择器只会命中第一个,第二个实例行为就不符合预期。
小结
本节核心不是复杂语法,而是建立“实例与挂载元素一对一”的模型:页面可以有多块区域,每块区域用独立 Vue 实例挂载;el 用 #id 精确指定容器;data 为当前实例提供数据;模板中的 {{str}} 只在当前实例范围内取值。按转写稿的调试路径:先单容器验证,再复制成两个容器,分别改 id、el、data,刷新对照上下显示结果,就能稳定复现“不同 Vue 实例挂载到不同元素”的效果。
课堂画面






代码示例
示例一:Vue 实例与挂载
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 实例与挂载</title>
<!-- 开发环境版本,包含完整的警告和调试模式 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<!-- 1. el 指定的元素就是 Vue 实例的挂载点,也就是"Vue 模板" -->
<div id="root">
<h1>{{str}}</h1>
<h1>{{this.str}}</h1>
</div>
<!-- 2. 同一个选择器匹配到多个元素时,只会挂载第一个 -->
<div class="item"><h1>{{str}}</h1></div>
<div class="item"><h1>{{str}}</h1></div>
<!-- 3. 一个页面允许不同实例挂载到不同元素 -->
<div id="one"><h1>{{str}}</h1></div>
<div id="two"><h1>{{str}}</h1></div>
</body>
<script>
// data 中的属性会成为实例属性,且双向同步
const data = { a: 1 };
const vm = new Vue({ data });
data.a = 100;
vm.a = 200;
console.log(vm.a, data.a); // 200 200
// 挂载:el 可以是选择器字符串,也可以是 DOM 元素
new Vue({
el: '#root',
data: {
str: '我现在开始学习Vue,第一步要进行挂载'
}
});
// el 匹配到多个 .item,只有第一个被挂载,第二个原样输出 {{str}}
new Vue({
el: '.item',
data: { str: 'one' }
});
// 多个实例分别挂载到不同元素
new Vue({ el: '#one', data: { str: 'one' } });
new Vue({ el: '#two', data: { str: 'two' } });
</script>
</html>关键行与易错点
new Vue({...})的返回值就是 vm 实例,构造函数的参数是一个配置对象,常用data、methods、el。data里的属性会直接成为实例属性,并且双向同步:改data.a或改vm.a,两边值都变(最后打印两个 200 就是这个原因)。- 模板里可直接写
{{str}},不需要this;写{{this.str}}也能渲染,但不建议。 el写成选择器字符串时等价于document.querySelector,只取第一个满足条件的元素,所以示例二段里第二个.item保持原样。el: "body"、el: "html"不允许;el: "div"、el: ".item"这类宽泛选择器能跑但不建议,因为匹配范围不可控。
示例二:插值表达式与 v-text / v-html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板语法与指令</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>{{ name }}</p>
<p>{{ str }}</p>
<p>{{ 1 }}</p>
<p>{{ bol }}</p>
<p>{{ fn() }}</p>
<p>{{ arr }}</p>
<p>{{ obj }}</p>
<p>{{ null }} | {{ undefined }}</p>
<p>{{ sex === 1 ? '男' : '女' }}</p>
<p>{{ str.split('').reverse().join('') }}</p>
<hr>
<!-- 值是"属性名"的字符串,会去实例中找同名属性 -->
<p v-text="userName"></p>
<p v-html="userName"></p>
<!-- 值里再套一层引号,就直接输出这段字符串,不去实例里找 -->
<p v-text="'userName'"></p>
<!-- 唯一区别:v-html 会解析 HTML 标签,v-text 只当纯文本 -->
<p v-text="link"></p>
<p v-html="link"></p>
</div>
</body>
<script>
new Vue({
el: '#app',
data: {
name: 'zhangsan',
str: '超级魔法师',
bol: true,
arr: [1, 2, 3, 4],
obj: { userName: '张三' },
sex: 1,
userName: '我是一个字符串',
link: '<a href="http://www.baidu.com">百度</a>'
},
methods: {
fn() {
// 方法里的 this 指向 Vue 实例
return this.str.split('').reverse().join('');
}
}
});
</script>
</html>关键行与易错点
- 插值表达式支持:字符串、数字、布尔、对象、方法调用、三元表达式、字符串反转等表达式;
methods中的方法也会被挂到实例上,模板里可直接fn()调用。 null和undefined不会做任何输出,页面只剩|。- 数组
{{ arr }}输出的是[1,2,3,4]原样文本,不会自动展开成元素(这点和 React 不同)。 - 指令值是"属性名"字符串:
v-text="userName"会去实例找userName;若实例里没有这个属性,控制台会报Property or method "xxx" is not defined。 - 想直接输出字符串本身,就要在双引号里再套单引号:
v-text="'userName'"(等价于反引号写法),此时不会去实例里查。 v-text与v-html唯一区别就是是否解析 HTML:v-text输出文本、标签不生效;v-html输出会被浏览器识别,a标签可点击跳转。另外两者都会覆盖元素原有内容。
示例三:v-if / v-else-if / v-else、template、v-show
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>条件渲染</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 1. 多条件:v-if / v-else-if / v-else 必须紧挨着写 -->
<p v-if="age > 80">老年</p>
<p v-else-if="age > 40">中年</p>
<p v-else-if="age > 28">壮年</p>
<p v-else-if="age > 18">青年</p>
<p v-else-if="age > 12">少年</p>
<p v-else-if="age > 6">儿童</p>
<p v-else>婴儿</p>
<hr>
<!-- 2. template 只是包裹标签,本身不渲染,可用于 v-if 分支 -->
<template v-if="isLogin">
<input type="text" placeholder="请输入管理员账号">
<input type="password" placeholder="请输入管理员密码">
</template>
<p v-else>未登录</p>
<hr>
<!-- 3. v-if 与 v-show 的区别 -->
<p v-if="isShow">v-if 控制</p>
<p v-show="isShow">v-show 控制</p>
</div>
</body>
<script>
new Vue({
el: '#app',
data: {
age: 100, // 改成 60 / 19 / 3 观察分支切换
isLogin: false, // 改成 true 观察 template 分支
isShow: true // 改成 false 观察 v-if 与 v-show 的区别
}
});
</script>
</html>关键行与易错点
v-if的值是布尔表达式:成立则渲染该元素,不成立则不渲染(元素在 DOM 中根本不存在);v-else-if、v-else的值写法与v-if相同,v-else后面不写值。v-else-if/v-else必须紧跟在v-if或上一个v-else-if之后,中间不能插入其它元素,否则会编译报错或分支错乱。把几个v-else-if的v-else去掉后,age=100会同时命中所有条件、全部渲染——这是最典型的错误。template是包裹标签,自身不渲染任何标签,很适合给一组元素加同一个v-if,但template上不能和v-text/v-html结合使用(没效果)。v-show与v-if值相同,但v-show是通过display: none控制显示隐藏,元素始终在 DOM 中;v-if是真正销毁/重建。要频繁切换用v-show。v-show的值会做真假判断:空字符串、0、null、undefined都相当于 false,非空字符串、数字1相当于 true。
常见错误
el写成"root",页面原样显示{{str}}现象:控制台无报错,但插值原封不动出现在页面上,Vue 开发者工具里看不到根实例。 原因:el接收的是 CSS 选择器字符串或真实 DOM 元素,"root"被当成标签名去找<root>,找不到就静默不挂载。 修法:el: "#root";或el: document.querySelector("#root")。挂载失败时优先检查这里。el用 class 或标签名,第二个同类容器不渲染 现象:页面里两个<div>都写了{{str}},只有第一个变成真实数据,第二个仍是花括号。 原因:选择器匹配到多个元素时,Vue 只取第一个命中项作为挂载点,不会批量挂载。 修法:每个实例绑定唯一id;一个实例只对应一个容器,多个容器就多写几个new Vue。模板里写
{{this.str}}、{{vm.str}},或把if/for塞进插值 现象:有时能渲染,有时报xxx is not defined;写语句时直接编译报错。 原因:模板表达式的作用域就是实例本身,this可用但不该用;且插值只接受表达式,不接受语句。 修法:直接写{{str}};多分支逻辑抽到methods里{{format(x)}},或退化成三元表达式。指令值引号用错:
v-text="张三"报未定义,v-show="'isShow'"永远显示 现象一:控制台报Property or method "张三" is not defined。 现象二:明明数据是false,元素却始终可见。 原因:指令值默认按表达式解析,张三被当作变量名去实例上找;加了一层引号后'isShow'变成非空字符串,恒为真值。 修法:要取数据写v-text="userName";要输出字面量写v-text="'张三'";v-show只写变量名,不加内层引号。v-if/v-else-if/v-else链中间夹了别的元素 现象:条件判断整体失效,多个分支同时渲染,或编译期报错。 原因:v-else-if/v-else必须与紧邻的前一个v-if配对,中间插入任何无指令元素都会断链。 修法:删除中间元素;确实需要分组时用<template v-if>包裹整段,而不是在分支之间插内容。把
v-show/v-html/v-text写在<template>上 现象:页面什么都不显示,控制台也不报错,排查很久找不到原因。 原因:<template>是编译期包裹标签,本身不产生真实 DOM 节点;v-show靠display控制显隐、v-html需要真实节点承载,都无处落脚。 修法:v-show换成v-if(可以作用在<template>上),或把指令下移到内层真实元素上。改了数据视图不更新:
vm.arr[0] = x、vm.obj.newKey = x现象:控制台打印数据已变,页面上还是旧值,且没有任何警告。 原因:Vue 2 通过Object.defineProperty劫持已有属性,数组下标赋值和对象新增属性都不在劫持范围内。 修法:数组用splice/ 整体替换,对象用Vue.set(vm.obj, 'newKey', x)或vm.$set;改完仍不生效时先看是不是这个原因,而不是怀疑模板写错。用生产版 CDN 调试,警告全消失就以为没问题 现象:本地引入
vue@2(生产版)后,模板语法错误不再提示,问题被掩盖到线上才暴露。 原因:生产版移除了警告与调试模式,只保留运行时行为。 修法:开发阶段固定用vue.js(开发版),上线再换vue.min.js/ 生产 CDN。
练习
挂载纠错:给出一段代码——
el: "body"、两个<div class="box">、模板里写{{this.msg}}、v-text="msg"但data里只有message。要求找出全部问题并改到正常渲染,同时说明每处为什么错。同数据两种渲染:用一个
age变量,分别用「三元表达式」「methods方法」「v-if指令链」三种方式渲染「老年 / 中年 / 青年 / 少年 / 儿童」。写完后对比:哪种在模板里最容易维护,哪种在 DOM 上更省节点。v-if与v-show对照实验:做一个「登录 / 未登录」切换。两个版本分别用v-if和v-show实现,用开发者工具观察切换时元素是消失还是仅display: none,并记录页面初次加载时两者的差异。v-html与v-text安全练习:把"<a href='https://www.baidu.com'>百度</a>"分别用v-html和v-text渲染,观察结果;再假设这段内容来自用户输入框,说明为什么不能直接交给v-html。多实例拆分:一个页面放三个区块(头部、列表、侧栏),要求用三个独立 Vue 实例分别挂载,各自维护自己的
data。做完后思考:如果三个区块需要共享同一份数据,这种拆分方式会带来什么问题。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 不同Vue实例挂载到不同元素 | 04:16 |
| 2 | 模板中的插值表达式 | 16:38 |
| 3 | v-html v-text | 18:05 |
| 4 | v-if v-else-if v-else | 19:02 |
| 5 | 补充-template | 06:37 |
| 6 | v-show | 10:02 |
小结
- 挂载是整个流程的入口,
el写错时 Vue 往往不报错只是不干活,所以任何"页面没反应"的问题,第一步永远是确认实例有没有挂上、挂在哪个元素上。 - 一个实例对应一个容器,容器用唯一
id;class 和标签名选择器属于"能跑但埋雷"的写法,不要用。 - 模板里的一切表达式都在实例作用域内求值,写之前先问自己:这个名字在
data/methods里存在吗? - 指令的值分两种语义——表达式(默认)和字面量(额外加引号),引号层级是新手最高频的翻车点,写指令时先数清引号。
v-if与v-show不是二选一的美学问题,而是渲染成本 vs 切换成本的取舍;同时记住v-if支持<template>,v-show不支持。- Vue 2 的响应式是有边界的,数组下标和对象新增属性是已知盲区,遇到"数据变了视图没变",先怀疑赋值方式而不是模板。
- 开发期一律用带警告的版本,把控制台当成第一手反馈;没有警告不等于没有问题,可能只是你把警报器关了。
- 这一节的所有指令与插值,本质都是在解决同一件事:用声明式写法描述"数据长什么样",把 DOM 操作交给框架——后面学组件、计算属性、
v-for时,这条主线不会变。