Vue.js 实例和组件
在使用 Vue
开发的每个 Web
应用中,大多数是一个单页应用(Single Page Application, SPA),就是只有一个 Web
页面的应用,它加载单个 HTML
页面,并在用户与应用程序交互时动态地更新该页面的 DOM
内容。下面只讨论单页应用的场合。
每一个单页 Vue
应用都需要从一个 Vue
实例开始。每一个 Vue
应用都由若干个 Vue
实例或组件组成。
创建 Vue.js 实例
首先新建 index.html
,并通过 <script>
的方式来导入 Vue.js
,然后创建一个 Vue
实例,如示例代码2-1-1所示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no" />
<title>Vue实例</title>
<script src="https://unpkg.com/vue@3.2.28/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
{{msg}}
</div>
<script type="text/javascript">
const app = Vue.createApp({
data(){
return {
msg: "hello world",
}
}
})
app.mount('#app')
</script>
</body>
</html>
通过 createApp
方法可以创建一个 Vue
应用实例,一个 Vue
实例若想和页面上的 DOM
渲染进行挂载,就需要调用 mount
方法,参数传递 id
选择器,挂载之后这个 id
选择器对应的 DOM
会被 Vue
实例接管,当然也可以用 class
选择器。需要注意,如果是通过 class
选择器找到多个 DOM
元素,则只会选取第一个。
data
属性表示数据,用于接收一个对象。也就是说,如果 Vue
实例需要操作页面 DOM
里的数据,可以通过 data
来控制,需要在 HTML 代码中写差值表达式 {{}}
,然后获取 data
中的数据(如 {{msg}}
会显示成 hello world
)。接着可以在 Vue
实例中通过 this.xxx
使用 data
中定义的值。需要注意在 Vue 3 中,data
需要是一个函数 function
,并返回对象。在浏览器中打开 index.html
来查看,效果如图2-1所示。

createApp
方法传递的参数是根组件(也可以叫作根实例)的配置,返回的对象叫作 Vue
应用实例,当应用实例调用 mount
方法后返回的对象叫作根组件实例,一个 Vue
应用由若干个实例组成,准确地说是由一个根实例和若干个子实例(也叫子组件)组成。如果把一个 Vue
应用看作一棵大树,那么称根节点为 Vue
根实例,子节点为 Vue
子组件。当然,一个 Vue
实例还有很多其他的属性和方法,在后续章节中会讲到。
用 component() 方法创建组件
首先,Vue
中每个组件都可以定义自己的名字。下面就新建一个自定义组件,将其放在 Vue
根实例中使用,可使用上一步返回的 Vue
实例 app
调用 component()
方法新建一个组件,如示例代码2-1-2所示。
const app = Vue.createApp({})
// 定义一个名为 button-component 的新组件
app.component('button-component', {
data() {
return {
str: 'btn'
}
},
template: '<button>I am a {{str}}</button>'
})
app.mount('#app')
app.component()
方法的第一个参数是标识这个组件的名字,名为 button-component
,第二个参数是一个对象,这里的 data
必须是一个函数 function
,这个函数返回一个对象。template
定义了一个模板,表示这个组件将会使用这部分 HTML
代码作为其内容,如示例代码2-1-3所示。下面我们来看刚刚定义的 button-component
组件的使用。
<div id="app">
<button-component></button-component>
{{msg}}
</div>
<button-component>
表示用了一个自定义标签来使用组件,其内容保持和组件名一样,这是 Vue
中特有的使用组件的写法。
此外,也可以多次使用 <button-component>
组件,以达到简单的组件复用的效果,如示例代码2-1-4所示。
<div id="app">
<button-component></button-component>
<button-component></button-component>
{{msg}}
</div>
再次运行 index.html
,效果如图2-2所示。

