Vue组件化
组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项
非单文件组件
即一个文件中有若干组件
基本使用
基本配置
基本配置和实例对象相同,只有两个注意:
- 没有
el属性:所有组件最终都要归于同一个vm实例管理,避免混乱 - data一定要写为函数,否则复用性会导致data中的数据频繁改变
如:
data: {
n: 1,
opacity: 2
},
改变data时data内的元素随之改变,而设置为函数,将对象作为返回值:
data() {
return {
n: 1,
opacity: 2
}
}
每次调用都会传入一个全新的对象,不会改变原有数据
使用组件
- 创建组件:
使用extend方法,组件要包含数据和结构(html语句)
使用标签template定义结构,` `是模板字符串语法
const school = Vue.extend({
template: `
<div>
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</div>
`,
data() {
return {
name: "Orange",
age: 18
}
}
})
- 在实例中注册组件
使用
Vue({components})属性,局部注册Vue.component全局注册
和过滤器、自定义指令的逻辑相同
// 局部
const vm = new Vue({
el: '#root',
components: {
school,
}
});
// 全局
Vue.component('name',{
school,
})
注册名和临时变量名相同时可简写
3. 编写组件标签
<div id="root">
<school></school>
</div>
脚手架环境下可使用自闭合标签
<school/>
组件命名
- 一个单词首字母大小写不限制,标签中统一即可
<school></school>
<School></School>
- 多个单词
- 使用
-连接
const tmp = Vue.extend({})
const vm = new Vue({
el: '#root',
components: {
'my-school':tmp,
}
});
注意JS语法要求属性的key含有-时必须用''包裹
对应标签
<my-school></my-school>
- 所有单词首字母大写(需要Vue脚手架支持)
- 使用属性name改变开发者工具中显示的名称
const school = Vue.extend({
name:"ShowName"
})
简写方法
可以直接不调用extend()方法:
const school = {
template: `
<div>
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</div>
`,
data() {
return {
name: "Orange",
age: 18
}
}
}
组件嵌套
组件内部也可以使用components属性配置子组件,由于是在内部配置,只能在该组件内部调用(即template内)
<body>
<div id="root">
<school></school>
</div>
<script>
const student = {
template: `
<div>
<h3>{{name}}</h3>
</div>
`,
data() {
return {
name: "Orange"
}
}
};
const school = {
template: `
<div>
<h1>{{address}}</h1>
<student></student> <!-- 内部调用子组件 -->
</div>
`,
data() {
return {
address: "StairWay to Heaven"
}
},
components:{
student
}
};
const vm=new Vue({
el:"#root",
components:{
school,
}
})
</script>
</body>
在标准化开发中,通常使用app组件管理所有组件,实例对象只管理app组件,同时将结构放在实例对象中用template定义,使逻辑清晰:
<body>
<div id="root">
</div>
<script>
const student = {
template: `
<div>
<h3>{{name}}</h3>
</div>
`,
data() {
return {
name: "Orange"
}
}
};
const school = {
template: `
<div>
<h1>{{address}}</h1>
<student></student>
</div>
`,
data() {
return {
address: "StairWay to Heaven"
}
},
components:{
student
}
};
const hello={
template: `<p>hello</p>`,
}
const app={
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
school,
hello
}
}
const vm=new Vue({
el:"#root",
template:`<app></app>`,
components:{
app,
}
})
</script>
</body>
注意:Vue2中的template只允许一个根标签,即2个及以上的标签要用一个div包裹
关于VueComponent
-
school组件本质是一个名为VueComponent的构造函数,且是由Vue.extend()生成的。 -
只需要写
<school/>或<school></school>,Vue解析时会创建school组件的实例对象,即Vue执行:new VueComponent(options)。 -
特别注意:每次调用
Vue.extend,返回的都是一个全新的VueComponent -
关于
this指向:- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的
this均是VueComponent实例对象 new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象
- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的
-
一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
单文件组件
单文件组件 — Vue.js
模块化开发的思想,和上面的类似,只不过每个组件放在单独的文件夹内
一般文件结构:
App.vue管理所有组件main.js创建Vue实例,管理Appxxx.vue具体组件的实现
模块化开发需要脚手架配合实现
脚手架
使用官方脚手架Vue CLI,见资源汇总
几个陌生语法:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- <%= BASE_URL %>指的是本文件夹 是Vue脚手架的特有语法-->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置页面标题,是webpack的语法,将package.json中name属性的值作为标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 浏览器不支持JS时,noscript包裹的内容就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
scoped属性
修饰组件的<style>标签,官方文档中作了详细的解释:HTML&CSS
在渲染页面时,所有组件的style都会同时渲染,此时就可能会出现同名的问题,同名时依照后来的覆盖前面的样式的原则,此时使用scoped属性修饰<style>标签说明改样式为局部样式,不会影响其他组件
<style scoped></style>
但是这个属性不适合用在App组件中
render()函数
render()在使用精简版的vue时被启用,此时template不会被解析,需要使用该函数
render()有一个返回值,是一个函数,参数为html模板,包含标签和html内容
new Vue({
render: h => h(App),
})
上方的代码就是render的简单实现,标准写法为:
new Vue({
render(createElement) {
return createElement('h1', 'Hello');
}
})
此时会在页面上渲染<h1>Hello<\h1>,由于函数中没有使用this,所以可以写为箭头函数,又参数只有一个,可以不加(),又App中同时包含标签和内容,所以只传入了一个参数
ref
官方文档:API-ref
和key一样,ref是一个特殊属性,被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
如果用在子组件上,引用就指向组件实例:
<div id="app">
<h1 ref="hello">Hello</h1>
<School ref="sch"></School>
<button @click="ShowELement" ref="btn">Get Element</button>
</div>
使用方法可以简单的查看ref的指向:
methods: {
ShowELement() {
console.log(this.$refs);
}
}
这里的方法是组件的方法,所以this指向组件实例对象vc,参见Vue组件化#关于VueComponent
配置项
props
props可以是数组或对象,用于接收来自父组件的数据。props可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。
基于数组时要写成字符串
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
基于对象时可以使用以下选项:
type:
可以是下列原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组。会检查一个 prop 是否是给定的类型,否则抛出警告。Prop 类型的更多信息在此default:any
为该 prop 指定一个默认值。如果该 prop 没有被传入,则换做用这个值。对象或数组的默认值必须从一个工厂函数返回。required:Boolean
定义该 prop 是否是必填项。在非生产环境中,如果这个值为 truthy 且该 prop 没有被传入的,则一个控制台警告将会被抛出。validator:Function
自定义验证函数会将该 prop 的值作为唯一的参数代入。在非生产环境下,如果该函数返回一个 falsy 的值 (也就是验证失败),一个控制台警告将会被抛出。你可以在这里查阅更多 prop 验证的相关信息
每个数据也要写成对象
name:{
type:String, //name的类型是字符串
required:true, //name是必要的
},
age:{
type:Number,
default:99 //默认值
},
注意事项
- 父组件传入的类型默认都是
String类型,若想使用数字等,需要使用:(v-bind)
<School name="A" :age='18' address="a"></School>
- 父组件传入的数据不允许修改
props中的数据优先级高于data,即优先接收props中的数据根据这一特性可以在data中配置临时元素实现对props中的元素的修改(不是修改原有元素)
data() {
console.log(this)
return {
msg:'我是一个尚硅谷的学生',
myAge:this.age
}
},
methods: {
updateAge(){
this.myAge++
}
},
//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props:{
age:{
type:Number,
default:99 //默认值
},
}
mixin
mixins选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑
使用方法:
- 局部使用:
多文件中的混入使用
一般将混入的内容放在跟目录下的mixin.js文件中,使用分别暴露语法暴露各个功能
export const mix = {
methods: {
showName() {
alert(this.name);
console.log('mix')
}
},
}
导入时也相应的改变写法:
import { mix } from '../mixin'
- 全局使用
在main.js中使用全局命令
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
导入的组件将会适用于本工程的所有Vue实例对象(vm)和组件实例对象(vc)
注意:
- 在混入和原文件中同时定义的数据以原文件为准
- 同时定义的钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用
插件
- 功能:用于增强Vue
- 本质上是一个暴露出来包含
install方法的对象,包含若干参数,第一个是Vue构造函数,后面时使用者传入的参数,参数通过Vue.use()传入 - 定义:一般定义在根目录下的plugins.js文件夹,使用导出语法进行导出
export default {
install(Vue) {
//自定义全局过滤器
Vue.filter('SliceString', function (value) {
return value.slice(0, 4);
});
//自定义全局指令
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
});
//...
}
}
- 使用:在main.js中使用全局方法
Vue.use()进行导入
import plugins from './plugins.js'
Vue.use(plugins)
就可以正常使用所有在插件中定义的方法,不需要其他操作
如果遇到报错:"import ... =" 只能在 TypeScript 文件中使用,只需在配置文件settings.json中添加:"javascript.validate.enable":false即可
参见这篇博客
$nextTick()
$nextTick()
在DOM更新后执行回调函数
组件自定义事件
组件自定义事件适用于子组件==>父组件
使用实例对象的函数$emit() 触发事件
$emit()
- 语法:
vm.$emit( eventName, […args] )
- 参数:
{string} eventName带绑定的事件名[...args]参数,调用时按照顺序依次调用即可
- 示例:
事件绑定情况如下:
<Student @myEvent="demo"></Student>
只有一个参数时:
demo(name) {
console.log('student name is ' + name)
}
this.$emit('myEvent', this.name);
有多个参数时:
demo(name,...a) {
console.log('student name is ' + name + a)
}
getStudentName() {
this.$emit('myEvent', this.name,1,2,3);
}
...a是es6中的写法,表示其他所有参数均存放在数组a中
绑定自定义事件
在子组件中,创建待绑定的函数,同时传入参数
下面为button标签绑定了点击事件的函数getStudentName()
<button @click="getStudentName">click</button>
methods: {
getStudentName() {
this.$emit('myEvent', this.name);
}
},
使用方法$emit()绑定触发的函数并传入参数
相应的,在父组件中,绑定对应的事件和方法:
- 方法一:
<Student @myEvent="demo"></Student>
methods: {
demo(name) {
console.log('student name is ' + name)
}
},
- 方法二:
<Student ref="student"></Student>
mounted() {
this.$refs.student.$on('myEvent', this.demo)
}
使用ref将子组件添加到父组件的ref对象上,使用$refs()方法访问,因为是组件,可以使用组件实例方法$on()绑定事件
实际上二者的逻辑一致,都是为标签绑定方法
解绑自定义事件
使用$off()解绑自定义事件
- 语法:
vm.$off( [event1, event2] )
使用数组形式解绑多个事件
或者什么都不传,解绑所有自定义事件
- 用法
- 移除自定义事件监听器。
- 如果没有提供参数,则移除所有的事件监听器;
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
- 移除自定义事件监听器。
注意事项
- ref写法中的
this指向问题
this.$refs.student.$on('myEvent', this.demo)
Vue中的一般规则是v-on与谁绑定,this就是谁,上面的代码this正常应该为Student组件实例对象,但是特别的,如果该元素在本实例对象中定义,那么this指向本实例对象,demo在本实例对象中定义了,所以this可以访问到demo()
- 组件标签的事件全部默认为自定义事件,即使是原生事件也需要
$emit()声明或者使用修饰符native
<Student ref="student" @click.native="demo"></Student>