Vue2 - 组件
1. 对组件的理解
以前我们做前端项目的时候,比如一个管理系统,有顶部,内容区,底部,我们都是将页面的结构放到一个HTML结构中,然后js文件一般会模块化分为多个,分别引入,然后CSS也是多个,引入。
但是这样就会存在一个问题,比如说我现在要做第二个管理系统了,同样有顶部、底部,只是内容区不一样,那么我一般会复制之前的代码,CSS文件,js相关文件,然后来做,显然是十分麻烦的,而且当多个项目都引入了相同的CSS、js之后,不仅管理混乱,而且一处CSS文件修改了,凡是引入过它的页面样式都会导致修改,那么有没有什么办法来解决这个问题呢?
这就要引入Vue组件的概念了,有了Vue组件之后,我们只需要对于重复的内容引入组件即可,注意,这里可不是复制代码实现的,而是引入组件实现的,每一个组件中都有自己的HTML、CSS、JS。极大提高了服用的效果,而且管理起来非常清晰。
组件的定义——实现应用中局部功能代码和资源的集合
其中Vue的组件中,又分为单文件组件和非单文件组件
补充:
在实际开发中,往往真个系统就只有一个vm实例,其中vm实例下管理着很多的组件,其中,组件又是可以嵌套组件的。
关系图:
2. 非单文件组件
定义:一个文件中包含了n个组件
使用组件的步骤:
- 创建组件
- 注册组件
- 使用组件
例如下面的代码:
<div id="root">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<!--<button @click="showName">点我提示学校名</button>-->
<hr/>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
schoolName: 'B站大学',
address: 'www.bilibili.com',
studentName: '念心卓',
age: 18
}
})
</script>
以前我们写代码就是这样,整个页面使用一个HTML来写。
现在我们引入非单文件组件:
<div id="root">
<school></school> <!--4. 使用组件-->
<!--<button @click="showName">点我提示学校名</button>-->
<hr/>
<student></student><!--4. 使用组件-->
</div>
<script type="text/javascript">
Vue.config.productionTip = false
//1. 定义school组件
const school = Vue.extend({
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
schoolName: 'B站大学',
address: 'www.bilibili.com'
}
}
});
//2. 定义student组件
const student = Vue.extend({
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentName: '念心卓',
age: 18
}
}
});
new Vue({
el: '#root',
//3. 使用components配置来注册组件
components:{
school,
student
}
})
</script>
对应的页面也是一样的。
仔细观察我的上述代码:
首先要定义组件
定义组件的时候,需要使用extend关键字来定义,其本身有拓展的意思。
Vue.extend({ //配置对象 })
配置组件内的数据
比如基本的页面的HTML结构,使用
template
配置项来配置。注意:
- 这里template的内容最好使用反引号包起来。
- 模板里面的内容,必须要有一个根标签,否则报错,禁止使用
<template>
作为根标签
其次,对于每个组件的数据,必须使用data函数来配置,不允许使用以前的data对象,因为可能多个页面使用同一个组件的时候,如果数据是使用的对象的方式来定义的话,那么如果其中一个页面的数据发生了变化,会连带其他页面的数据也跟着变化的,因为对象是引用类型。所以我们一般把组件中的数据配置为data函数,然后返回值是一个对象形式即可:
data(){ return 对象 }
注册组件
注册组件使用
components
关键字:new Vue({ //其他配置... components:{ //定义组件的名称,可以是key-value形式,如果你的key和value想等,可以写简写形式,如上我的代码 } })
使用组件
因为你定义好了组件,并且组成到了vm上,比如上面我定义的student组件,那么你使用的时候,直接写student标签即可。
注意:这里虽然标签你的student第一个字母虽然是小写,但是在Vue开发者工具中却是大写:
3. 组件的几个注意点
3.1 组件名
组件由一个单词组成:
- 第一种写法(首字母小写):school
- 第一种写法(首字母大写):School
组件由多个单词组成:
- 第一种写法(
kebab-case
命名):my-school - 第二种写法(
CamelCase
命名):MySchool (需要Vue脚手架支持)
3.2 组件标签
第一种写法 - 双标签:<school></school>
第二种写法 - 自闭合:<school/>
备注:不用使用脚手架时,
<school/>
会导致后续组件不能渲染。
3.3 简写
之前我们定义一个组件:
const xxx = Vue.extend({
//配置对象
})
简写:
const xxx = {
//配置对象
}
4. 组件嵌套
从字面意思上也不难理解,组件之间是可以嵌套的:
<!-- 准备好一个容器-->
<div id="root">
<school></school>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//定义student组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
name:'念心卓',
age:18
}
}
})
//定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
//注册组件(局部)
components:{
student //注意,这里必须注册要嵌套的组件
}
})
const vm = new Vue({
el:'#root',
components: {
school
}
})
不过,学到后面组件由很多的时候,我们一般不会直接用vm来接管众多的组件,而是由一个新的组件APP来接管,然后vm接管APP,所以,APP是一人之下万人之上的组件。
<!-- 准备好一个容器-->
<div id="root">
<app></app>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//定义student组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
name:'念心卓',
age:18
}
}
})
//定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
//注册组件(局部)
components:{
student
}
})
//定义hello组件
const hello = Vue.extend({
template:`<h1>{{msg}}</h1>`,
data(){
return {
msg:'Vue学习中...'
}
}
})
//定义app组件
const app = Vue.extend({
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
//在app组件中,在接管众多组件
components:{
school,
hello
}
})
const vm = new Vue({
el:'#root',
components: {
app //vm只需接管app组件即可
}
})
可见上面的代码和效果都是由App来接管众多组件,然后由vm来接管app。
5. VueComponent
首先VueComponent
是一个构造函数。
之前我们定义组件的时候写了这么一部分代码:
//定义student组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
name:'念心卓',
age:18
}
}
})
现在我们输出这个组件对象来看看:
console.log(student)
惊奇的发现,输出它的时候,并不是一个对象形式,而是一个构造函数,那这个构造函数在哪里呢,我们来看看源码:
可见,整个Vue的JavaScript的源码中,只有在Vue.extend
函数中出现了这个构造函数。
所以,看到这我们明白:xxx组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
当我们定义好组件,并且注册好之后,我们要去使用他的时候,往往是这样:<xxxx</xxxx>
的方式来使用,所以,当你使用的时候,Vue又帮你做了一件事:Vue解析时会帮我们创建xxxx组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
特别注意:每次调用Vue.extend,也就是来使用这个组件的时候(
<xxxx</xxxx>
),返回的都是一个全新的VueComponent!!!!//定义student组件 const student = Vue.extend({ name:'student', template:` <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { name:'念心卓', age:18 } } }) console.log(student) //定义hello组件 const hello = Vue.extend({ template:`<h1>{{msg}}</h1>`, data(){ return { msg:'Vue学习中...' } } }) console.log(hello) console.log(hello === student) //false
所以,虽然输出看上去像同一个VueComponent,但是实际上却是两个完全不同的VueComponent。
现在我们还有一个问题,关于this指向:
<!-- 准备好一个容器-->
<div id="root">
<app></app>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//定义student组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showThis">点我输出this</button>
</div>
`,
data(){
return {
name:'念心卓',
age:18
}
},
methods:{
showThis(){
console.log(this)
}
}
})
//定义app组件
const app = Vue.extend({
template:`
<div>
<student></student>
</div>
`,
components:{
student
}
})
const vm = new Vue({
el:'#root',
components: {
app
}
})
</script>
可见,在组件中,this指的就是VueComponent,并且仔细查看其里面的内容,发现和Vue实例vm实际上内容基本上一致。
所以:
- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
- new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
VueComponent的实例对象,以后简称为:组件实例对象。Vue的实例对象,以后简称vm。
6. 一个重要的内置关系
之前我说过,VueComponent其实和Vue实例vm差不多,只是vm配置属性中,指明了为哪个容器服务,而组件却不可以,因为组件本身就是需要复用的。但是其中却包含了一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。让它做一个兜底的选择,目的就是如果在组件实例上找数据,如果组件实例上找不到,就再去Vue实例上找数据,如果还找不到,那就却是没办法了,只有在Object去找了。
看到这里,你是不是有点熟悉感觉,没错,就是原型链。
忘记原型的读者,可以去JavaScript笔记中查找看看。
简单解释:
- 每个构造函数身上都有一个prototype属性,并且这个属性指向的是原型对象
- 每一个实例对象身上都有一个
__proto__
属性,并且这个属性指向prototype的原型对象。__proto__
是JavaScript非标准属性,所以打印的时候显示出来的都是[[prototype]]
7. 单文件组件
有了上面几小节的知识铺垫,单文件组件也就可以来聊聊了。
从这里开始,我们将使用一个全新的文件格式.vue
来表示一个单文件,每一个.vue
文件中,包含3个部分:
- template:里面写以前的html结构,最终解析过后的template标签是不显示到页面上的,不过还是要有根标签。
- script:里面写以前的JavaScript代码
- style:里面写以前的css样式
<template>
<!-- 里面写以前的html结构-->
</template>
<script>
/*里面写以前的JavaScript代码*/
</script>
<style>
/*里面写以前的css样式*/
</style>
并且,对于单文件组件命名,如果是一个单词,我们一般首字母大写,如果是多个单词组成,我们一般是大驼峰。
下面改造之前出现过的代码:
Student.vue
文件:
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
<style>
/*里面写以前的css样式*/
</style>
对于上面script标签中的代码,export语法是ES6模块化中的语法,不懂的可以去补一下基础,并且上面组件也是用到简写形式,原本是这样的:
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
const student = Vue.extend({
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
})
</script>
然后你必须要把这个组件给暴露出去,让外面通过import方式引入,所以就有了export关键词:
export const student = Vue.extend({
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
})
或者:
const student = Vue.extend({
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
});
export default student;
这里的暴露方式有多种,可以自行去了解模块化。
只不过我上面写了一个最简单的形式。
School.vue
文件:
<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
name: 'B站大学',
address: www.bilibili.com
}
},
methods: {
showName() {
alert(this.name)
}
}
}
</script>
<style>
.demo {
background-color: orange;
}
</style>
组件有了之后,我们还要一个一人之下的组件App.vue:
<template>
<student></student>
<school></school>
</template>
<script>
//要先引入组件才能够使用
import Student from "./Student.vue";
import School from "./School.vue";
export default {
name: "App",
components:{ //注册组件
Student,
School
}
}
</script>
<style >
</style>
现在App组件也有了,现在我们需要一个大哥了Vue实例,申明Vue实例我们一般使用main.js
文化:
import App from "./App.vue";
new Vue({
el:'#root',
template:`
<App></App>
`,
components:{
App
}
})
现在Vue也有了,我们需要一个容器,让Vue实例来为我们服务,一般使用index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>练习一下单文件组件的语法</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
<script type="text/javascript" src="../Vue_js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
注意:我们必须先使用Vue的开发文件,之后才能够引入main.js。
目录结构:
这就是Vue开发的第一步。
但是你运行代码一看,发现报错:
表面浏览器不能直接支持ES6的模块化语法,这怎么办呢?
这就要说到下一章了 - Vue脚手架