四、Vue 组件
ES6知识补充
- ES6模块化
- 没有模块化的世界:全局变量污染、难以管理的依赖
- 常见的模块化标准:CommonJS、ES6 Module、AMD、CMD、UMD。。。
组件概念
一个完整的网页是复杂的,如果将其作为一个整体来进行开发,将会遇到下面的困难
- 代码凌乱臃肿
- 不易协作
- 难以复用
vue推荐使用一种更加精细的控制方案——组件化开发
所谓组件化,即把一个页面中区域功能细分,每一个区域成为一个组件,每个组件包含:
- 功能(JS代码)
- 内容(模板代码)
- 样式(CSS代码)
由于没有构建工具的支撑,CSS代码暂时无法放到组件中
组件开发
创建组件
组件是根据一个普通的配置对象创建的,所以要开发一个组件,只需要写一个配置对象即可
该配置对象和vue实例的配置是几乎一样的
//组件配置对象
var myComp = {
data(){
return {
// ...
}
},
computed:{
//...
},
methods:{
//...
},
template: `....`
}
值得注意的是,组件配置对象和vue实例有以下几点差异:
- 无
el
data
必须是一个函数,该函数返回的对象作为数据- 由于没有
el
配置,组件的模板必须定义在template
中 template
中的所有HTML元素必须且只能有一个根元素
注册组件
注册组件分为两种方式,一种是全局注册,一种是局部注册
全局注册
一旦全局注册了一个组件,整个应用中任何地方都可以使用该组件
全局注册的方式是:
// 参数1:组件名称,将来在模板中使用组件时,会使用该名称
// 参数2:组件配置对象
// 该代码运行后,即可在模板中使用组件
Vue.component('my-comp', myComp)
在模板中,可以使用组件了
<my-comp />
<!-- 或 -->
<my-comp></my-comp>
但在一些工程化的大型项目中,很多组件都不需要全局使用。 比如一个登录组件,只有在登录的相关页面中使用,如果全局注册,将导致构建工具无法优化打包 因此,除非组件特别通用,否则不建议使用全局注册
局部注册
局部注册就是哪里要用到组件,就在哪里注册
局部注册的方式是,在要使用组件的组件或实例中加入一个配置:
// 这是另一个要使用my-comp的组件
var otherComp = {
components:{
// 属性名为组件名称,模板中将使用该名称
// 属性值为组件配置对象
"my-comp": myComp
},
template: `
<div>
<!-- 该组件的其他内容 -->
<my-comp></my-comp>
</div>
`;
}
应用组件
在模板中使用组件特别简单,把组件名当作HTML元素名使用即可。
但要注意以下几点:
- 组件必须有结束
组件可以自结束,也可以用结束标记结束,但必须要有结束
下面的组件使用是错误的:
<my-comp>
- 组件的命名
无论你使用哪种方式注册组件,组件的命名需要遵循规范。
组件可以使用kebab-case 短横线命名法
,也可以使用PascalCase 大驼峰命名法
下面两种命名均是可以的
var otherComp = {
components:{
"my-comp": myComp, // 方式1
MyComp: myComp //方式2
}
}
实际上,使用
小驼峰命名法 camelCase
也是可以识别的,只不过不符合官方要求的规范
使用PascalCase
方式命名还有一个额外的好处,即可以在模板中使用两种组件名
var otherComp = {
components:{
MyComp: myComp
}
}
模板中:
<!-- 可用 -->
<my-comp />
<MyComp />
因此,在使用组件时,为了方便,往往使用以下代码:
var MyComp = {
//组件配置
}
var OtherComp = {
components:{
MyComp // ES6速写属性
}
}
注意,
PascalCase
命名不可以直接在html中使用,但可以在template配置中使用 想想为什么?
组件树
一个组件创建好后,往往会在各种地方使用它。它可能多次出现在vue实例中,也可能出现在其他组件中。
于是就形成了一个组件树
向组件传递数据
大部分组件要完成自身的功能,都需要一些额外的信息
比如一个头像组件,需要告诉它头像的地址,这就需要在使用组件时向组件传递数据
传递数据的方式有很多种,最常见的一种是使用组件属性 component props
首先在组件中申明可以接收哪些属性:
var MyComp = {
props:["p1", "p2", "p3"],
// 和vue实例一样,使用组件时也会创建组件的实例
// 而组件的属性会被提取到组件实例中,因此可以在模板中使用
template: `
<div>
{{p1}}, {{p2}}, {{p3}}
</div>
`
}
在使用组件时,向其传递属性:
var OtherComp = {
components: {
MyComp
},
data(){
return {
a:1
}
},
template: `
<my-comp :p1="a" :p2="2" p3="3"/>
`
}
注意:在组件中,属性是只读的,绝不可以更改,这叫做单向数据流
组件的生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
beforeCreate:在实例开始初始化时同步调用。此时数据观测、事件等都尚未初始化。
created:在实例创建之后调用。此时已完成数据观测、事件方法,但尚未开始DOM编译,即未挂载到document中。
beforeMount:在mounted之前运行。
mounted:在编译结束时调用。此时所有指令已生效,数据变化已能触发DOM更新,但不保证$el已插入文档。
beforeUpdate:在实例挂载之后,再次更新实例(例如更新 data)时会调用该方法,此时尚未更新DOM结构。
updated:在实例挂载之后,再次更新实例并更新完DOM结构后调用。
beforeDestroy:在开始销毁实例时调用,此刻实例仍然有效。
destroyed:在实例被销毁之后调用。此时所有绑定和实例指令都已经解绑,子实例也被销毁。
工程结构
调整了文件夹结构,适应工程化 将自定义组件放在components文件夹中,libs是需要引入的vue.js源文件(注意是支持模块化的源文件),main.js是主要的文件,index.html中引入了main.js
UserInfo.js
var template = `<div>
<p>姓名:{{username}}</p>
<p>年龄:{{age}}</p>
</div>`;
export default{
data(){
return {
username:"张三",
age:17
}
},
template:template
}
main.js
import Vue from './libs/vue-module.js';
import UserInfo from './components/UserInfo.js'
var template = `
<div>
<h1>{{title}}</h1>
<UserInfo></UserInfo>
</div>
`;
new Vue({
el:"#app",
components:{UserInfo},
data:{
title:"模块化"
},
template
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="src/main.js" type="module"></script>
</html>
这里的UserInfo组件并不能接收参数,作为一个组件,最好能接收参数.
这其实就是组件和组件之间的传值问题,那么现在是父组件往子组件传值
加入一个新的组件UserInfoParam.js
var template = `<div>
<p>姓名:{{username}}</p>
<p>电话:{{tel}}</p>
</div>`;
export default{
props:["username","tel"], // 需要调用页面传送过来的变量名
template:template
}
将项目结构再次做出修改,不需要在main.js中加入太多代码,main.js的功能只在于启动程序 现在代码主要写在app.js文件中,而main.js只负责引用vue.js与引用app .js
main.js
import Vue from './libs/vue-module.js';
import App from './app.js'
new Vue({
render:(h)=>h(App)
}).$mount("#app");
app.js
import UserInfo from './components/UserInfo.js'
import UserInfoParam from './components/UserInfoParam.js'
var template = `<div>
<h1>{{title}}</h1>
<UserInfo></UserInfo>
<hr />
<UserInfoParam v-for="(item,i) in users" :key="i" :username="item.username" :tel="item.tel"></UserInfoParam>
</div>`;
export default{
data(){
return {
title:"模块化",
users:[
{username:"张三",tel:"12345"},
{username:"李四",tel:"12345"},
{username:"王五",tel:"12345"},
{username:"赵六",tel:"12345"},
]
}
},
components:{UserInfo,UserInfoParam},
template
};
上面演示的是传多个的单值,当然也可以直接传数组,将上面的代码稍微修改
UserInfoParam.js
var template = `<div>
<div v-for="(user,i) in users" :key="i">
<p>姓名:{{user.username}}</p>
<p>电话:{{user.tel}}</p>
</div>
</div>`;
export default{
props:{
users:{
type:Array,
required:true
}
},
template:template
}
app.js
import UserInfo from './components/UserInfo.js'
import UserInfoParam from './components/UserInfoParam.js'
var template = `<div>
<h1>{{title}}</h1>
<UserInfo></UserInfo>
<hr />
<UserInfoParam :users="users"></UserInfoParam>
</div>`;
export default{
data(){
return {
title:"模块化",
users:[
{username:"张三",tel:"12345"},
{username:"李四",tel:"12345"},
{username:"王五",tel:"12345"},
{username:"赵六",tel:"12345"},
]
}
},
components:{UserInfo,UserInfoParam},
template
};
Comments