1. 如何注册一个组件?
(1)全局注册Vue.component
<div id="app"></div>
<script>
// 1. 全局注册
Vue.component("Foo",{
template:`<div>Foo</div>`
})
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!'
},
template:`<div>
123{{msg}}
<Foo></Foo>
</div>`
})
</script>
(2)局部注册,components属性
<div id="app"></div>
<script>
const Foo = {
template:`<div>Foo</div>`
}
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!'
},
components: {
Foo,
},
template:`<div>
123{{msg}}
<Foo></Foo>
</div>`
})
</script>
注意:
- 组件的template属性里面可以写入组件的视图
- 全局注册的组件在哪里都可以直接以标签引入的方式直接使用,而局部注册的组件只能在注册了该组件的组件里面使用,在其他组件里面使用会报错(未注册)
- 在一个组件里面,只能有一个根节点,(Vue3允许有多个)
- 组件里面的data属性必须是一个函数,返回一个对象,而不是直接的一个对象。原因是,组件是可以复用的,而对象是一个引用类型,如果不返回一个全新的对象,如果在某一个组件里面data被修改了,可能会影响到别的组件也发生改变,将data写成函数的形式,每次返回一个全新的对象,那么组件之间的data就不是同一个data,就隔离开了。
2. 组件的生命周期
Vue 一共有8个生命阶段,分别是创建前、创建后、加载前、加载后、更新前、更新后、销毁前和销毁后,每个阶段对应了一个生命周期的钩子函数。
beforeCreate钩子函数,在实例初始化之后,在数据监听和事件配置之前触发。因此在这个事件中我们是获取不到 data 数据的。created钩子函数,在实例创建完成后触发,此时可以访问 data、methods 等属性。但这个时候组件还没有被挂载到页面中去,所以这个时候访问不到 $el 属性。一般我们可以在这个函数中进行一些页面初始化的工作,比如通过 ajax 请求数据来对页面进行初始化。beforeMount钩子函数,在组件被挂载到页面之前触发。在 beforeMount 之前,会找到对应的 template,并编译成 render 函数。mounted钩子函数,在组件挂载到页面之后触发。此时可以通过 DOM API 获取到页面中的 DOM 元素。beforeUpdate钩子函数,在响应式数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前,这个时候我们可以对可能会被移除的元素做一些操作,比如移除事件监听器。updated钩子函数,虚拟 DOM 重新渲染和打补丁之后调用。beforeDestroy钩子函数,在实例销毁之前调用。一般在这一步我们可以销毁定时器、解绑全局事件等。destroyed钩子函数,在实例销毁之后调用,调用后,Vue 实例中的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
// 子组件的子组件
const Bar = {
template:`<div>bar</div>`
}
// 局部注册的子组件
const Foo = {
data() {
return {
msg: "Foo",
count:1
}
},
components: {
Bar,
},
beforeCreate () {
// 创建之前调用,一般很少用到
console.log('beforeCreate');;
},
created () {
// 用于请求后端接口来获取数据
// 可以结合async和await来使用
console.log('created');
console.log(this.$el); // undefined
},
beforeMount () {
// 挂载之前
console.log('beforeMounted');
},
mounted () {
// 渲染完成了
console.log('mounted');
console.log(this.$el); // Vue 实例使用的根 DOM 元素。
},
beforeUpdate () {
// 更新之前
console.log('beforeUpdate');
},
updated () {
// 更新完成了
console.log('updated');
},
beforeDestroy () {
// 销毁之前
console.log('beforeDestory');
},
destroyed () {
// 销毁完成,移除一些事件的监听
console.log('destoryed');
},
template:`<div>{{msg}} --- {{count}}
<button @click="count++">click</button>
<Bar></Bar></div>`
}
//根组件
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!',
showFoo:true
},
components: {
Foo
},
methods: {
handleShowfoo() {
this.showFoo = !this.showFoo;
}
},
template:`<div>
{{msg}}
<button @click="handleShowfoo">showFoo</button>
<Foo v-if="showFoo"></Foo>
</div>`
})
- 初始情况下,触发前4个钩子函数,组件处于挂载完成的阶段,如果有多个组件的顺序:父组件beforeCreate->父组件created->父组件beforeMounted->子组件beforeCreate->子组件created->子组件beforeMounted->子组件mounted->父组件mounted,销毁的时候依次触发父组件beforeDestory->子组件beforeDestory->子组件destoryed->父组件destoryed
- 当数据发生改变的时候(点击按钮click),触发beforeUpdate和updated,每次改变都会触发,更新的操作是在updated里面完成的,beforeUpdate这个时候的视图还是更新之前的
- 当通过v-if设置为false的时候,触发beforeDestory和destoryed,涉及到组件的销毁
3. Props给组件传参
- props相当于组件的输入
- 定义类型
type指定传参的类型,如果类型不对会报错;:message = '1'传入的是数值类型的1 - 定义默认值
default当没有传参数的时候取得这个默认值; - 可定义必须值
required:true表明这个参数是必传的; - 可自定义校验规则validator
- props是单向数据流,传过来的数据是不能被修改的
// 在Foo组件中定义props
props: {
message: {
type: String,
default: "hello"
},
}
// 使用props,以属性的形式传入
<Foo message = 'hhh'></Foo>
4. emit发出事件(自定义事件)
- 相当于组件的输出
- 这里的需求是:Bar能够控制自己的关闭,但是控制Bar是否关闭的showBar在父组件Foo里面,所以Bar要发出事件close然后在父组件Foo中处理
const Bar = {
methods: {
handleClick() {
// 发出事件并可以传参数
this.$emit('hello', 1, 3);
},
handleClose() {
this.$emit('close');
}
},
template:`<div>Bar
<button @click="handleClick">to Foo</button>
<button @click="handleClose">close</button>
</div>`
}
const Foo = {
data() {
return {
msg: "Foo",
showBar: true
}
},
components: {
Bar,
},
methods: {
handleHello(a, b) {
// 这里可以接收参数
console.log('hello', a, b)
},
handleClose() {
this.showBar = false;
},
handleShowBar() {
this.showBar = true;
}
},
// 在Bar里处理发出的事件hello和close
template:`<div>{{msg}}
<button @click="handleShowBar">show bar</button>
<Bar v-show="showBar" @hello="handleHello" @close="handleClose"></Bar></div>`
}
//根组件
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!',
showFoo:true
},
components: {
Foo
},
methods: {
handleShowfoo() {
this.showFoo = !this.showFoo;
}
},
template:`<div>
{{msg}}
<Foo></Foo>
</div>`
})
5. 自定义组件的v-model
- 利用v-model的双向绑定机制
- 自定义组件的v-model
const Bar = {
model: {
prop: "visible",
event: "close"
},
props:["visible"],
template:`<div v-show="visible">Bar
<button @click="$emit('close',false)">close</button>
</div>`
}
const Foo = {
data() {
return {
msg: "Foo",
showBar: true
}
},
components: {
Bar,
},
methods: {
handleShowBar() {
this.showBar = true;
}
},
// v-model自动把值传给visible
template:`<div>{{msg}}
<button @click="handleShowBar">show bar</button>
<Bar v-model="showBar"></Bar></div>`
}
- showBar控制Bar的显示和隐藏,一开始为true,在Bar里面点击close按钮之后,修改visible的值为false,通过v-model的双向绑定将visible的值自动绑定到showBar,实现Bar的隐藏。
- 可以用sync代替,将Bar的template里面的emit事件改成
"$emit('update:visible',false)",在使用Bar组件的地方改成<Bar :visible.sync="showBar"></Bar>
6. 插槽——内容分发
可以在自定义的组件中添加模板代码,并在注册组件的时候在template里面加入元素,这样的话组件渲染的时候,<slot></slot>里面的内容会被替换成添加的代码模板。但是当该组件的template里面没有slot元素时,该组件起始标签和结束标签的任何内容就会被抛弃。
const Foo = {
data() {
return {
msg: "Foo"
}
},
components: {
Bar,
},
// 在slot中可以加入后备内容,当组件标签中没有写入内容时显示这个
template:`<div>{{msg}}<slot>default</slot>
<Bar></Bar></div>`
}
//根组件
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!',
},
components: {
Foo
},
template:`<div>
{{msg}}
<Foo>这里是插槽内容</Foo>
</div>`
})
具名插槽
const Foo = {
data() {
return {
msg: "Foo"
}
},
components: {
Bar,
},
template:`<div>{{msg}}
<h1>
<slot name = "h1"></slot>
</h1>
<slot>default</slot>
<Bar></Bar></div>`
}
//根组件
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!',
},
components: {
Foo
},
template:`<div>
{{msg}}
<Foo>
<template v-slot:h1>
这里是插入的h1
</template>
haha
</Foo>
</div>`
})
可以给插槽指定名字,对应的内容插在对应的插槽里,如果没有指定就是默认插槽,“haha”被插在没有指定名称的插槽里。其中v-slot:h1可以简写成#h1
作用域插槽
const Foo = {
data() {
return {
msg_foo: "Foo",
count: 1
}
},
components: {
Bar,
},
template:`<div>{{msg_foo}}
<h1>
<slot name = "h1" :msg_foo="msg_foo" :count = "count"></slot>
</h1>
<slot>default</slot>
<Bar></Bar></div>`
}
//根组件
const app = new Vue({
el:"#app",
data:{
msg:'hello vue!',
},
components: {
Foo
},
template:`<div>
{{msg}}
<Foo>
<template v-slot:h1="data" >
这里是插入的h1 {{data}}
</template>
haha
</Foo>
</div>`
})
如果在插槽中使用子组件的数据,例如要访问Foo中的msg_foo,直接在根组件里面是访问不到的,那么可以在子组件的slot元素中绑定属性,注意属性可以绑定多个,然后在父组件中通过一个对象去进行接收,(上例中的data),也可以通过解构拿到Foo中特定属性的值。
7. 复用性
通过mixin将复用逻辑抽离出来,可以在多个组件内使用,这个例子是:实现多个组件都能显示当前鼠标所在的坐标
const mousemoveMixin = {
data() {
return {
x:0,
y:0
}
},
methods: {
handleMousemove(e) {
this.x = e.pageX;
this.y = e.pageY;
}
},
created () {
window.addEventListener("mousemove", this.handleMousemove);
},
destroyed() {
window.removeEventListener("mousemove", this.handleMousemove);
},
}
const Bar = {
mixins:[mousemoveMixin],
template:`<div>
Bar {{x}} -- {{y}}
</div>`
}
const Foo = {
data() {
return {
msg_foo: "Foo",
}
},
components: {
Bar,
},
mixins: [mousemoveMixin],
template:`<div>Foo {{x}} -- {{y}}
<Bar></Bar></div>`
}
- mixins是一个数组,能够包含多个mixin
- 多个mixin会造成来源不清晰和命名冲突问题,假设还有一个mixin里面还有属性x和y,那么我们就不清楚到底是哪个mixin提供给我们的x和y了