Vue2
<son v-model='message'></son>
message:123
-
给子组件绑定v-model
-
子组件默认通过value接收message(需写props接收value),通过this.$emit('input','456')修改
props:['value'],//默认接收v-model的值为value,此行可省略
methods:{
handleClick(){
this.$emit('input',456)
}
}
- 子组件也可自定义model改变props和事件名称,修改时通过this.$emit(‘model绑定的event名称’,'修改的值')
model:{
prop:myValue,
event:'myChange'
},
props:['myValue'],
methods:{
handleClick(){
this.$emit('myChange',456)
//此时父组件data中的message已被改为456
}
}
3.如不通过model自定义,可通过
完善:
- 此时组件的V-model 父 -> 子已实现,接下来需要实现 子 -> 父,即 子组件中修改数据同步父组件数据
<input v-model="childValue">我是子组件中的标签<input/>
computed:{
childValue:{
get(){
return this.myValue
//通过get set劫持,使用childValue时拿的是父v-model的值,
},
set(val){
this.$emit('myChange',val)
// 对数据进行修改时触发model事件,修改父v-model的值,此时组件的v-model就实现了。
}
}
}
vue3
Vue3和vue2稍有不同,Vue2默认接收是value,Vue3(setup语法)中是modelValue,vue3中不需要自定义model,固定语法 this.$emit('update:model-value',val)
父
<Son v-model:myValue="message">
// 子组件 set劫持时,this.$emit('update:modelValue',val)
可通过给v-model传参更改默认modelValue值,同时实现传递多个v-model
<son v-model:name="" v-model:age.modifiers=""></son>
此时son组件可以接收props,name和age
<son>组件
props:{
name:String,
age:String
},
methods:{
setName(){
this.$emit('update:name',123)
},
setAge(){
this.$emit('update:age',123)
}
}
扩展:可传递v-model修饰符,可根据修饰符对v-model值进行处理。 加了 .modifiers 之后可以接收一个props为modelModifiers对象,通过this.modelModifiers.modifiers 是否为true做一些逻辑处理(如果加了修饰符为true)
3.完善一下 vue3二次封装组件
1.组件的v-model
1. 方法1
// Comp
<el-dialog v-bind="$attrs">
</el-dialog>
<Comp v-model="visible"></Comp>
// 给Comp传入v-model 自动实现了组件的v-model。因为 vue3的$attrs同时继承了attrs和emit
2.方法2
// vue3.4新出的defineModel()钩子,使用起来很简单
子组件中
// Comp
const visible = defineModel()
<el-dialog v-model="visible">
</el-dialog>
<Comp v-model="visible"></Comp>
// 这样写就实现了组件的v-model,代替了以前的computed劫持modelValue写法,其实他的原理实现基本逻辑也是定义了一个visible变量和watch变量时emit('update:modelValue')
那么改变prop的默认值以及类型呢,也比较简单直接传入即可
const visible = defineModel({
type:Boolean,
default:false,
require:false
})
如何定义多个v-model,那么第一个参数就不是对象了,而是一个字符串
const visible1 = defineModel('visible1',{type:Boolean})
const visible2 = defineModel('visible2',{type:Boolean})
<Comp v-model:visible1="visible1" v-model:visible2="visible2"/>
2.插槽处理
// Comp
// 常规做法:循环$slots 声明插槽并将接收到的数据传递给 当前源组件的插槽(el-dialog)。 #[slot]是v-slot:[slot]的简写
<el-dialog v-bind="$attrs">
<template v-for="(_,slot) in $slots" #[slot]="slotProps">
<slot :name="slot" v-bind="slotProps"></slot>
</template>
//如果组件有预设好的插槽 加上v-if即可
<template #footer v-if="!$slots.footer">
我是二次封装时预设好的插槽,此处内容从props中渲染。如果父组件没自定义footer时显示这个逻辑
</template>
</el-dialog>
// 新的尝试。上面的写法无非就是一段虚拟dom,那么就可以采用js的方式实现
<Comp></Comp>
<script lang="ts" setup>
// 用箭头函数包裹是因为,h函数在setup中是没有effecttive环境的。所以在函数调用时 是template中的render函数中,所以就是有响应式的
const Comp = ()=>{
h(ElDialog,useAttrs(),useSlots())
}
// 或者直接使用动态组件 一行代码可以搞定。h函数从vue引入,ElDialog从element plus组件库引入,也可以是自己写的普通组件。
<component :is="h(ElDialog,$attrs,$slots)"></component>
</script>
3.暴露组件的方法
//常规做法1:创建一个instance。获取到组件的$.expoxed push进去。在defineExpose中导出。
//常规做法2:合并组件的ref直接导出,带来的弊端是如果组件有v-if,那么compRef可能为null。
<script lang="ts" setup>
const obj = {}
const compRef = ref()
onMounted(() => {
Object.assign(obj,compRef.value )
})
defineExpose({
obj
})
</script>
----------------------
// 新的做法。返回一个代理函数,外部组件访问时会走到代理方式,
defineExpose({},{
get(target,key){
return compRef.value?.[key]
},
has(target,key){
return key in compRef.value
}
})
4.h函数的使用
1.定义
// h函数返回一个vnode.可以通过component is渲染到页面上。
// 接收3个参数
// 第一个参数可以是 html元素 也可以是组件
// 第二个参数是对象 传递给div或组件的属性方法
// 第三个参数 则接收一个vnode的list 可以 渲染出来
const Comp = ()=>h('div', {
style:{
color: 'red'
}
},[h('span', {}, 'aaa'),h('span', {}, 'bbb')])
}
作为vnode使用:<component :is="Comp"></component>
作为组件使用:
<Comp :count="5">
<div>我是插槽</div>
</Comp>
如何接收参数呢
const Comp:FunctionalComponent<{ count: number}> = (props, ctx) => {
return ()=>h('div', {
style:{
color: 'red'
}
},props.count)
}
2.作用域插槽和事件处理
<Comp :count="5">
<div>我是插槽</div>
<template #footer="a">footer的插槽传参 {{ a }}</template>
</Comp>
const Comp:FunctionalComponent<{ count: number}> = (props, {slots}) => {
const a =ref('作用域插槽')
return ()=>h('div', {
onClick(){
console.log('元素或者子组件传递过来的事件');
},
style:{
color: 'red'
}
},[slots?.footer(a.value),'后面是默认插槽', slots?.default?.() ])
}
3.h函数渲染组件时的处理
<Comp >
<div>我是默认插槽</div>
<div>{{ props.msg }}</div>
<slot name="footer" a="我是组件传递给插槽的值"></slot>
</Comp>
const Comp:FunctionalComponent<{ count: number}> = (props, {slots}) => {
return ()=>h(HellowWorld, {
msg:'我是传给helloword的值',
onFoo(val){
console.log('元素或者子组件传递过来的事件',val);
}
},{
default:slots.default,
footer:({a})=>h('div', {}, '自定义的footer插槽'+a)
})
}
4.在vue3中的用法
// 写法1
const Comp = defineComponent({
name: 'HeaderMenu',
props: {
id: number,
},
render(props, ctx) {
// ctx为上下文对象。包含了attrs emits和slots
return ()=>h('div', {
class:'my-class'
}, ctx.$slots.default())
}
})
// 写法2
const Comp = defineComponent((props,ctx) => {
return ()=>h('div', {}, ctx.$slots.default() + props.id,)
}, {
id: number
})
// 写法3(推荐 省事儿)
const Comp = (props,ctx)=>{
return ()=> h('div',{onFoo:(val)=>{console.log('子组件传过来的emit参数')}},'')
}
h函数的第二个参数可以是传递给属性的值或者传递给组件的参数。亦或者是监听组件的emit。
比如 Comp emit('onFoo')