Vue常用的高级特性
自定义组件的 v-model
- 一个组件上的
v-model
默认会利用名为value
的 prop 和名为input
的事件
父组件 index.vue
<template>
<div>
<div>{{ text1 }}</div>
<childA v-model="text1" />
<div>{{ text2 }}</div>
<!-- 自定义 v-model 的原理 -->
<childA :value="text2" @input="text2 = $event" />
</div>
</template>
<script>
import childA from './components/childA.vue'
export default {
components: {
childA
},
data() {
return {
text1: '测试',
text2: '原理'
}
}
}
</script>
子组件 childA.vue
<template>
<div>
<input :value="value" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
}
}
</script>
- 单选框、复选框等类型的输入控件可能会将
value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突
父组件 index.vue
<template>
<div>
<div>{{ boo }}</div>
<childB v-model="boo" />
</div>
</template>
<script>
import childB from './components/childB.vue'
export default {
components: {
childB
},
data() {
return {
boo: false
}
}
}
</script>
子组件 childB.vue
<template>
<div>
<input type="checkbox" :checked="aaa" @input="$emit('change', $event.target.checked)">
</div>
</template>
<script>
export default {
model: {
prop: 'aaa',
event: 'change'
},
props: {
aaa: Boolean
}
}
</script>
$listeners
$listeners
: 当前组件所有的事件监听器
$listeners简单应用
父组件 index.vue
<template>
<div>
<childA @click="handleClick" @test="handleTest" />
</div>
</template>
<script>
import childA from './components/childA.vue'
export default {
components: {
childA
},
data() {
return {}
},
methods: {
handleClick() {
console.log('通过childB触发了click')
},
handleTest() {
console.log('通过childB触发了test')
}
}
}
</script>
子组件 childA.vue
<template>
<div>
<!-- 将 组件childA 所有的事件监听器指向 子组件childB -->
<childB v-on="$listeners" />
</div>
</template>
<script>
import childB from './childB.vue'
export default {
components: {
childB
},
mounted() {
console.log(this.$listeners)
// 打印 组件childA 所有事件监听器
// click: ƒ invoker()
// test: ƒ invoker()
}
}
</script>
子组件 childA.vue 的子组件 childB.vue
<template>
<div>
<button @click="handleClick">触发click</button>
<button @click="handleTest">触发test</button>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$listeners)
},
methods: {
handleClick() {
this.$emit('click')
},
handleTest() {
this.$emit('test')
}
}
}
</script>
利用$listeners使封装的组件是一个完全透明的包裹器了
父组件 index.vue
<template>
<div>
<div>{{ val }}</div>
<baseInput v-model="val" label="测试:" />
</div>
</template>
<script>
import baseInput from './components/base-input.vue'
export default {
components: {
baseInput
},
data() {
return {
val: '123'
}
},
methods: {}
}
</script>
子组件 base-input.vue
<template>
<div>
<label>
{{ label }}
<input :value="value" v-bind="$attrs" v-on="inputListeners">
</label>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 不希望组件的`div`根元素继承非 props 的属性,利用`v-bind="$attrs"`将属性绑定到`input`原型元素上
props: {
label: {
type: String,
default: ''
},
value: {
type: String,
default: ''
}
},
computed: {
inputListeners: function() {
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: event => {
this.$emit('input', event.target.value)
}
}
)
}
}
}
</script>
现在 <base-input>
组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input>
元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native
监听器。
.sync 修饰符
与自定义组件的 v-model
有相同之处
- 自定义组件的
v-model
:子组件$emit('input', xxx)
,父组件v-model
接收 .sync
修饰符:子组件$emit('update:title', xxx)
, 父组件:title.sync
接收- 它们的目的都是为了双向绑定
父组件 index.vue
<template>
<div>
<div>我是父组件title:{{ title1 }}</div>
<!-- 传统方式:不一定是 @update:title,子组件 $emit 什么就是 @ 什么 -->
<childA :title="title1" @update:title="title1 = $event" />
<div>我是父组件title:{{ title2 }}</div>
<!-- 缩写方式,即 .sync 修饰符:规定子组件必须是 $emit('update:title', xxx) -->
<childA :title.sync="title2" />
</div>
</template>
<script>
import childA from './components/childA.vue'
export default {
components: {
childA
},
data() {
return {
title1: '',
title2: ''
}
},
methods: {}
}
</script>
子组件 childA
<template>
<div>
<div>我是子组件title:{{ title }}</div>
<input v-model="text" type="text" @input="handleInput">
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
},
data() {
return {
text: ''
}
},
methods: {
handleInput() {
this.$emit('update:title', this.text)
}
}
}
</script>
$nextTick
等数据改变并渲染结束再执行 $nextTick 的回调
<template>
<div>
<ul ref="ulRef1">
<li v-for="item in list1" :key="item">{{ item }}</li>
</ul>
<button @click="add1">添加</button>
<ul ref="ulRef2">
<li v-for="item in list2" :key="item">{{ item }}</li>
</ul>
<button @click="add2">添加</button>
</div>
</template>
<script>
export default {
data() {
return {
list1: [1, 2, 3],
list2: [1, 2, 3]
}
},
methods: {
add1() {
this.list1.push(4)
this.list1.push(5)
this.list1.push(6)
// 数据被改变,但是还没完成渲染
console.log(this.$refs.ulRef1.children.length) // 3
this.$nextTick(() => {
// 数据被改变,且完成了渲染
console.log(this.$refs.ulRef1.children.length) // 6
})
},
async add2() {
this.list2.push(4)
this.list2.push(5)
this.list2.push(6)
// 数据被改变,但是还没完成渲染
console.log(this.$refs.ulRef2.children.length) // 3
await this.$nextTick()
// 数据被改变,且完成了渲染
console.log(this.$refs.ulRef2.children.length) // 6
}
}
}
</script>
作用域插槽
作用域插槽: 让插槽能够访问子组件中的数据
父组件 index.vue
<template>
<div>
<childA>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</childA>
</div>
</template>
<script>
import childA from './components/childA.vue'
export default {
components: {
childA
},
data() {
return {
list1: [1, 2, 3],
list2: [1, 2, 3]
}
}
}
</script>
子组件 childA.vue
<template>
<span>
<slot :user="user">
{{ user.lastName }}
</slot>
</span>
</template>
<script>
export default {
data() {
return {
user: {
firstName: 'James',
lastName: 'Leblanc'
}
}
}
}
</script>
动态组件 与 keep-alive
自定义指令
全局自定义指令
指令挂在 Vue
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function(el) {
// 聚焦元素
el.focus()
}
})
局部自定义指令
指令挂在当前组件
<template>
<div>
<!-- v-focus 显示的时候自动获得焦点 -->
<input v-if="show" v-focus type="text" />
<!-- v-background 显示的时候添加样式 -->
<div v-if="show" v-background="'#f17f7f'">0</div>
<button @click="toggle">切换显示</button>
</div>
</template>
<script>
export default {
directives: {
focus: {
bind(el, binding, vnode, oldVnode) {
console.log('%c bind---', 'color:blue')
console.log('el---', el)
console.log('binding---', binding)
console.log('vnode---', vnode)
console.log('oldVnode---', oldVnode)
},
inserted(el) {
console.log('%c inserted---', 'color:blue')
el.focus()
},
update() {
console.log('%c update---', 'color:blue')
},
componentUpdated() {
console.log('%c componentUpdated---', 'color:blue')
},
unbind() {
console.log('%c unbind---', 'color:blue')
}
},
background: {
inserted(el, binding) {
console.log('el---', el)
console.log('binding---', binding)
console.log(el.style)
Object.assign(el.style, {
width: '100px',
lineHeight: '100px',
background: binding.value,
fontSize: '50px',
textAlign: 'center',
transition: 'all 3s'
})
}
}
},
data() {
return {
show: true
}
},
methods: {
toggle() {
this.show = !this.show
}
}
}
</script>