vue组件深入介绍(事件、v-model及attribute透传)

组件的事件

事件名 --- 命名规则

不同于组件和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 执行副作用。

相关推荐
长天一色6 分钟前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_23423 分钟前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河26 分钟前
CSS总结
前端·css
NiNg_1_23426 分钟前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
读心悦27 分钟前
如何在 Axios 中封装事件中心EventEmitter
javascript·http
BigYe程普1 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
神之王楠1 小时前
如何通过js加载css和html
javascript·css·html
余生H1 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼1 小时前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js
程序员-珍1 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发