Vue 组件、根组件、实例的区别
在一般情况下,我们使用 createApp
创建的叫作应用(根)实例,然后调用 mount
方法得到的叫作根组件,而使用 app.component()
方法创建的叫作子组件,组件也可以叫作组件实例,概念上它们的区别并不大。一个 Vue
应用由一个根组件和多个子组件组成,它们之间的关系和区别主要是:
-
根组件也是组件,只是根组件需要应用实例挂载之后得到,可以看作是一个实例化的过程。
-
创建子组件需要指定组件的名称,第二个对象参数和创建根组件时基本一致。
-
子组件是可复用的。一个组件被创建好之后,就可以被用在任何地方。所以子组件的
data
属性需要一个函数function
,以保证组件无论被复用了多少次,组件中的data
数据都是相互隔离、互不影响的。
在 |
在一般情况下,Vue
中的组件是相互嵌套的,可以看作是一个树结构,每个组件可以引用多个其他组件,而其他组件又可以引用另外一些组件,但是它们有一个共同的根组件,这就是组件树。
全局组件和局部组件
在 Vue
中,组件又可以分成 全局组件 和 局部组件。之前在代码中直接使用 app.component()
创建的组件为全局组件,全局组件无须特意指定挂载到哪个实例上,可以在需要时直接在组件中使用,但是需要注意的是,全局组件必须在根应用实例挂载前定义才行,否则将无法被使用该根组件的应用找到,就像在 2.1.3 节的代码中,要写在 app.mount('#app')
之前,否则无法找到这个组件。
全局组件可以在任意的 Vue
组件中使用,也就意味着只要注册了全局组件,无论是否被引用,它在整个代码逻辑中都可见。而局部组件则表示指定它被某个组件所引用,或者说局部组件只在当前注册的这个组件中使用。创建局部组件如示例代码2-1-5所示。
// 局部组件
<div id="app">
{{msg}}
<inner-component></inner-component>
</div>
const app = Vue.createApp({
data() {
return {
msg: "hello inner"
}
},
components: { // 可设置多个
'inner-component': {
template: '<h2>inner component</h2>'
}
}
}).mount('#app')
上面的代码中,inner-component
是一个局部组件,它只有一个简单的 template
属性,在使用者的组件中可以通过 components
将局部组件挂载进去,注意这里是 components
(复数),而不是 component
,因为可能有多个局部组件。这个局部组件只能被当前 app
的根组件使用。为了组件复用的效果,也可以将组件单独抽离出来,如示例代码2-1-6所示。
const myComponenta = {
template: '<h2>{{str}}</h2>',
data() {
return {
str: 'inner a'
}
}
}
const app = Vue.createApp({
components: { // 根组件中复用组件
'my-component-a': myComponenta
}
})
app.component('button-component', {
data() {
return {
str: 'btn'
}
},
components: {
'my-component-a': myComponenta
},
template: '<my-component-a></my-component-a>'
})
app.mount('#app')
在上面的代码中定义了局部组件的配置项 myComponenta
,然后在根组件 app
和全局组件 <button-component>
中分别复用了使用配置项 myComponenta
的局部组件 <my-component-a>
。
组件方法和事件的交互操作
在 Vue
中可以使用 methods
为每个组件添加方法,然后可以通过 this.xxx()
来调用。下面通过一个单击事件的交互操作来演示如何使用 methods
,如示例代码2-1-7所示。
<div id="app">
<h2 @click="clickCallback">{{msg}}</h2>
</div>
Vue.createApp({
data(){
return {msg: "hello inner"}
},
methods:{
clickCallback(){
alert("click")
}
}
})
在组件或者实例中,methods
接收一个对象,对象内部可以设置方法,并且可以设置多个。在上面的代码段中,clickCallback
是方法名。
在模板中通过设置 @click="clickCallback"
表示为 <h2>
绑定了一个 click
事件,回调方法是 clickCallback
,当单击发生时,会自动从 methods
中寻找 clickCallback
这个方法,并且触发它。
同理,可以设置另一个方法,同时在 clickCallback
中使用 this.xxx()
去调用,如示例代码2-1-8所示。
Vue.createApp({
data() {
return {msg: "hello inner"}
},
methods:{
clickCallback(){
alert("click")
this.foo()
},
foo(){
alert("foo")
}
}
}).mount("#app")
在了解了组件方法 methods
的用法之后,下面借助 methods
通过一个计数器的例子来演示 Vue
中的事件和 DOM
交互操作的用法,如示例代码 2-1-9 所示。
<div id="counter">
<my-component></my-component>
</div>
const myComponent = {
template: '<h2 @click="clickCallback">点击{{num}}</h2>',
data(){
return {
num: 0
}
},
methods: {
clickCallback(){
this.num++
}
}
}
Vue.createApp({
components:{
myComponent: myComponent
}
}).mount('#counter')
在这段代码中使用了局部组件进行演示,当单击 <h2>
时,会触发 clickCallback
回调方法,在回调方法内对当前 data
中的 num
值进行了加 1
自增,num
通过插值表达式 {{num}}
在页面中显示出来。我们会发现,每单击一次,页面上的 num
就增加 1
,这就是 Vue
中响应式的体现,即当一个对象变化时,能够被实时检测到,并且实时修改结果。只有有了响应式,才能有双向绑定,这也是 Vue
中双向绑定时 Model
影响 DOM
的具体体现。在后续的章节中会讲解 DOM
影响 Model
的情况。
本小节讲解了 Vue
中基本的组件用法,使用了 createApp
、data
、template
、methods
等属性和方法,这些基础内容对我们后续的学习有很大帮助,当然使用插值表达式 {{msg}}
、指令 @click
、生命周期等相关的用法还有很多,后面会进行深入详细的讲解。
单文件组件
Vue.js
的组件化是指每个组件控制一块用户界面的显示和用户的交互操作,每个组件都有自己的职能,代码在自己的模块内互相不影响,这是使用 Vue.js
的一大优势。
在一个有很多组件的项目中,如果想要达到组件复用,就可能需要使用 app.component()
来定义多个全局组件,或者定义多个局部组件,然后在组件中互相调用它们,但是前提是所有组件定义和引用的代码都必须在一个上下文对象中,或者说是写在一个 JavaScript
文件中,维护效率很低,这不符合前端工程化的思想。这样的写法有以下几点不足:
-
全局定义(Global definitions):强制要求每个
component
中的命名不得重复。 -
字符串模板(String templates):缺乏语法高亮显示功能,在 HTML 有多行时,需要用到丑陋的
“\”
或者“+”
来拼接字符串。 -
不支持 CSS(No CSS support):意味着当 HTML 和
JavaScript
组件化时,CSS 只能写在一个文件里,没法突出组件化的优点。 -
没有构建步骤(No build step):在当前比较流行的前端工程化中,如果一个项目没有构建步骤,开发起来将会变得异常麻烦,简单地使用
app.component()
来定义组件是无法集成构建功能的。
文件扩展名为 .vue
的单文件组件(Single File Components, SFC)为以上所有问题提供了解决方法,并且还可以使用 Webpack
或 Rollup
等模块打包工具。该特性带来的好处是,对于项目所需要的众多组件进行文件化管理,再通过压缩工具和基本的封装工具处理之后,最终得到的可能只有一个文件,这极大地减少了对于网络请求多个文件带来的文件缓存或延时问题。
下面是一个单文件组件 index.vue
的例子,如示例代码2-1-10所示。
<template>
<div class="box">
{{msg}}
</div>
</template>
<script>
module.exports = {
name: 'single',
data () {
return {
msg: 'Single File Components'
}
}
}
</script>
<style scoped>
.box {
color: #000;
}
</style>
在上面的代码中,组件的模板代码被抽离到一起,使用 <template>
标签包裹;组件的脚本代码被抽离到一起,使用 <script>
标签包裹;组件的样式代码被抽离到一起,使用 <style>
标签包裹。这使得组件 UI 样式和交互操作的代码可以写在一个文件内,方便了维护和管理。
当然,这个文件是无法被浏览器直接解析的,因而需要通过构建步骤把这些文件编译并打包成浏览器可以识别的 JavaScript 和 CSS,例如 Webpack 的 vue-loader
,对于 <template>
中的代码,会被解析成 Vue
的 render
方法中的虚拟 DOM
对应的 JavaScript 代码,<script>
中的代码会被解析成 Vue
组件的配置对应的 JavaScript
代码,<style>
中的内容会被单独抽离出来,在组件加载时插入 HTML
页面中。
当然,对于 <style>
标签,可以配置一些属性来提供比较实用的功能,scoped
属性表示当前 <style>
中的样式代码只会对当前的单文件组件生效,这样即使多个单文件组件被打包到一起,也不会互相影响。同时,<style>
标签也提供了 lang
属性,可以用来启用 scss
或 less
,代码如下:
<style scoped lang='less'></style>
<style scoped lang='scss'></style>
当 <style>
标签启用了 scoped
后,如果想要在样式代码中写一些样式来影响非当前组件所产生的 DOM
元素,可以采用深度选择器 :deep()
,代码如下:
// 局部组件<aButton>
const aButton = {
template: '<div class="a-button"></div>',
}
<template>
<div class="content">
<aButton />
<a-button>
</template>
<script>
module.exports = {
name: 'single',
components:{
aButton:aButton
}
}
</script>
<style scoped>
.content :deep(.a-button) {
/* ... */
}
</style>
上面的代码中,.a-button
这个 class
的样式可以通过父组件 single
中的 <style>
来设置。
<script>
标签可以标识当前使用的语言引擎,以便进行预处理,最常见的就是在 <script>
中使用 lang
属性来声明 TypeScript,代码如下:
<script lang="ts">
// 使用 TypeScript
</script>
如果想将 *.vue
组件拆分为多个文件,<template>
、<style>
和 <script>
都可以使用 src
属性来引入外部的文件作为语言块,代码如下:
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
注意 src
引入所需遵循的路径解析规则与构建工具(例如 Webpack
模块)一致,即:
-
相对路径需要以
./
开头。 -
可以直接从
node_modules
依赖中引入资源。
直接引入 node_modules
的资源,代码如下:
<!-- 从已安装的 "todomvc-app-css" npm 包中引入文件 -->
<style src="todomvc-app-css/index.css">
最后,对于 <script>
标签,在 Vue 3
中引入了 setup
属性,当配置之后,就相当于可以在 <script>
标签内部直接写 Composition API 中的 setup()
方法中的代码,当然最终还是会在打包时被编译成对应的 JavaScript 代码,但是在开发阶段就显得简洁和便利了,我们会在后面的章节深入讲解 setup()
方法。
正因为 Vue.js
有了单文件组件,才能将其和构建工具(Webpack
等)结合起来,使得 Vue.js
项目不单单是简单的静态资源查看,而是可以集成更多文件预处理功能,这些功能改变了传统的前端开发模式,更能体现出前端工程化的特性,目前大部分 Vue.js
项目都会采用单文件组件。