目录
一、 组件
1.1 了解组件
传统方式编写应用
组件方式编写应用
组件:实现应用中局部功能代码和资源的集合
所有的组件都必须听大哥vm的指挥,并且组件可以产生嵌套
组件可以复用编码,简化项目编码,提高运行效率
模块:一个模块也就是一个JS文件
模块化后可以复用JS,简化JS的编写,提高JS运行效率
模块化:应用中的JS都以模块来编写,那这个应用就是一个模块化的应用
一个JS文件太大,我们可以拆分成许多份,也就是模块化应用
组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
一个组件太大,可以按照功能点拆成许多份,不同的功能点就是一个组件,这样就是一个组件化
Vue中组件有两种形式:
-
非单文件组件
一个文件中包含n个组件
-
单文件组件
一个文件中只包含1个组件。文件名是a.vue而不是a.html,后缀名称发生了改变
1.2 非单文件组件
将下面的形式改成非单文件组件
<!--准备好一个容器-->
<div id="root">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<hr>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
data: {
schoolName:'qilugongyedaxue',
address:'济南',
studentName:'zhangjingqi',
age:18
}
1.2.1 使用步骤
我们可以将学校信息创建为一个组件,将学生信息创建为一个组件
-
创建组件
template是一个模板字符串,在使用template需要有一个根元素,我们一般设置为div标签
//创建school组件
//Vue.extent扩展,创建组件的API
//这个school不是组件名,只是一个中转的变量
const school = Vue.extend({
// 用div包裹,但是这个div最终不会出现在页面上
template: `<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名称</button>
</div> `
,
data() {
return {
schoolName: 'qiludongyedaxue',
address: '济南',
}
},
methods: {
showName() {
alert(this.schoolName)
}
},
})
// 创建student组件
//这个student不是组件名,只是一个中转的变量
const student = Vue.extend({
template: `<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div> `
,
data() {
return {
studentName: '张三',
age: 18
}
}
})
-
局部注册组件
new Vue({
el:'#root',
// 这个地方依然可以写数据,依然可以用
data:{
},
// 注册组件(局部注册)
components:{
// 这个地方是key:value的形式,key是组件的名字 value就是我们上面定义的const,我们上面创建的const不是组件名,是我们中转的变量,下面的key才是组件名
// school:school,
// student:student
// 下面是简写
school,
student
}
)}
-
编写组件标签
<body>
<div id="root">
<!--第三步:编写组件标签 -->
<school></school>
<br><br>
<student></student>
</div>
</body>
我们代码中组件标签开头字母是小写,但是在Vue开发工具中是大写的
1.2.2 效果图
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF8" />
<title>初始vue</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!--第三步:编写组件标签 -->
<school></school>
<br><br>
<student></student>
</div>
</body>
<script type="text/javascript">
//关闭开发环境时的生产提示
Vue.config.productionTip = false
//创建school组件
//Vue.extent扩展,创建组件的API
const school = Vue.extend({
// 用div包裹,但是这个div最终不会出现在页面上
template: `<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名称</button>
</div> `
,
//el:'#root', 如果存在此语句的话,也会报错
// 如果data写成这种形式会报错,只能写成函数的形式
// data:{
// schoolName:'qiludongyedaxue',
// address:'济南',
// },
data() {
return {
schoolName: 'qiludongyedaxue',
address: '济南',
}
},
methods: {
showName() {
alert(this.schoolName)
}
},
})
// 创建student组件
const student = Vue.extend({
template: `<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div> `
,
data() {
return {
studentName: '张三',
age: 18
}
}
})
new Vue({
el: "#root",
//这里面继续写配置也可以
data: {
},
components: {
// 这个地方是key:value的形式,key是组件的名字 value就是我们上面定义的const,我们上面创建的const不是组件名,是我们中转的变量,下面的key才是组件名
// school:school,
// student:student
// 下面是简写
school,
student
}
})
</script>
</html>
1.3 组件嵌套
在下图中,蓝色框和和橙色框中的组件就是嵌套,蓝色框组件嵌套了两个橙色框组件,并且有一个上下级关系
蓝色框称为父组件
橙色框称为子组件
下面完成school组件嵌套student组件
也就是父组件school,子组件student
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 初识vue</title>
<!--引入vue 引入之后,全局就多了一个vue这个构造函数-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<app></app>
</div>
</body>
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip=false
// 创建student组件
const student = Vue.extend({
template:`<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div> `
,
data(){
return{
studentName:'张三',
age:18
}
}
})
// 创建school组件
const school = Vue.extend({
// 用div包裹,但是这个div最终不会出现在页面上
// 嵌套了student组件
template:`<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div> `
,
data(){
return{
schoolName:'qilugongyedaxue',
address:'济南',
}
},
components:{
// 注意!这里嵌套的时候有一个先后关系,一定要先创建student组件
student
}
})
// 创建hello组件
const hello =Vue.extend({
template:`<h1>{{msg}}</h1>`,
data(){
return {
msg:'欢迎来到qilugongyedaxue学习'
}
}
})
// 创建app组件 其他的组件都在这个地方
// 一人之下,万人之上(在vm下)
const app = Vue.extend({
template: `
<div>
<hello></hello>
<school></school>
</div> `
,
components:{
//school组件中嵌套了student组件
school,
hello,
}
})
new Vue({
el:'#root',
//注册组件
components:{
app
}
})
</script>
</html>
我们上面定义的模式是vm组件管理app组件,然后app组件管理其他相关组件
1.4 VueComponent
我们可以输出一下定义的组件
const school = Vue.extend({
// 用div包裹,但是这个div最终不会出现在页面上
// 嵌套了student组件
template:`<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div> `
,
data(){
return{
schoolName:'qilugongyedaxue',
address:'济南',
}
},
components:{
// 注意!这里嵌套的时候有一个先后关系,一定要先创建student组件
student
}
})
console.log('@',school)
发现是一个构造函数,组件的本质就是一个构造函数
-
school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
-
我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象
即Vue帮我们执行的:new VueComponent(options)
-
特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!
school与hello是两个VueComponent,只不过两个长得一样,功能也一样
下面验证即可
console.log('@',school === hello)
或者是通过下面的原码,每次创建组件的时候都是重新定义的一个变量Sub
-
关于this的指向
-
组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数,this指向的均是vc实例对象
-
new Vue配置中
data函数、methods中的函数、watch中的函数、computed中的函数,this指向均是vm实例对象
vc和vm 的功能是一个样子的,也有数据代理
-
VueComponent的实例对象,以后简称vc,也可称为组件实例对象
组件是可复用的Vue实例,所以它们与new Vue 接收相同的选项,例如data、computed、watch、methods以及声明周期钩子等。
仅有的例外是像el这样跟实例特有的选项
vm能通过el决定为哪个容器进行服务,vc不能
二、 非单文件组件总结
2.1 el 定义问题
我们在使用Vue.extent创建组件的时候编写了el:'#root',这种情况是会报错的,
提示el仅仅被使用在实例身上并且使用new关键字调用的时候
const school = Vue.extent({
// 用div包裹,但是这个div最终不会出现在页面上
template:`<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名称</button>
</div> `
,
el:'#root', //如果存在此语句的话,也会报错
// 如果data写成这种形式会报错,只能写成函数的形式
data:{
schoolName:'qilugongyedaxue',
address:'济南',
}
)}
那我们不写el指定根容器,那怎么知道为谁服务?
因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
创建组件的时候从来不说组件为谁服务,组件就是一块砖,哪里需要往哪搬,最终为谁服务看大哥vm的指示
2.2 data函数问题
假如我们在非单文件组件中如下所示定义data,就会报错
data:{
schoolName:'qilugongyedaxue',
address:'济南',
}
data(){
return{
schoolName:'qilugongyedaxue',
address:'济南',
}
}
为什么非要写成一个函数?
是因为对象的引用关系。假如A组件和B组件同时引用了school组件,然后A组件中对schoolName进行修改,那B组件中引用的schoolName也会跟着修改,可能会导致B组件页面渲染发生问题
但是写成一个函数的形式,A组件和B组件之间修改就不会互相影响了。因为我们每次调用data的时候都是返回一个全新的对象
2.3 注册组件方式
组件的注册分为局部注册和全局注册
-
局部注册
这种方式使用的较多
// 创建student组件
const student = Vue.extend({
template: `<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div> `
,
data() {
return {
studentName: '张三',
age: 18
}
}
})
new Vue({
el: "#root",
//这里面继续写配置也可以
data: {
},
components: {
school,
student
}
})
-
全局注册
//1.创建hello组件
const student = Vue.extend({....})
//2.全局注册组件
// 参数1:组件的名字;
// 参数2:传入我们定义的组件
Vue.component('hello',hello)
2.4 内置关系
2.4.1 铺垫知识
无论是函数身上的显示原型属性还是实例对象身上的隐式原型属性,都指向原型对象
//定义一个构造函数
function Demo(){
this.a = 1
this.b = 2
}
// 创建一个Demo实例对象
const d = new Demo()
//Demo.prototype显示原型属性
// 我们通过Demo.prototype已经拿到原型对象了
console.log(Demo.prototype)
//此构造函数所缔造出来的实例对象有一个__proto__属性(前后两个下划线)
//隐式原型属性
console.log(d.__proto__)
程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
//拿到原型对象后往其身上加了一个x属性为99
Demo.prototype.x=99
访问刚刚添加的x属性值
console.log(d.x)
//两种方式都可以
console.log(d.__proto__.x)
Demo自身没有x,就会去隐式里面找,就会找到x
并且下面的结果其实是true
console.log(d.__proto__ === Demo.prototype)
2.4.2 内置关系
VueComponent.prototype.__proto__===Vue.prototype
可以使用下面的代码验证一下我们的结论
<script type="text/javascript">
//关闭开发环境时的生产提示
Vue.config.productionTip = false
//定义组件VueComponent
const school = Vue.extend({
// 用div包裹,但是这个div最终不会出现在页面上
// 嵌套了student组件
template: `<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
</div> `
,
data() {
return {
schoolName: 'qilugongyedaxue',
address: '济南',
}
}
})
console.log(school.prototype.__proto__ === Vue.prototype)//true
new Vue({
el: "#root",
data: {
msg: '你好'
}
})
</script>
下面分析Vue与VueComponent的关系
-
当我们使用下面语句后,Vue实例引入进来
<script type="text/javascript" src="../js/vue.js"></script>
Vue构造函数就位,config、component....是Vue身上的属性和方法
-
Vue身上还有一个prototype属性
Vue的原型对象
之前在学习el两种写法的时候见过$mount函数
其实vm身上是没有$mount函数的,但是顺着隐式原型链__proto__就可以找到$mount函数
vm.$mount('#root')
-
new Vue可以创建Vue原型对象,创建的vm实例一定会有一个隐式原型对象__proto__
-
Vue的原型对象也会有隐式原型属性,并且指向Object的原型对象
实例的原型属性永远指向自己缔造者的原型对象
Vue原型对象的缔造者是Object原型对象
Vue的实例对象的缔造者是Vue的原型对象
-
同理VueComponent函数身上也会有一个prototype,并且指向VueComponent的原型对象
-
假如编写了一个组件标签<school/>,随后Vue就帮我们new了一个VueComponent的实例对象vc
-
vc也是一个实例对象,身上也会有隐式原型属性,并且指向自己的缔造者的原型对象,即指向VueComponent的原型对象
-
同理VueComponent的原型对象也会有隐式原型属性,并且指向自己缔造者的原型对象,但是Vue做了一件事,没有让VueComponent的原型对象的隐式原型属性指向Object的原型对象,而是让VueComponent的原型对象的隐式原型属性指向Vue的原型对象
-
图示总览
总的来说就是,VueComponent的原型对象的原型对象就是Vue的原型对象
为什么要这么折腾一圈呢?
当放在Vue的原型对象一个属性x=99,那vc首先会在自己实例对象身上找,找不到的话就会在隐式原型对象上找,也就是VueComponent的原型对象
继续在VueComponent的原型对象上找,如果找不到的话会在隐式原型对象上找,也就是Vue原型对象,最终找到了
假如说在Vue原型对象上也没有找到,会继续在Object原型对象身上找
2.5 总结
-
Vue中使用组件步骤
-
定义组件(创建组件)
-
注册组件
-
使用组件(写组件标签)
-
如何定义一个组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有差别
区别:
-
el不写。最终所有的组件都要经过一个vm管理,由vm中的el决定服务哪个容器
-
data必须写成函数。避免组件被复用时,数据存在引用关系(比如a,b同时用了data数据,a改了数据,那b得到的数据就是a改了之后的数据)
备注:使用template可以配置组件结构
-
关于组件标签
第一种写法:<school></school>
第二种写法:<school/>
备注:
不使用脚手架时,<school/>会导致后续组建不能渲染
-
关于组件名
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
备注:
1. 组件名尽可能回避HTML中已有的元素名称,例如:h2,H2都不可以
2. 可以使用name配置项指定组件在开发者工具中呈现的名字,如下所示
给组件添加一个name配置项即可,其他地方都不改变
<div id="root">
<!--第三步:编写组件标签 -->
<school></school>
<br><br>
<student></student>
</div>
// 创建student组件
const student = Vue.extend({
name:'zhangjignqi',
template: `<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div> `
,
data() {
return {
studentName: '张三',
age: 18
}
}
}),
new Vue({
el: "#root",
data: {
'22'
// }
},
components: {
school,
student
}
})
-
注册组件的简写方式
const school =Vue.extend(options)
可以简写为下面所示代码:
直接编写配置对象
const school = options
三、单文件组件
单文件组件的后缀是.vue文件
但是怎么编译怎么运行,需要学习脚手架
创建文件的命名可以参考2.5总结中的内容
一个单词组成:
第一种写法(首字母小写):school.vue
第二种写法(首字母大写):School.vue
多个单词组成:
第一种写法(kebab-case命名):my-school.vue
第二种写法(CamelCase命名):MySchool.vue(需要Vue脚手架支持)
一般页面的编写需要html、css、js,Vue为了完成这些常见的属性,提供了一些标签<template>、
<template>
<!-- 组件的结构 -->
<\template>
<script>
//组件交互所需要的代码(包括数据、方法等等)
</script>
<style >
/*组件的样式*/
</style>
-
我们可以创建一个学校组件school.vue
<!-- 此标签不会参与标签的编译 -->
<template>
<div class="demo">
<h2>学校名称:{{ schoolName }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="showName">点我提示学校名称</button>
</div>
</template>
<script>
//ES6中模块化暴露(默认暴露)
//创建组件Vue.extent是可以省略的,直接暴露组件的配置对象即可
export default Vue.extent({
//组件名
name: "School",
data() {
return {
schoolName: "qiludongyedaxue",
address: "济南",
};
},
methods: {
showName() {
alert(this.schoolName);
},
}
});
</script>
<style >
.demo {
background: orange;
}
</style>
-
创建student.vue
<!-- 此标签不会参与标签的编译 -->
<template>
<div >
<h2>学生姓名:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
</template>
<script>
export default Vue.extent({
//组件名
name: "Student",
data() {
return {
studentName: "张三",
age: 18,
};
},
});
</script>
<style ></style>
-
创建App.vue,汇聚所有组件
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from "./School.vue";
import Student from "./Student.vue";
export default {
name: "App",
components: {
School,
Student,
},
};
</script>
<style>
</style>
-
main.js文件会创建Vue实例
入口文件,一切的活都是从这里开始的
//关闭开发环境时的生产提示
Vue.config.productionTip = false
import App from './App.vue'
new Vue({
el:'#root',
components:{
App
}
})
-
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 联系单文件组件语法</title>
<!-- 在这个地方引入会出现main.js会出现一个问题,就是main.js找结构,但是发现结构还没有出来 -->
<!-- <script type="text/javascript" src="./main.js"></script> -->
</head>
<body>
<div id="root">
<!-- 容器里面的模板 -->
<App></App>
</div>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<!-- 先让模板出来再引入 -->
<script type="text/javascript" src="./main.js"></script>
</body>
</html>