主题
购物车(略)与组件(重点中的重点)(Lesson4 1/3)
本页对应 Lesson04,共 6 段视频,约 1 小时 20 分。
学习目标
- 能够说明计算属性的缓存机制与依赖追踪规则,并判断给定场景应使用计算属性、方法还是过滤器,同时指出计算属性与 data/methods 重名时的表现。
- 能够分析 async 函数的返回值与 Promise 状态/结果,区分 async 函数返回普通值、Promise、抛出异常及 await 非 Promise/Promise 时的执行结果。
- 能够使用字符串模板、
script type="text/x-template"模板和template标签三种方式定义模板,并说明模板替换挂载元素/组件渲染内容的行为及根元素限制。 - 能够通过
components选项注册局部组件,在视图中正确使用组件,并依据组件命名规则处理大小写、驼峰与短横线写法。 - 能够实现父子组件嵌套,说明局部组件的使用范围,并将子组件注册到父组件的
components中完成组合渲染。
知识点
从购物车合计看计算属性的使用边界
为什么需要:购物车列表里常有“合计”,它往往不是单个数据,而是由一个或多个状态计算出来的结果。转写稿先用这个场景复习:计算属性不是“同一视图多次渲染才能用”,而是适合表达“结果由状态推导”。
怎么想:先判断这个结果是“单个数据的格式化”,还是“多个数据参与计算”。如果只是把 str 反转,用过滤器、方法或计算属性都能做;如果是合计、汇总、依赖多个状态的结果,计算属性更合适。计算属性在渲染解析时会走对应函数并缓存结果,只有影响结果的数据变化才重新执行。
怎么做:例如反转字符串可写成:
js
computed: {
comReverse() {
return this.str.split('').reverse().join('')
}
}模板中用 {{ comReverse }}。转写稿调试时还加了 name 按钮:改 name 不会让 comReverse 重新执行,因为它的依赖只有 str。
易错点:计算属性不要与 data 或 methods 中的名字重复,否则控制台会警告;不是异常时页面可能还能看到,但命名冲突必须改掉。计算属性慢一点来自解析和缓存逻辑,实际可忽略,但“单个数据简单反转”用它有点浪费。
计算属性、过滤器与方法的取舍
为什么需要:同样一个反转,为什么有时用过滤器、有时用计算属性?转写稿强调应用场景不同:过滤器偏向某个数据的格式化,计算属性偏向一个或多个数据状态计算出的结果。
怎么想:过滤器里 this 指向 window,不希望你操作 Vue 实例里的其他属性;计算属性依赖实例数据状态。面试可补一句:Vue 中过滤器完全可由函数替代,Vue 单独设计它是为了抽离常见格式化工作。
怎么做:简单格式化可用过滤器或方法;需要依赖多个状态并缓存结果时用计算属性。不要误以为“计算属性只能在模板里用多次才值得用”。
易错点:把过滤器当万能计算工具,或在过滤器里访问实例状态,都会偏离设计意图。
命名冲突与依赖缓存:两个调试现场
为什么需要:转写稿用两个现场说明计算属性的规则。第一,data 里也写 comReverse,与计算属性重名,控制台出现警告。第二,改 name 时 comReverse 不重新执行。
怎么想:计算属性的值“往往是由一个或多个数据状态计算出来的”,只有影响计算结果的数据发生改变,对应函数才重新执行。命名上,计算属性不能与 data 或 methods 重名。
怎么做:删掉 data 中同名项;用按钮修改 name 观察 comReverse 是否执行,再用 str 验证依赖变化。易错点:把警告当异常;重名后不清理;误以为任何数据变化都会触发所有计算属性。
getter/setter:计算属性也可被赋值
为什么需要:计算属性不仅可以返回函数结果,也可写成对象形式,包含 get 和 set。转写稿提醒:使用计算属性时执行 get;更改计算属性值时要走 set。
怎么做:
js
computed: {
fullName: {
get() { return this.first + this.last },
set(val) { /* 拆分并写回数据 */ }
}
}易错点:只写 get 却直接赋值,会出异常;需要赋值时必须提供 set。
async/await 复习:返回值和异常
为什么需要:转写稿说后面做 PC 端项目会常用 async/await,所以先复习。async 描述函数,await 描述表达式;await 可离开 async 吗?不行,await 离不开 async。
怎么想:async 函数返回的永远是新的 Promise。若 return 非 Promise,得到成功 Promise,值就是返回值;若 return 一个 Promise,状态和值与其一致,但引用不同;若返回失败 Promise 或抛异常,则失败。await 右侧是非 Promise 就直接得值;右侧失败会抛异常,需要 try/catch;右侧 pending 会等待,后续不执行。
怎么做:可用 try { const a = await p } catch(e) {} 捕获。易错点:语法错误会让 JS 根本不执行;类型错误等运行错误才可能被 catch 捕获;不要把 async 返回值误认为原 Promise 本身。
组件与模板:先理解“零件”
为什么需要:组件就是组装件、零件,把页面拆成若干部分,提升可读性、便于团队协作。转写稿把组件分为通用性组件(多个页面引入使用)和功能性组件(把页面内容拆成若干文件,在不同组件完成对应部分)。
怎么想:像拆页面:头部、尾部、导航、表格、分页。分页常是通用性;不同业务表格是功能性。怎么做:Vue 中声明组件也叫注册组件,需在配置对象中设置 components,属性名即组件名,值是对象。易错点:组件名大小写规则要遵守;模板内容只能有一个根元素。
模板的三种写法与根元素限制
为什么需要:学组件前要先会模板。转写稿给出字符串模板、script type="text/x-template"、template 标签三种写法。
怎么做:template 会替换 el 所指定挂载元素的内容;script 写法要设 type="text/x-template" 和 id,再用 template: '#id' 指定;template 标签写法用 template: '#id' 指定。易错点:模板只能有一个根元素,多个同级元素会报编译错误;script 不写 type 会被当普通脚本解析。
定义局部组件与组件嵌套
为什么需要:局部组件只作用于当前实例或父组件。转写稿演示:在实例 components 中注册 one,模板里写 <one></one>;组件内用 template 指定渲染内容。
怎么做:
js
new Vue({
el: '#app',
components: {
one: { template: '<div><h3>这是one组件</h3></div>' }
}
})组件名小写,视图小写即可;首字母大写、其余小写,视图大小写均可;多单词驼峰在视图中要用横线分割,如 myComment 写成 <my-comment>。建议首字母大写,或全小写并用横线。嵌套时,子组件要注册在父组件的 components 中;实例 components 里声明的组件只允许在实例模板中使用。易错点:把子组件只注册在实例里却在父组件模板中使用,会提示找不到组件;直接闭合标签在转写稿当前环境可能只生效一次,脚手架中再验证。
组件是什么:对标签的扩展
为什么需要:页面一复杂,所有结构和逻辑都堆在一个实例里,阅读和维护都会变差。组件就是把页面拆成可组装的“零器件”,再把一组标签和逻辑封装成自定义标签来用。
怎么想:笔记里说“组件:对标签的扩展”,可以理解成:原本只有 div、p 等原生标签,现在我们可以注册 one、App、chart 这样的自定义标签。它和 React 的组件思想相通,但 Vue 里使用组件时首字母不必强制大写。
怎么做:先按页面区域拆分,例如头部、尾部、新闻列表、商品列表;每个区域写成一个组件,再组合成页面。
易错点:组件不是写个标签就自动生效,必须先注册;注册后还要注意模板根元素和命名规则。
功能性组件与通用性组件
为什么需要:不是所有组件都为了“多处复用”,有些组件是为了把复杂页面拆开,降低单页阅读难度。
怎么想:笔记把组件分为功能性组件和通用性组件。通用性组件往往会在多个页面中引入并使用,提升复用率;功能性组件是把页面中的内容拆分成若干组件,在不同组件中完成对应功能,提高阅读性,也方便团队协作。
怎么做:按复用范围判断。转写里举了表格和分页:不同业务的表格(医院列表、用户列表、订单列表)通常各自是功能性组件;而每个列表下面都有的分页,更适合做成通用性组件。
易错点:不要把所有组件都叫“通用”。通用看的是是否多页面复用;功能看的是是否承担页面中的某块功能。
模板三种写法:先会写,再选型
为什么需要:组件要告诉 Vue“我渲染成什么”,就要用 template 指定模板内容。模板写法不熟,后面组件注册就会卡住。
怎么想:转写里提到三种:字符串模板、script type="text/x-template"、<template> 标签。script 方案了解即可,实际相对更常见的是 <template> 标签;字符串模板适合短内容。
怎么做:
html
<script type="text/x-template" id="tp">
<div>
<h3>这是 script 模板</h3>
<p>{{ a }} {{ b }}</p>
</div>
</script>
<template id="tp2">
<div>
<h3>这是 template 标签模板</h3>
</div>
</template>js
new Vue({
el: '#app',
data: { a: 'A', b: 'B' },
template: `<div><h3>字符串模板</h3><p>{{ a }} {{ b }}</p></div>`
})易错点:template 会替换 el 所指定的挂载元素,不是简单插进去;模板内容只能有一个根元素,多个同级根元素会报编译错误。转写里演示过:去掉外层 div 后控制台报模板编译错误。script 必须写 type="text/x-template",否则会被当普通 HTML 解析。
局部组件注册:components 是登记表
为什么需要:自定义标签要让当前 Vue 实例认识,否则视图里写了也不认识。
怎么想:转写里说,Vue 中声明组件也被称为注册组件;局部组件要写在配置对象的 components 中。属性名就是组件名,属性值是一个对象。
怎么做:
js
new Vue({
el: '#app',
components: {
one: {
template: '<div><h3>这是我的 one 组件</h3></div>'
}
}
})html
<div id="app">
<one></one>
<one></one>
</div>易错点:没注册就在视图里用,会提示找不到组件、建议注册;注册了但没使用不会报错;组件里 template 或 render 二选一,没定义会提示挂载组件失败。组件可以多次使用。当前演示环境里自闭合写法要留意,初学先用成对标签更稳;转写也说明后续脚手架中自闭合没问题。
组件命名:大小写与短横线规则
为什么需要:HTML 解析对大小写不敏感,组件名写错大小写或多单词分隔方式,视图就不渲染。
怎么想:转写调试出的规则是:组件名小写,视图中小写可以直接渲染;组件名首字母大写、其他小写,视图中大写或小写都可以;多单词驼峰命名在视图中要用横线分割;全大写通常不识别。
怎么做:建议两种实践:一是首字母大写,如 App、Chart;二是全小写短横线,如 my-comment。驼峰 commentOn 在视图中写成 comment-on。如果定义时写横线,属性名要加引号:
js
components: {
'my-comment': { template: '<div>my comment</div>' }
}易错点:commentOn 直接在视图写 commentOn 可能找不到;写成 comment-on 才行。全大写如 THREE 在转写演示中不被识别。Vue 还建议组件名尽量多个单词,不建议只写一个单词。
组件嵌套:在谁的 components 里注册,就只能在谁里面用
为什么需要:一个组件里还要包含其他组件,就要做组件嵌套,也叫组合组件。
怎么想:转写里创建 App 组件,再想在 App 的模板里使用 chart。这时 chart 不能只注册在外层实例的 components 里,而要在 App 组件自己的 components 里注册。这样 App 是父组件,chart 是子组件。
怎么做:
js
new Vue({
el: '#app',
components: {
App: {
template: '<div><h3>APP 组件</h3><chart></chart></div>',
components: {
chart: {
template: '<div><h3>chart 组件</h3></div>'
}
}
}
}
})html
<div id="app">
<App></App>
</div>易错点:在实例 components 里声明 chart,却在 App 模板里用,会报找不到组件;因为该 components 声明的组件只允许在所在实例/组件中使用。把 chart 放进 App 的 components 后,嵌套才成立。
用调试反推规则
为什么需要:组件规则多,靠背容易混;转写里大量通过控制台报错反推规则。
怎么想:先看“有没有注册”,再看“template/render 有没有定义”,再看“模板是否单根”,最后看“命名是否被识别”。
怎么做:写完组件先刷新看控制台;未注册会提示找不到组件;template 或 render 未定义会提示挂载失败;多根元素会报模板编译错误;命名不对会提示没有找到组件。
易错点:警告不等于异常,但会提示问题;模板字符串要注意反引号和引号;script 模板缺 type 会导致解析问题。把这些调试顺序记住,比死记规则更稳。
先明确:模板解决什么问题
为什么需要:Vue 要让数据驱动视图,就必须先声明“视图长什么样”。模板就是视图结构的声明,它最终会被编译成渲染内容。
怎么想:el 只是告诉 Vue 挂载到哪个元素;template 才是真正决定渲染结构的配置。转写稿里反复强调:模板不是随便写一段 HTML,它有自己的规则。
怎么做:在实例配置里写 template,例如:
html
<div id="app"></div>
<script>
new Vue({
el: '#app',
data: { a: 'A', b: 'B' },
template: `<div>
<p>{{ a }}</p>
<p>{{ b }}</p>
</div>`
})
</script>易错点:不要把 el 和 template 混为一谈。el 指定挂载位置,template 指定要渲染的内容;一旦写了 template,页面表现会和只写 el 不一样。
特点一:会替换
为什么需要:理解“替换”才能知道 el 命中的原元素会不会保留。
怎么想:转写稿中的调试很直接:原来 #app 里有内容,写了 template 后刷新,原内容消失,检查 body 只剩模板渲染出的结构。说明模板内容替换了 el 所指定的挂载元素。
怎么做:保留上面的代码,把 template 写成 '<div>模板内容</div>',刷新后观察 body。
易错点:不是“插入到 #app 里面”,而是替换 el 命中的元素。原 #app 上的内容和结构不要指望保留。转写稿里也提醒:el 不是没用,它可以指定“替换哪一块”。
特点二:只能有一个根标签
为什么需要:Vue 编译模板时需要明确的根节点,多个同级根标签会让编译器无法确定根。
怎么想:转写稿中把模板写成两个同级 div 后,控制台报模板编译错误,提示只能有一个根元素;需要多个分支时可用 v-if 等条件指令,但最终仍只渲染一个根。
怎么做:正确写法是用一个外层元素包住:
js
template: `<div>
<h3>标题</h3>
<p>内容</p>
</div>`错误写法是:
js
template: `<div>一</div>
<div>二</div>`易错点:多个同级标签会报错;注释、文本节点也不要随意放在根层级。组件模板同样受这个限制,转写稿在局部组件里也出现了“模板只能有一个 root”的报错。
三种模板写法
第一种:字符串模板
为什么需要:适合短模板,写起来直接。
怎么做:template: '<div>...</div>',长一点可用反引号。
易错点:模板长了可读性差,引号容易写错。
第二种:script type="text/x-template"
为什么需要:模板较长时,把结构从 JS 里拿出来,阅读更清楚。
怎么做:
html
<script type="text/x-template" id="tp">
<div>
<h3>标题</h3>
<p>{{ a }}</p>
</div>
</script>
<script>
new Vue({
el: '#app',
data: { a: 'A' },
template: '#tp'
})
</script>易错点:type 必须写成 text/x-template,否则会被当脚本解析;id 要和 template: '#tp' 对上,不引用就不生效。
第三种:template 标签
为什么需要:结构更直观,也更常用于组织较长模板。
怎么做:
html
<template id="tp">
<div>
<h3>标题</h3>
<p>{{ a }}</p>
</div>
</template>
<script>
new Vue({
el: '#app',
data: { a: 'A' },
template: '#tp'
})
</script>易错点:template 标签本身不直接渲染,必须通过 template: '#id' 指定;同样只能有一个根标签。
调试过程与组件铺垫
转写稿的调试路径是:先写 template,刷新看原 #app 是否被替换;再故意写多个根标签,打开控制台看模板编译错误;最后换 script 模板和 template 标签,确认 id 与 template 选项对应后才生效。
这些规则也是学习组件的基础:局部组件通过 components 注册,组件里的 template 决定组件渲染内容,并且组件模板也只能有一个根标签。例如:
js
new Vue({
el: '#app',
components: {
one: {
template: `<div><h3>one 组件</h3></div>`
}
}
})使用时写 <one></one>。易错点:组件名和标签名要注意大小写与短横线规则;模板不引用、根标签不唯一,都会导致不渲染或编译报错。
先看现象:组件不注册就识别不了
局部组件解决的是“把页面拆成零件”的问题。转写稿里先用一个最小例子调试:在 #app 中写 <one></one>,但没有注册组件,控制台会提示找不到组件,并建议注册;注册 one 后,又提示 template 或 render 未定义;补上 template 后才正常渲染。这个过程说明:自定义标签必须先注册,且要提供模板内容。
为什么需要局部组件?一是把页面拆成若干组件,阅读和维护更清晰;二是方便团队协作,不同人负责不同组件;三是通用组件可在多个页面复用。怎么想?组件可以理解为“自定义标签”,局部注册只作用于当前 Vue 实例或当前父组件。怎么做?在 new Vue 的配置对象中写 components,属性名就是组件名,属性值是一个对象,对象里用 template 指定渲染内容。易错点:components 是复数;组件值是对象;模板必须能正常解析。
注册位置决定使用范围
局部组件的关键在“局部”二字。转写稿中演示:在实例的 components 里注册了 APP 和 char,APP 可以在 #app 模板中使用;但 char 不能直接写进 APP 的模板里,因为 char 注册在外层实例,而 APP 组件内部没有注册它。若想让 char 成为 APP 的子组件,就要在 APP 组件的配置对象里再写 components: { char: {...} }。这时 APP 是父组件,char 是子组件,形成组件嵌套。易错点:在哪个 components 中声明,就只能在对应实例或对应父组件模板中使用;不要以为外层注册了,所有子组件都能直接用。
组件模板的三种写法
本节代码保留了三种模板写法:一是字符串模板,如 one 直接用反引号写 <div>...</div>;二是 script type="x/template" 加 id,如 Two 使用 template:"#two";三是 <template id="three">,如 three 使用 template:"#three"。转写稿说明:script 方式要加 type="x/template",否则里面的 HTML 会被当成脚本解析而出错;template 标签方式更直观。易错点:无论哪种模板,模板内容只能有一个根元素,多个同级元素会报编译错误;模板内容会替换组件标签所在位置。
命名规则与使用建议
转写稿反复调试了大小写:组件名全小写,视图中全小写即可;组件名首字母大写、其余小写,视图中首字母大小写都可;驼峰命名在视图中要用 - 分割,例如 CommentOn 写成 <comment-on>;全部大写通常不识别。定义带横线的名字如 "my-com" 时要加引号。建议采用两种形式:首字母大写;或全小写、单词间用 -。更稳妥的是定义用大驼峰,使用时全小写加连字符。易错点:不要把组件名写成多个大写字母连用;模板中标签名要和注册名对应。
完整示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<h3>学习组件</h3>
<one></one>
<hr/>
<Two></Two>
<Two></Two>
<Two></Two>
<Two></Two>
<Two></Two>
<hr/>
<three></three>
<hr/>
<comment-on></comment-on>
<hr/>
<my-com/>
</div>
</body>
<script type="x/template" id="two">
<div>
<h3>two组件</h3>
</div>
</script>
<template id="three">
<div>
<h3>three组件</h3>
</div>
</template>
<script>
// 在Vue中声明组件(注册组件),需要在配置对象中设置components属性,属性的名字即是组件的名字
// 注意:
// 1- 如果组件名字小写,在视图中小写即可实现渲染
// 2- 如果组件名字首字母大写,其它小写,那么视图中可以实现渲染(首字母可大写,也可小写)
// 3- 如果使用驼峰命名,在视图中渲染需要用-进行分割多个单词。
// 建议:
// 形式一:首字母大写
// 形式二:全部小写,单词之间用-分割(建议)
// 形式三:定义组件采用大驼峰命名,使用时全部小写,岮峰之间使用-
new Vue({
el:"#app",
components:{
// 注册了一个名字为one的组件
one:{
template:`
<div>
<h3>one组件</h3>
</div>
`
},
Two:{
template:"#two"
},
three:{
template:"#three"
},
CommentOn:{
template:`
<div>
<h3>CommentOn</h3>
</div>
`
},
"my-com":{
template:`
<h3>myCom组件</h3>
`
}
}
})
</script>
</html>运行后检查:Two 可重复使用多次;comment-on 对应 CommentOn;my-com 因定义名带横线需加引号。若控制台报“模板只能有一个根元素”,先检查模板是否多个同级标签;若报组件未注册,先检查 components 拼写、注册位置和标签名大小写。
先看目标:为什么需要组件嵌套
组件可以理解成页面里的“零件”:头部、列表、分页、弹窗都可以拆成组件。转写稿里反复强调,组件嵌套也叫“组合组件”,就是一个组件内部还能包含其他组件。这样做的原因很直接:页面复杂后,如果把所有逻辑都堆在一个 Vue 实例里,阅读性差、维护困难,也不方便团队协作。把页面拆成 App、Child 这样的父子结构,父组件负责整体布局,子组件负责局部内容,职责更清楚。
怎么想:先确定“谁包含谁”。本节例子里,App 是最外层组件,App 的模板中要出现 Child,所以 App 是父组件,Child 是子组件。怎么做:子组件必须注册在“使用它的那个父组件”的 components 里,而不是随便注册在根实例里。易错点:不要把“根实例的 components”和“组件内部的 components”混为一谈,作用域不同。
局部组件注册的作用域:components 写在哪,组件就能在哪用
为什么需要先讲作用域?因为嵌套失败最常见的原因不是模板写错,而是组件注册层级放错。转写稿里做了对比:在根实例 components 中声明的 App,只允许在 Vue 实例挂载的 #app 范围内使用;在 App 组件自己的 components 中声明的 Child,只允许在 App 组件内部使用。
怎么想:看到模板里写了 <Child></Child>,就要问一句“这个 Child 是在哪个 components 里注册的?”如果只把 Child 写在根实例的 components 里,App 的模板并不认识它。怎么做:把 Child 放进 App 的 components。调试时可以先故意把 Child 从 App 的 components 挪到根实例,刷新后控制台会提示找不到/未注册组件;再放回 App 内部,页面恢复。易错点:components 是复数;注册的是“组件名: 配置对象”;不要以为写在根实例里就全局可用。
按笔记搭出父子嵌套:App 包 Child
下面保留本节笔记代码。这里 App 的 template 使用反引号字符串模板,里面写了 <Child></Child>;Child 在 App 的 components 中声明,因此能在 App 模板中渲染。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<App></App>
</div>
</body>
<script>
new Vue({
el: "#app",
// 在该components属性中声明的组件只允许在Vue实例中使用
components: {
// 声明了一个名字为App的组件
App: {
// 指定组件渲染内容
template: `
<div>
<h3>App组件</h3>
<Child></Child>
</div>
`,
// 在该components属性中声明的组件只允许在组件App中使用
components: {
Child: {
template: `
<div>
<h3>Child</h3>
</div>
`
}
}
}
}
})
</script>
</html>怎么想:渲染 App 时,Vue 解析到 <Child>,会到 App 的 components 里找 Child;找到后用 Child 的 template 替换 <Child> 位置。怎么做:先写 App 的模板和 Child 的模板,再确认 Child 注册在 App 内部。易错点:组件模板只能有一个根元素,所以 App 和 Child 的 template 都用一个 <div> 包住;不要写成多个同级根标签。组件名建议首字母大写或使用 kebab-case;如果模板直接写在 DOM 里,HTML 不区分大小写,更推荐 <app>、<child> 这种写法。
调试过程:从“找不到组件”到正确嵌套
转写稿里的调试思路很值得照做:先在 App 模板中写 <Child></Child>,但 Child 没有注册在 App 的 components 中,页面不显示 Child,控制台提示找不到组件/未注册。此时不要急着改模板,而是检查 components 的层级:Child 应该写在 App 的配置对象里,而不是只写在根实例的 components 里。修正后刷新,页面出现“App组件”和“Child”,说明 App 成功包住了 Child。
怎么想:父子关系不是靠标签位置“自动成立”,而是靠“父组件模板中使用子组件 + 子组件在父组件 components 中注册”共同成立。怎么做:按“根实例注册 App → App 模板使用 Child → App 的 components 注册 Child”的顺序检查。易错点:不要把 Child 同时注册在根实例和 App 里造成混淆;不要漏写 template;不要把 template 拼成 templete。后续学习 props 时,父传子、子传父也都建立在这种嵌套关系上。练习时可以再给 Child 加一个 Grandson,并注册在 Child 的 components 中,验证“谁使用,谁注册”的规则。
课堂画面






