事件
触发与监听事件
标题解读:子组件调用父组件传递的方法,在调用时父组件监听该方法;
在组件模板表达式中,可以直接使用$emit
方法触发自定义事件(父组件传递的事件)
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
父组件代码
<MyComponent @some-event="callback" />
<MyComponent @some-event.once="callback" />
💯注意命名方式
事件参数
子组件传递参数
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
父组件接受方式
<MyButton @increase-by="(n) => count += n" />
<MyButton @increase-by="increaseCount" />
声明触发事件
显示声明组件接收的函数
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
},
methods:{
buttonClick:function(t){
this.$emit('someEvent2',t)
}
}
}
export default {
emits: ['inFocus', 'submit'],
setup(props, { emit }) {
emit('submit')
},
methods:{
buttonClick:function(t){
this.$emit('someEvent2',t)
}
}
}
这个 emits
选项还支持对象语法,它允许我们对触发事件的参数进行验证:
<script setup>
const emit = defineEmits({
submit(payload) {
// 通过返回值为 `true` 还是为 `false` 来判断
// 验证是否通过
}
})
</script>
事件校验
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件 根据返回的布尔值进行事件拦截
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
🛑配合v-model使用
自定义事件可以用于开发支持v-model的自定义表单组件。
<input v-model="searchText" />
上面的代码解释之后就是
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
当使用在自定义组件上,展开形式如下
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
这个例子工作起来组件内部需要做两件事:
将内部input元素的value绑定到modelValue值;输入新值在input上触发update:modalValue事件。
完整的案例
<template>
<CustomInput
:modalValue="message"
@update:modalValue="newV=>message=newV"
/>
<!-- <CustomInput v-model="searchText" /> -->
</template>
<script setup>
import { defineProps,defineEmits } from 'vue';
defineProps(['modalValue']);
defineEmits(['update:modalValue']);
</script>
<template>
<h3>自定义组件配合v-model使用{{ modalValue }}</h3>
<input :value="modalValue" @input="$emit('update:modalValue',$event.target.value)" />
</template>
另一种实现方案
借助组件的computed,使用一个可写的,同时具有 getter 和 setter 的计算属性。get
方法需返回 modelValue
prop,而 set
方法需触发相应的事件。
<NextCustomInput
v-model="nextMessage"
/>
<script setup>
import { computed } from 'vue';
const props=defineProps(['modelValue']);
const emit=defineEmits(['update:modelValue']);
const value=computed({
get(){
return props.modelValue;
},
set(value){
emit('update:modelValue',value);
}
})
</script>
<template>
<h3>另一种方案自定义组件v-model实现{{ value }}</h3>
<input v-model="value" />
</template>
v-model的参数(起别名)
<MyComponent v-model:title="bookTitle" />
就是这么简单,子组件应声明一个title
prop,通过update:title
事件更新父组件值
<AliasInput v-model:title="nextMessage" />
<script setup>
const props=defineProps(['title']);
const emits=defineEmits(['update:title']);
</script>
<template>
<h3>自定义组件v-model起别名</h3>
<input
:value="props.title"
@input="$emit('update:title',$event.target.value)"
/>
</template>
多个 v-model
绑定
<MoreInput v-model:one="message" v-model:two="nextMessage" />
<script setup>
const props = defineProps(["one", "two"]);
const emits = defineEmits(["update:one", "update:two"]);
</script>
<template>
<h3>一个组件多个model</h3>
<input @input='$emit("update:one",$event.target.value)' :value='one' />
<input @input='$emit("update:two",$event.target.value)' :value='two' />
</template>
自定义指令
实战
简介
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会受到指令所绑定的元素作为其参数。
<script setup>
const vFocus={
mounted:(el)=>el.focus()
}
</script>
<template>
<input v-focus />
</template>
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
在 <script setup>
中,任何以 v
开头的驼峰式命名的变量都可以被用作一个自定义指令。
将自定义指令注册到应用层级
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
指令钩子
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
钩子参数
指令的钩子会传递以下几种参数:
el
:指令绑定到的元素。这可以用于直接操作 DOM。binding
:一个对象,包含以下属性。value
:传递给指令的值。例如在v-my-directive="1 + 1"
中,值是2
。oldValue
:之前的值,仅在beforeUpdate
和updated
中可用。无论值是否更改,它都可用。arg
:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo
中,参数是"foo"
。modifiers
:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
。instance
:使用该指令的组件实例。dir
:指令的定义对象。
vnode
:代表绑定元素的底层 VNode。prevNode
:之前的渲染中代表指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用。
举例来说,像下面这样使用指令:
<div v-example:foo.bar="baz">
binding
参数会是一个这样的对象:
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
和内置指令类似,自定义指令的参数也可以是动态的。举例来说:
<div v-example:[arg]="value"></div>
这里指令的参数会基于组件的 arg
数据属性响应式地更新。
简化形式
当自定义指令仅仅需要在mounted和updated上实现相同的行为,不需要其他钩子
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
对象字面量
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
<!-- MyComponent 的模板 -->
<div> <!-- v-demo 指令会被应用在此处 -->
<span>My component content</span>
</div>
在组件上使用
组件上使用自定义命令,始终应用于组件的根节点
<MyComponent v-demo="test" />
<!-- MyComponent 的模板 -->
<div> <!-- v-demo 指令会被应用在此处 -->
<span>My component content</span>
</div>
需要注意的是组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过 v-bind="$attrs"
来传递给一个不同的元素。总的来说,不推荐在组件上使用自定义指令。