组件的事件
事件名 --- 命名规则
不同于组件和prop属性,事件名不存在任何自动化的大小写转换。而是触发的事件名需要 完全匹配 监听这个事件所用的名称。
bash
//vue2 子组件定义事件名 myEvent
this.$emit('myEvent')
bash
<!-- 父组件触发名为my-event -->
<!-- 此写法是没有任何效果的 -->
<my-component v-on:my-event="doSomething"></my-component>
事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了;
并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent------导致 myEvent 不可能被监听到。
建议始终使用**kebab-case(短横线)**的事件名书写;
组件的触发与监听
在子组件中,我们可以直接使用$emit方法触发自定义事件;
vue2中的使用方法
bash
//vue2子组件 MyComponent.vue
<template>
<button @click="$emit('some-event')">click me</button>
<!-- <button @click="btnClick">click me</button> -->
</template>
<script>
export default {
props:['postTitle'],
data(){
return { }
},
methods:{
/*定义方法使用$emit触发*/
btnClick(){
this.$emit('some-event')
},
}
}
</script>
bash
//vue2 父组件 index.vue
<template>
<div class="index-main">
<my-component v-on:some-event="callback"></my-component>
<!-- 组件的事件监听器也支持.once修饰符 -->
<my-component v-on:some-event.once="callback"></my-component>
</div>
</template>
<script>
import my-component from '../components/MyComponent';
export default {
components:{
'my-component': my-component
},
data(){
return {}
}
}
</script>
vue3直接 使用 $emit 触发自定事件
bash
//vue3子组件 MyComponent.vue
<script setup>
</script>
<template>
<button @click="$emit('some-event')">click me</button>
</template>
vue3使用< script setup >
需要通过 defineEmits() 宏来声明它要触发的事件
bash
//vue3子组件
<script setup>
const emit = defineEmits(['some-event']);
function btnClick(){
emit('some-event');
}
</script>
<template>
<button @click="btnClick">click me</button>
</template>
vue3不使用< script setup >
bash
export default {
emits: ['some-event'],
setup(props, ctx) {
ctx.emit('some-event')
}
}
bash
//vue3 父组件 index.vue
<script setup>
import {ref, reactive } from 'vue';\
import my-component'@/components/MyComponent.vue'
function someHandle(){
console.log('子组件事件触发');
}
</script>
<template>
<div class="index-main">
<my-component v-on:some-event="someHandle"></my-component>
</div>
</template>
组件事件参数
bash
//vue3 子组件
<button @click="$emit('some-event', 2)">click me</button>
bash
```bash
//父组件
<my-component @some-event="(n) => count += n" />
定义一个方法
bash
<my-component @some-event="someHandle" />
function someHandle(e){
console.log(e);
}
父子组件均需传参时
bash
<my-component @some-event="(e) => someHandle('父组件参数',e)" />
function someHandle(text,n){
console.log('text',text);//text的值为:父组件参数
console.log('子组件传参',n);//n的值为:2
}
vue3组件事件的校验
和prop的校验类似,要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit 的内容,返回一个布尔值来表明事件是否合法。
bash
<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>
vue2之v-model
在了解v-model在组件中的使用之前,可先了解在f表单控件中的使用,具体参考vue模板语法介绍,此文中介绍了v-model语法糖的实现原理;
vue2自定义组件的v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
bash
//将value更改为checked,将input事件更改为change事件
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
vue3中v-model的用法
v-model可以再组件是实现双向绑定;
从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:
defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:
它的 .value 和父组件的 v-model 的值同步;
当它被子组件变更了,会触发父组件绑定的值一起更新。
bash
//父组件 index.vue
<script setup>
import InputText from '@/components/InputText.vue'
import { ref } from 'vue'
const textVal = ref();
const inputText = ref();
function submit(){
textVal.value = inputText.value;
}
</script>
<template>
<p>textVal的值:{{textVal}}</p>
<InputText v-model="textVal" placeholder="请输入内容"></InputText>
<input v-model="inputText"/>
<button @click="submit">修改父组件的值</button>
</template>
bash
//子组件 InputText.vue
<script setup>
const inputText = defineModel();
</script>
<template>
<p>子组件inputText:{{inputText}}</p>
</template>
将v-model绑定input
基于v-model能实现父子组件双向绑定的作用,意味着你也可以用 v-model 把这个 ref 绑定到一个原生 input 元素上,在提供相同的 v-model 用法的同时轻松包装原生 input 元素:
bash
//父组件
<script setup>
import InputText from '@/components/InputText.vue'
import { ref } from 'vue'
const textVal = ref();
</script>
<template>
<div class="index-main">
<p>{{textVal}}</p>
<InputText v-model="textVal" placeholder="请输入内容"></InputText>
</div>
</template>
bash
//子组件 InputText.vue
<script setup>
const inputText = defineModel()
const props = defineProps(['placeholder'])
</script>
<template>
<input v-model="inputText" :placeholder="placeholder"/>
</template>
设置必填和默认值
bash
//使v-model必填
//const model = defineModel({ required: true })
// 提供一个默认值
//const model = defineModel({ default: 0 })
注意:如果为 defineModel prop 设置了一个 default 值且父组件没有为该 prop 提供任何值,会导致父组件与子组件之间不同步。在下面的示例中,父组件的 myRef 是 undefined,而子组件的 model 是 1:
bash
//子组件
const model = defineModel({
default:1
})
//父组件
const textVal = ref();
<InputText v-model="textVal"></InputText>
底层机制
defineModel 是一个便利宏。编译器将其展开为以下内容:
- 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
- 一个名为 update:modelValue 的事件,当本地
ref 的值发生变更时触发。
bash
//3.4版本之前
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
v-model的参数
bash
//父组件
<script setup>
import InputText from '@/components/InputText.vue'
import { ref } from 'vue'
const textVal = ref();
</script>
<template>
<div class="index-main">
<p>textVal的值:{{textVal}}</p>
<InputText v-model:title="textVal" placeholder="请输入内容"></InputText>
</div>
</template>
bash
//子组件 InputText.vue
<script setup>
const inputText = defineModel('title')
//如果需要额外的 prop 选项,应该在 model 名称之后传递:
//const inputText = defineModel('title', { required: true })
const props = defineProps(['placeholder'])
</script>
<template>
<input v-model="inputText" :placeholder="placeholder"/>
</template>
bash
//3.4之前的用法
<script setup>
defineProps({
title: {
required: true
}
})
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
多个v-model绑定
bash
//父组件
<script setup>
import InputText from '@/components/InputText.vue'
import { ref } from 'vue'
const first = ref();
const last = ref();
</script>
<template>
<div class="index-main">
<InputText v-model:first-name="first"
v-model:last-name="last"></InputText>
</div>
</template>
bash
//子组件 InputText.vue
<script setup>
const firstN = defineModel('firstName');
const lastN = defineModel('lastName');
</script>
<template>
<input v-model="firstN" />
<input v-model="lastN" />
</template>
bash
//3.4之前用法
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
处理v-model修饰符
自定义修饰符
为了能够基于修饰符选择性地调节值的读取和写入方式,我们可以给 defineModel() 传入 get 和 set 这两个选项。这两个选项在从模型引用中读取或设置值时会接收到当前的值,并且它们都应该返回一个经过处理的新值。下面是一个例子,展示了如何利用 set 选项来应用 capitalize (首字母大写) 修饰符:
bash
//父组件,当输入字母时,首字母将会更改为大写
<InputText v-model.capitalize="first"></InputText>
bash
//子组件
<script setup>
//通过解构获取v-model的修饰符
const [inputText,modifiers] = defineModel({
set(value){
if( modifiers.capitalize ){
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
});
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="inputText" />
</template>
bash
//3.4之前用法
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
带参数修饰符
bash
//父组件
<InputText v-model:first-name.capitalize="first" v-model:last-name.uppercase="last"></InputText>
bash
//子组件
<script setup>
const [firstN, firstNameModifiers] = defineModel('firstName',{
set(value){
if( firstNameModifiers.capitalize ){
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
});
const [lastN, lastNameModifiers] = defineModel('lastName',{
set(value){
console.log('lastNameModifiers',lastNameModifiers);
if( lastNameModifiers.uppercase ){
return value.toUpperCase();
}
return value
}
});
</script>
<template>
<input type="text" v-model="firstN" />
<input type="text" v-model="lastN" />
</template>
透传Attributes(vue3)
"透传 attribute"指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
当一个组件只有一个元素作为根元素时,透传属性会自动添加到根元素上;参考2.1class绑定;
如果子组件已有class或style属性,父组件继承的值会与原有值值进行合并;点击此处查看官网介绍
bash
<!-- <MyButton> 子组件的模板 -->
<button class="btn">click me</button>
<!-- 父组件 -->
<my-button class="large"></my-button>
<!-- 渲染结果 -->
<button class="btn large">click me</button>
深层组件继承
bash
//父组件
<BaseButton class="red"></BaseButton>
//BaseButton子组件
<MyButton></MyButton>
//MyButton子组件
<button>子组件按钮</button>
//渲染结果
<button class="red">子组件按钮</button>
red类会通过BaseButton组件继续透传给MyButton组件;
1、透传的 attribute 不会包含 < BaseButton> 上声明过的 props 或是针对 emits 声明事件的 v-on 侦听函数,换句话说,声明过的 props 和侦听函数被 < BaseButton>"消费"了
2、透传的 attribute 若符合声明,也可以作为 props 传入 < MyButton>
禁用Attributes继承
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
从 3.3 开始你也可以直接在
bash
//MyButton子组件
<template>
<button>子组件按钮</button>
</template>
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
bash
//父组件
<script setup>
import MyButton from '@/components/MyButton.vue'
</script>
<template>
<div class="index-main">
<MyButton class="red"></MyButton>
</div>
</template>
当需要把透传属性用在根节点以外的其他元素时,需设置inheritAttrs:false;
在模板中直接$attrs可访问到除组件所声明的props和emits之外的所有透传内容,例如:class、style、v-on监听器等等;
注意事项:
1、和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问
2、像 @click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick
bash
//父组件
<MyButton class="red" style="font-size:20px; font-weight: 700;" @click="btnClick"></MyButton>
//子组件
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
<template>
<div>
<button v-bind="$attrs">子组件按钮</button>
</div>
</template>
子组件多个根节点时父组件透传则会报错;增加$attrs显示绑定则报错消失;
bash
//子组件,此时会报错
<template>
<div>
<button>子组件按钮</button>
</div>
<div>多个根节点</div>
</template>
bash
//解决报错
<template>
<div>
<button v-bind="$attrs">子组件按钮</button>
</div>
<div>多个根节点</div>
</template>
JavaScript 中访问透传
如果需要,你可以在
bash
//子组件
<script setup>
import {ref, useAttrs} from 'vue'
const attrs = useAttrs();
console.log('attrs',attrs);
</script>
bash
//没有使用script setup
export default {
setup(props, ctx) {
// 透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs)
}
}
需要注意的是,虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用。