:::tip 组件
实现应用局部功能代码和资源的集合
:::
非单文件组件
非单文件组件即所有组件写在同一个文件里。
基本使用
定义组件:
- 使用
Vue.extend(options)
创建,和 new Vue(options)
的区别;
el
不写,最终所有的组件都要经过 vm 的管理,由 vm 的 el
决定服务哪个容器
data
必须写成函数,避免组件被复用时,数据存在引用关系
- 使用
template
节点可配置组件结构
注册组件;
- 局部注册:
components
选项
- 全局注册:
Vue.component('组件名',组件)
使用组件:<school></school>
1 2 3 4
| <div id="root"> <hello></hello> <school></school> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| const student = Vue.extend({ template: ` <div> <h2>学生姓名:{{studentName}}</h2> </div> `, data() { return { studentName: '张三', } }, })
const hello = Vue.extend({ template: ` <div> <h2>{{name}}</h2> </div> `, data() { return { name: 'Tom', } }, })
const school = Vue.extend({ name: 'school', template: ` <div> <h2>学校名称:{{name}}</h2> <student></student> </div> `, data() { return { name: '北京大学', } }, components: { student, }, })
Vue.component('hello', hello)
new Vue({ el: '#root', components: { school, }, })
|
注意事项:
- 组件名
- 一个单词:school, School
- 多个单词:my-school, MySchool(需要 vue-cli 支持)
- 使用组件
<school></school>
<school />
(需要 vue-cli 支持)
const school = Vue.extend(options)
可简写为 const school = options
。这是脚手架里 <script>
代码的简写来源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import HelloWorld from './components/HelloWorld.vue'
export default { name: 'App', components: { HelloWorld } }
const vc = Vue.extend({ name: 'App', components: { HelloWorld } }) export default vc
|
关于 VueComponent 构造函数
- 组件本质是一个名为
VueComponent
的构造函数,不是程序员定义的,是 Vue.extend
生成的
1 2 3
| const school = Vue.extend({...})
console.dir(school)
|
使用组件时,Vue 自动创建组件实例对象,即 new VueComponent(options)
是 Vue 做的
每次调用 Vue.extend
,返回的都是一个全新的 VueComponent
构造函数
1 2 3 4
| const school = Vue.extend({...}) const student = Vue.extend({...})
console.log(school === student)
|
组件的 this
指向 VueComponent
实例对象,而非 Vue 实例对象
重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
,这个改动使得组件实例对象得以访问 Vue 原型上的属性方法
单文件组件
单文件组件即 .vue
文件
scoped 解决样式冲突
- 原理:为当前组件所有 DOM 元素分配唯一的自定义属性,写样式时使用属性选择器防止样式冲突问题
scoped
只给子组件最外层的 div 添加了自定义属性 [data-v-xxx]
,子组件内部的标签并没有添加。因此父组件只能修改子组件最外层的 div 样式,修改子组件内层元素的样式是不可行的
- 若想让某些样式对子组件生效,需使用
/deep/
深度选择器
1 2 3 4 5 6 7 8 9 10 11 12
| <style lang="less" scoped> .title { color: blue; }
/deep/ .title { color: blue; } </style>
|
组件通信
自定义属性 props
父传子、子传父
props
验证:
- 基础类型检查:
String, Number, Boolean, Array, Object, Date, Function, Symbol
- 多个可能的类型
- 必填项检查
- 默认值
- 自定义验证函数
validator
props
是只读的,若是对象,对象内部的修改不报错,但不推荐。若需修改,则把 props
内容拷贝一份到 data
进行修改
父传子:
1 2 3
|
<Son :num="count" :msg="message" :pub-time="time"></Son>
|
1 2 3
| <p>父组件传过来的值:{{ num }}</p> <p>父组件传过来的值:{{ msg }}</p>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| props: ['num', 'msg', 'pubTime']
props: { num: Number, msg: String }
props: { num: { type: Number, default: 0 }, msg: { type: [String, Number], required: true, validator(value) { return value === 'hello' || value === 1 }, default: 1 } }
|
子传父:
- 父组件通过
props
给子组件传递函数,子组件调用该函数即可修改父组件的数据
- 组件
methods
里函数的 this 始终指向该组件实例,可理解为 Vue 底层对这些函数做了bind
处理
- 通过
bind
修改 this 指向后的新函数,其 this 指向不能再次修改。官网说明
- 思否文章
- 不推荐该方式进行子传父,推荐使用自定义事件
1
| <Son :addCount="addCount"></Son>
|
1 2 3 4 5 6 7 8 9 10 11 12
| export default { data() { return { count: 1, } }, methods: { addCount() { this.count++ }, }, }
|
1 2 3 4 5 6 7 8
| export default { props: ['addCount'], methods: { add() { this.addCount() }, }, }
|
自定义事件
自定义事件可用于实现子传父
子组件触发自定义事件,并传递数据:
1 2 3 4 5 6 7 8 9 10 11 12
| data() { return { count: 1 } }, methods: { add() { this.count += 1 this.$emit('count-change', this.count) } }
|
父组件监听子组件的自定义事件,并调用回调函数处理数据:
- 父组件通过
this.$refs.xxx.$on('事件名称',回调)
监听子组件自定义事件时,回调函数要么配置在 methods
中,要么用箭头函数,否则 this 指向会出问题
- 组件上也可以绑定原生 DOM 事件,需要使用
native
修饰符
- 若想让自定义事件只触发一次,可以使用
once
修饰符,或 $once
方法
1 2 3 4 5 6 7 8 9
| <Son @count-change="getNewCount"></Son> <Son @count-change.once="getNewCount"></Son>
<Son ref="sonRef"></Son>
<Son @click.native="handleClick"></Son>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export default { data() { return { father: 1, } }, methods: { getNewCount(val) { this.father = val }, }, mounted() { this.$refs.sonRef.$on('count-change', this.getNewCount) this.$refs.sonRef.$once('count-change', this.getNewCount)
this.$refs.sonRef.$on('count-change', (val) => (this.father = val)) }, }
|
解绑自定义事件this.$off()
:
1 2 3 4 5 6
| this.$off('count-change')
this.$off(['count-change', 'add'])
this.$off()
|
EventBus 全局事件总线
:::tip 思想
弄一个所有组件实例都能访问到的 Vue 实例对象,Vue 原型上包含事件处理的相关方法,包括 $on, $emit, $off, $once
:::
方式一
安装全局事件总线:
1 2 3 4 5 6 7 8
| new Vue({ ... beforeCreate() { Vue.prototype.$bus = this } ... })
|
数据接收方为自定义事件绑定回调函数:
1 2 3 4 5 6 7 8 9 10 11 12
| export default { methods: { handleData() {...} }, created() { this.$bus.$on('share', this.handleData) }, beforeDestroy() { this.$bus.$off('share') } }
|
数据发送方触发自定义事件:
1 2 3 4 5 6 7
| export default { methods: { sendData() { this.$bus.$emit('share', 666) }, }, }
|
方式二
创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象。
1 2 3 4
| import Vue from 'vue'
export default new Vue()
|
在数据发送方,调用 bus.$emit('事件名称', 要发送的数据)
方法触发自定义事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import bus from './eventBus.js'
export default { data() { return { message: 'hello', } }, methods: { sendData() { bus.$emit('share', this.message) }, }, }
|
在数据接收方,通过 bus.$on('事件名称', 事件处理函数)
为自定义事件注册事件处理函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import bus from './eventBus.js'
export default { data() { return { msg: '', } }, created() { bus.$on('share', (val) => { this.msg = val }) }, }
|
消息订阅与发布
:::tip
与全局事件总线很相似,因此一般用事件总线,不用这个
:::
安装第三方库 PubSubJS
:npm install -S pubsub.js
订阅消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import pubsub from 'pubsub-js'
export default { methods: { handleData(messageName, data) {...} }, created() { this.pubId = pubsub.subscribe('share', this.handleData) this.pubId = pubsub.subscribe('share', (messageName, data) => { console.log(data) }) }, beforeDestroy() { pubsub.unsubscribe(this.pubId) } }
|
发布消息:
1 2 3 4 5 6 7 8 9
| import pubsub from 'pubsub-js'
export default { methods: { sendData() { pubsub.publish('share', 666) }, }, }
|
ref / $refs
ref
用于给 DOM 元素或子组件注册引用信息。每个 vue 实例都有 $refs
对象,里面存储着 DOM 元素或子组件的引用。通过该方式可以获取到 DOM 元素或子组件实例。
可以父传子,也能子传父。子传父要和自定义事件搭配使用。
1 2 3 4 5 6 7
| <p ref="pp">这是段落</p> <button @click="getRef">获取 DOM 元素</button>
<son ref="sonRef"></son> <button @click="getComponent">获取子组件实例引用</button>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| methods: { getRef() { console.log(this.$refs.pp) this.$refs.pp.style.color = 'red' }, getComponent() { console.log(this.$refs.sonRef) this.$refs.sonRef.count = 1 this.$refs.sonRef.add() } }
|
组件的 $nextTick(cb)
方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行,即在 DOM 更新完成后再执行回调,从而保证 cb 回调可以获取最新的 DOM 元素。
1 2 3 4 5 6 7 8 9
| methods: { showInput() { this.inputVisible = true this.$nextTick(() => { this.$refs.input.focus() }) } }
|