目录
组件是什么?
定义 :组件(Component)是前端在单页面应用(SPA)上最好的一种实现方式,把所有功能模块拆解成单独的组件,每个组件都有独立的作用域,且还可以相互通信。
通俗来说 ,组件我们可以看为是一个组成文件,它是由html,css,js以及资源文件(图片,音频等)所组成。页面上有许多功能模块,我们都可以将之定义为一个组件,每个组件的作用域是独立的。
组件化是什么?
当我们应用的功能都是使用组件的方式来构成实现,那么此应用就是一个组件化的应用。
为什么要提倡组件化编程?
对比组件化,传统编写模式存在的弊端:
1.依赖关系混乱(文件引入),不好维护。
2.代码复用率相对不高。
组件化优势:
1.将功能模块组件化,能提高代码复用性以及开发效率。
2.减小了beg调试区域,简化调试步骤。
3.有利于多人协作开发。
非单文件组件
组件的定义方式有两种,一种是非单文件组件定义,一种是单文件组件定义。
非单文件组件,顾名思义,就是一个文件中可以定义多个组件,使用.html文件后缀,而单文件组件是一个文件仅包含一个组件,使用.vue文件后缀。
非单文件组件定义存在弊端:
1. 模板编写没有提示
2. 没有构建过程, 无法将 ES6 转换成 ES5
3. 不支持组件的 CSS
4. 真正开发中几乎不用既然非单文件组件存在那么多弊端,为什么还要说明?
通过非单文件组件的学习,我们可以了解组件定义和使用流程以及一些细节,那么接下来的单文件组件就简单轻松多了。
组件的创建步骤
<body>
<div id="root">
<!-- //======第三步,组件的使用=======// -->
<!-- 将定义好的组件名,使用如下方式插入在页面想要放置的位置 -->
<hr>
<!--使用标签方式1-->
<show-info></show-info>
<hr>
<!--使用标签方式2(需要vue脚手架支持)-->
<show-info/>
<hr>
</div>
<script>
//======第一步,组件的定义=======//
//创建一个show-info组件
//Vue.extend()可不写,直接写配置项,但是底层还是会调用extend(),推荐先写
const showinfo = Vue.extend({
//使用模板字符串定义组件的html结构
template:`
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</div>
`,
//组件中的data必须使用函数的形式书写,
//因为我们需要保证组件复用的时候,每一个组件
//都应该返回一份原始且独立的数据。
data(){
return{
name:"Maohe",
age:18
}
}
})
new Vue({
el:"#root",
data:{},
//======第二步,组件的注册(局部注册)=======//
components:{
//第一个是定义组件名,第二个是组件定义对象(上方定义的)
'show-info':showinfo
}
})
</script>
</body>
如上代码使用局部注册,组件仅在该vue实例中el定义的容器中奏效。
页面展示:
全局注册方式(在所有容器都可用)
//参数:第一个是定义组件名,第二个是组件定义对象
Vue.component('show-info',showinfo);
命名注意点
1.由一个单词组成(name)
写法一:name(首字母小写)
写法二:Name(首字母大写)
不要在中间突然来个大写,例如naMe(错误!会被认为成多个字母)
2.由多个单词组成(myname)
写法:my-name(烤肉串格式,由JS语法定义需要加""引号)
如果没有使用vue脚手架,不要使用myName格式,会报错!
组件的嵌套
顾名思义,就是组件下还可以定义组件。我们可以看一个图:
写法:
需求:我们定义一个school组件和,再在school组件下定义一个student组件。
页面vue开发工具和页面展示
代码
<body>
<div id="root">
</div>
<script>
//定义Student组件
const Student = Vue.extend({
name:"Student",
template:`
<div>
<div>学生名称:{{studentName}}</div>
</div>
`,
data(){
return{
studentName:"Maohe"
}
},
})
//定义School组件
const School = Vue.extend({
name:"School",
template:`
<div>
<div>学校名称:{{schoolName}}</div>
<hr>
<Student></Student>
</div>
`,
data(){
return{
schoolName:"OoO学校"
}
},
//将Student组件定义在School下
components:{Student}
})
new Vue({
el:"#root",
template:`
<div>
<School></School>
</div>
`,
data:{},
//注册School组件
components:{School}
})
</script>
</body>
标准化开发(父类组件App)
为了方便组件化开发的方便,我们需要定义一个顶级的组件,例如App组件,此App组件需要管理下属第一层的组件,vm则只需要管理App组件即可。图下如:
就不进行代码演示了,就是利用组件的嵌套形式编写即可,将App组件注册在vm中,A、B和C组件注册在App组件中。
对组件的加深理解(VueComponent构造函数)
这里有几个关于组件的知识点,进行了解以后,对组件的内容会更加清晰。
我们先看一下如下代码(定义一个school组件):
const School = Vue.extend({
name:"School",
template:`
<div>
<div>学校名称:{{schoolName}}</div>
</div>
`,
data(){
return{
schoolName:"OoO学校"
}
},
})
根据上述代码,解释组件内容:
1.School组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend()生成的,extend()方法的返回值就是VueComponent构造函数。
2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们自动创建school组件的实例对象,既内部执行了new VueComponent(option)。
3.每次调用Vue.extend(),返回的都是一个全新的VueComponent。
4.VueComponent的实例对象,一般简称为VC
5.VM(Vue实例对象)和VC内部方法和属性基本相同,但是两者不可划等号。
VC内部为什么会有VM的属性和方法如下会解释到。
VC原型链指向的更改(为什么VC会和VM基本相同)
解决问题:上方说了:VM(Vue实例对象)和VC内部函数和属性基本相同。
但是VC底层并没有对VM进行拷贝等类似的操作,那VC为什么会有于VM几乎相同的属性和函数呢?
重要等式:Vuecomponent.prototype.__proto__ = Vue.prototype,通过此等式的分析,就会知道为什么VC内部会有和VM内部几乎相同的函数和属性。
这里包含原型链的知识点,如果忘记了,在此带着回顾一下。
原型链作用:当我们创建了一个空对象person,打印:person.name,首先会在person对象中寻找属性name,发现person对象中没有此属性,接着就会沿着原型链寻找,直到找到尽头。如果没有就会报错。
原型链核心和过程简述:
每个函数都会有prototype(显示原型属性),此属性指向的是一个空的object对象,称为原型对象,每个实例对象上又会有__proto__(隐式原型属性),__proto__指向的是实例对象构造函数的prototype,最后也会指向原型对象,原型对象也是个对象(本质就是个空的Object对象),既然是对象就会有__proto__,原型对象上的__proto__指向的是Object的原型对象,Object原型对象的__proto__为空,所以当找到此时,就是原型链的尽头。
如果还是不太清除,可以访问我的主页,在专栏中找到JS,里面有原型链的说明,内存图解,可以更可视化的方式了解原型链的运行流程。
如上通过简述的方式回顾了原型链知识点,其实VC会拥有VM身上几乎所有的函数和属性,是因为vue在VC的原型链指向上做了一些指向改变才会有的。
正文解释:
正常的调用extend()后,返回VC的构造函数, vc的构造函数指向的就是VC的原型对象,既然是对象,身上就会存在__proto__,本来直接指向的是Object的 原型对象,但是在此vue对指向进行了修改,将原本指向Object原型对象的链重新指向Vue的原型对象身上(使得VC可以调用VM原型上的所有函数和属性),在通过Vue的原型对象才指到Object的原型对象,到达原型链尽头。
简易图解:
总结:就是因为Vue改变了VC的原型指向,使之指向了Vue的原型对象,所以VC就可以调用VM身上所定义的属性或函数了。所以Vm和VC才会几乎相同。
单文件组件
概述:
1.使用.vue后缀的文件编写。
2.文件命名建议使用大驼峰格式,组件名建议和文件名相同。
创建-单文件组件
首先我们需要创建.vue的文件,其次如果没有安装vue相关的插件,那么在.vue文件中将不会有代码高亮和一些必要的代码提示。
可以到自己的编码工具中下载对应插件,例如vscode中可以搜索"vetur"根据下载量下载版本。而HbuilderX中到工具-插件安装-点击到插件市场,搜索vue插件下载即可。
单文件组件的基本结构
<template>
<!-- HTML结构书写位置 -->
</template>
<script>
// 数据和交互书写位置
</script>
<style>
/* 样式书写位置 */
</style>
需求:创建一个UserInfo组件(单文件组件方式)
<template>
<!-- HTML结构书写位置 -->
<div>
<div id="name">用户姓名:{{userName}}</div>
<div id="age">用户年龄:{{UserAge}}</div>
<button @click="isAdult">是否成年?</button>
</div>
</template>
<script>
// 数据和交互书写位置(省略了Vue.extend(),直接写配置项)
//也不需要变量来接收,直接将配置项模块暴露
export default{
name:"UserInfo",
data(){
return{
userName:"Maohe",
userAge:18
}
},
methods:{
isAdult(){
this.userAge >= 18? alert("已经成年!") :alert("未成年!");
}
}
}
</script>
<style>
/* 样式书写位置 */
#name{
font-size: 19px;
}
</style>
根据标准化开发,我们需要创建一个App组件(组件父类),用来管理所有组件,而让vm管理一个组件App即可。
创建App组件
<template>
<div>
<UserInfo></UserInfo>
</div>
</template>
<script>
// 引入需要管理的子组件
import UserInfo from './UserInfo.vue'
// 将App组件暴露
export default{
name:"App",
//注册子组件
components:{
UserInfo
}
}
</script>
<style>
</style>
创建好app后,我们需要创建一个js文件(main.js=>入口文件),用来创建Vue实例,并且绑定容器和引入app组件。
创建main.js(入口文件)
// 引入App组件
import App from './App.vue'
// 创建Vue实例,绑定容器
new Vue({
el:"#root",
// 给App组件进行注册
components:{App}
})
以上都创建好了以后,我们就可以将js文件引入html文件中,至此一套标准的组件化开发就完成了。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="root">
<App></App>
</div>
<script src="../../vue.js"></script>
<script src="./main.js"></script>
</body>
</html>
上方是我们手动开发了所有必要的代码,但是直接在浏览器中并不可以正常解析。因为浏览器并不认识.vue文件,需要我们编译成js后浏览器才可以解析,这时我们就需要借助Vue脚手架(Vue-CLI 命令行界面),它是Vue提供的标准化开发工具,我们可以用来编译组件,使用脚手架还可以快速搭建项目结构,就可以省略掉许多地方的手写。
使用Vue脚手架(运行vue组件化应用)
安装脚手架
我们可以利用node环境的npm命令进行全局安装@vue/cli:
//在命令行界面执行
npm install -g @vue/cli
//要是npm下载速度慢,可以执行如下命令更换一个镜像
npm config set registry https://registr y.npm.taobao.org
安装好脚手架后后,我们到想要创建vue项目的目录中打开cmd窗口,
例如:我们想要在2023-2-6目录下创建vue项目:
或者直接打开cmd,使用cd,cd..等系统内置移动命令到达目录。
创建vue项目
创建好了以后,我们就可以在目录中看到了
运行上方定义的单文件组件
众多目录和文件中我们现在只需要关心src目录和public目录。
将上方我们定义的单文件组件中的App组件,和Student组件给替换掉我们使用脚手架自动创建的项目对应文件,将自定义的App组件替换时,记得更改子组件路径,将我们自定义的Student组件放到components目录中。
然后使用命令行进入(使用cd 目录名)创建好的vue项目。
执行命令:npm run serve
等待一会,会出现:
如此我们就在8080端口上开启了一个服务,我们通过浏览器访问此路径就可以看到我们写的项目了。
总结:其实已经可以发现,直接使用脚手架帮我们创建的项目,有着很全面的项目结构。
至此,我们定义的单文件组件运行也成功了。
Vue脚手架目录结构解读
render函数
在入口文件main.js中,我们发现存在一个陌生的函数rander(),本来此文件是用来给App组件进行注册并且使用template来渲染App组件的,但是现在并没有给App组件注册也没有使用template来渲染App组件。仅仅只有一个rander函数,那么它的作用是什么呢?
render函数的作用:渲染内容。
render函数的原貌
render(createElement){ //createElement名字自定义,原始文件中使用h
return createElement("h1","你好")
}render函数的参数就是一个函数,用来渲染内容。我们将组件作为函数放到参数函数里面就能渲染。在上方的书写中,我们可以将h1标签渲染并输入内容"你好"。或者我们可以将组件直接放到函数中当参数,就可以将组件进行渲染。
为什么需要使用rander函数渲染,而不是使用原来的方式先注册后放到template中进行解析渲染呢?
其实就是因为我们在使用脚手架创建vue项目的时候,main.js上方引入的vue.js不是原生的而是精简版的。此精简版的vue.js名字为vue.runtime.esm.js,这个精简版的与原生的vue.js的区别就是去掉了模板解析器。
验证
引入的是一个文件夹,我们查看文件夹。
node.js模块化的特性,当我们引入一个文件夹时,会去访问此文件夹的packge.json配置文件。
可以看到我们实际上默认是引入了module的值。
总结:所以就是因为我们引入的是精简版的vue(去掉了模板解析器),所以我们才不能使用template来解析,因为没有了模板解析器。之所以使用精简版的vue,是因为模板解析器占了vue的1/3空间。使用精简版更加的轻量级。