代码示例
示例 1:模板的两种写法(会替换 / 只能一个根标签)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<!-- el 挂载元素里的原有内容会被 template 整体替换掉 -->
<div id="app">
<h3>这里面原来的内容会被模板替换掉</h3>
</div>
<!-- 写法二:用 script 标签(或 template 标签)包裹模板内容,再通过选择器引用 -->
<script type="x/template" id="tp">
<div>
<h3>这是 tp 模板</h3>
<p>{{a}},{{b}}</p>
</div>
</script>
</body>
<script>
new Vue({
el: "#app",
data: {
a: "A数据",
b: "B数据"
},
// 写法一:字符串模板(推荐用 ES6 模板字符串,阅读性好)
// template: `
// <div>
// <h3>字符串模板</h3>
// <p>{{a}},{{b}}</p>
// </div>
// `
// 写法二:指定 id 为 tp 的 script 标签包裹的内容作为模板
template: "#tp"
})
</script>
</html>要点:
template的作用是替换el所指定的挂载元素的内容,页面里原本写的<h3>会被整个换掉。- 模板内容只能有一个根标签。写成两个同级
<div>会直接报模板编译错误(演示时控制台报的就是“编译 / 模板错误”)。 template的值可以是字符串,也可以是选择器("#tp"),选择器指向script type="x/template"或<template>包裹的内容。- 用
script包裹时必须写type="x/template"(或非 JS 的 type),否则浏览器会把它当 JS 解析,出现语法错误。 - 模板里依然可以使用实例
data中的数据({{a}}、{{b}})。
示例 2:定义局部组件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<h3>学习组件</h3>
<one></one>
<hr/>
<Two></Two>
<Two></Two>
<Two></Two>
<Two></Two>
<Two></Two>
<hr/>
<three></three>
<hr/>
<comment-on></comment-on>
<hr/>
<my-com/>
</div>
</body>
<script type="x/template" id="two">
<div>
<h3>two组件</h3>
</div>
</script>
<template id="three">
<div>
<h3>three组件</h3>
</div>
</template>
<script>
// 在Vue中声明组件(注册组件),需要在配置对象中设置components属性,属性的名字即是组件的名字
// 注意:
// 1- 如果组件名字小写,在视图中小写即可实现渲染
// 2- 如果组件名字首字母大写,其它小写,那么视图中可以实现渲染(首字母可大写,也可小写)
// 3- 如果使用驼峰命名,在视图中渲染需要用-进行分割多个单词。
// 建议:
// 形式一:首字母大写
// 形式二:全部小写,单词之间用-分割(建议)
// 形式三:定义组件采用大驼峰命名,使用时全部小写,驼峰之间使用-
new Vue({
el: "#app",
components: {
// 注册了一个名字为one的组件
one: {
template: `
<div>
<h3>one组件</h3>
</div>
`
},
Two: {
template: "#two"
},
three: {
template: "#three"
},
CommentOn: {
template: `
<div>
<h3>CommentOn</h3>
</div>
`
},
"my-com": {
template: `
<h3>myCom组件</h3>
`
}
}
})
</script>
</html>要点:
- 注册组件写在配置对象的
components里,属性名就是组件名,属性值是一个对象,对象里的template决定组件渲染的内容。 - 组件里的
template和实例上的template作用不同:实例的template替换el挂载元素的内容;组件的template替换的是视图里那个组件标签。 - 命名规则易错点:小写名 → 视图写小写;首字母大写其余小写 → 视图大写小写都能渲染;驼峰(如
CommentOn)→ 视图必须写成comment-on,写CommentOn找不到组件。 - 组件名里带横线(
"my-com")时必须加引号,不加引号是非法标识符,会报错。 - 组件可以重复使用(
<Two></Two>写了多次就渲染多次)。 - 每个组件的
template同样只能有一个根标签。
示例 3:组件嵌套
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<App></App>
</div>
</body>
<script>
new Vue({
el: "#app",
// 在该components属性中声明的组件只允许在Vue实例中使用
components: {
// 声明了一个名字为App的组件
App: {
// 指定组件渲染内容
template: `
<div>
<h3>App组件</h3>
<Child></Child>
</div>
`,
// 在该components属性中声明的组件只允许在组件App中使用
components: {
Child: {
template: `
<div>
<h3>Child</h3>
</div>
`
}
}
}
}
})
</script>
</html>要点:
- 组件内部还可以再写
components,实现组件套组件;此时App是父组件,Child是子组件。 - 作用域易错点:
components里声明的组件只能在它所在的那个实例 / 组件内部使用。把<Child></Child>写到外层#app的模板里会报“没有找到组件”,必须放在App自己的template里。 - 子组件要先在父组件的
components中注册,才能在父组件的template中使用,顺序不能反。 - 嵌套时每一层的
template依旧只能有一个根标签(这里都是<div>包住h3和子组件标签)。 - 视图里
<App></App>首字母大写可以渲染,按建议也可以写成小写形式使用。
常见错误
- 现象:组件模板写两个同级根标签,控制台报模板编译错误或只渲染一部分。→ 原因:组件
template只能有一个根元素。→ 修法:外层包一个div,或通过条件指令保证最终只渲染一个根节点。 - 现象:视图里写了
<CommentOn>或<commentOn>,组件不渲染并提示未注册。→ 原因:HTML 标签不保留驼峰语义,驼峰组件名使用时需转短横线。→ 修法:改为<comment-on>,或统一采用“全小写+短横线”命名。 - 现象:子组件已在某个组件里注册,却在根实例或兄弟组件中使用时报未找到组件。→ 原因:
components注册有作用域,只对当前实例/当前组件模板生效。→ 修法:在真正使用它的父级components中注册,或按需改为全局组件。 - 现象:
template: "#two"不生效,页面空白或模板内容被当普通 DOM。→ 原因:模板容器id不匹配,或script未设置type="x/template"。→ 修法:核对id与选择器一致,script加type="x/template",template标签用id并写#id。 - 现象:组件模板字符串换行后报语法错误。→ 原因:普通引号不能跨行,模板中引号嵌套也易冲突。→ 修法:改用反引号模板字符串,或抽离到
script/template模板容器中。
练习
- 在根实例中注册
one、two、three三个局部组件,分别用字符串模板、script type="x/template"、template标签实现,并在页面各渲染一次。验收:控制台无模板编译错误。 - 给出一个含两个同级
div的组件模板,将其改为合法单根模板,并说明修改点。验收:组件正常渲染且只保留一个根节点。 - 实现
App -> Child嵌套:根实例只注册App,Child只在App的components中注册。验收:App内可渲染Child,根实例直接写Child不生效。 - 将组件名
CommentOn分别按“首字母大写使用”和“短横线使用”两种方式接入页面,记录哪些写法可渲染。验收:能总结出团队统一命名方案。 - 用全小写短横线命名
my-com定义一个组件,并在同一页面复用 3 次。验收:3 次均渲染,且无命名警告。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 复习计算属性 | 12:27 |
| 2 | 复习async | 18:59 |
| 3 | 模板-字符串 | 17:01 |
| 4 | 模板-script-template | 05:02 |
| 5 | 定义局部组件 | 18:18 |
| 6 | 组件嵌套 | 09:09 |
小结
- 局部组件的关键不是“写了
components”,而是“注册在哪个父级、就在哪个作用域可用”。 - 组件模板先保证单根,再谈样式和逻辑;多根是高频编译错误来源。
- 组件命名优先统一为“全小写+短横线”,可规避大部分大小写与驼峰转写问题。
- 组件嵌套先画父子结构,再决定每个子组件注册在哪一层。
- 模板抽离优先用
script/template + id,比长字符串更利于阅读和排错。 - 排错顺序建议:看控制台错误类型 → 查组件是否注册 → 查标签名是否匹配 → 查模板选择器与根节点。
- 组件拆分应服务于复用、可读性和协作,不要为了拆而拆。