defineModel
前言
随着 Vue3.4
版本的发布,defineModel
也正式转正了。它可以简化父子组件之间的双向绑定
,是目前官方推荐的双向绑定实现方式。
defineModel 使用
在开发的过程中,如果有需要通过子组件进行状态更新
的话,v-model
是一个绕不开的点。以前的v-model
是这样用的 👇
Vue3中得 v-model 默认解析成 :modelValue 与 @update:modelValue
<!-- Father.vue -->
<template><span>num:{{ num }}</span><hr /><Child v-model="num" />
</template><script lang="ts" setup>
import { ref } from 'vue'
import Child from './Child.vue'
const num = ref<number>(0)
</script>
<!-- Child.vue -->
<template>num: {{ modelValue }}<button @click="onClick">count</button>
</template><script lang="ts" setup>
const $props = defineProps<{ modelValue: number }>()
const $emits = defineEmits<{(e: 'update:modelValue', newCount: number): void// 注册update:modelValue事件,作为状态更新的回调
}>()
function onClick() {$emits('update:modelValue', $props.modelValue + 1)// 状态更新时发布事件
}
</script>
在有了defineModel
之后,我们就可以在Child.vue
中这样实现 👇
<!-- Child.vue -->
<template>num: {{ num }}<button @click="onClick">count</button>
</template><script lang="ts" setup>
// 一步到位,完成事件注册和监听状态变化并发布事件
const num = defineModel({ type: Number, default: 0 })
// 单个v-model绑定等价于如下
// const num = defineModel('modelValue', { type: Number, default: 0 })
function onClick() {num.value += 1
}
</script>
defineModel
如何实现多个 v-model
绑定 👇
<!-- Father.vue -->
<template><span>num1: {{ num1 }}</span><span>num2: {{ num2 }}</span><hr /><Child v-model:num1="num1" v-model:num2="num2" />
</template><script lang="ts" setup>
import { ref } from 'vue'
import Child from './Child.vue'
const num1 = ref<number>(0)
const num2 = ref<number>(0)
</script>
<!-- Child.vue -->
<template>num1: {{ num1 }} num2: {{ num2 }}<button @click="onClick">+1</button>
</template><script lang="ts" setup>
const num1 = defineModel('num1', { type: Number, default: 20 })
const num2 = defineModel('num2', { type: Number, default: 20 })
// 一步到位,完成事件注册和监听状态变化并发布事件
function onClick() {num1.value += 1num2.value += 1
}
</script>
实现原理
<!-- Father.vue -->
<template><span>num:{{ num }}</span><hr /><Child v-model="num" />
</template><script lang="ts" setup>
import { ref } from 'vue'
import Child from './Child.vue'
const num = ref<number>(0)
</script>
<!-- Child.vue -->
<template>num: {{ num }}<button @click="onClick">count</button>
</template><script lang="ts" setup>
import { ref, watch } from 'vue'
const $props = defineProps<{ modelValue: number }>()
const $emits = defineEmits<{(e: 'update:modelValue', newCount: number): void
}>()const num = ref()watch(() => $props.modelValue,() => {num.value = $props.modelValue},{// 回调函数会在watch创建时立即执行一次immediate: true}
)
watch(num, () => {$emits('update:modelValue', num.value)
})function onClick() {num.value += 1
}
</script>
等同于如下
<!-- Child.vue -->
<template>num: {{ num }}<button @click="onClick">count</button>
</template><script lang="ts" setup>
const num = defineModel({ type: Number, default: 0 })function onClick() {num.value += 1
}
</script>
-
defineModel
其实就是在子组件内定义了一个ref变量num
和modelValue
的props。 -
watch
了props中的modelValue
,watch创建时立即执行一次将props
中的modelValue
赋值给num
。当props
中的modelValue
的值改变后会同步更新num
变量的值。 -
watch
了ref变量num
,当在子组件内改变num
变量的值后会抛出update:modelValue
事件 -
父组件收到这个事件后就会更新父组件中对应的变量值。
其实defineModel
的源码中是使用 customRef 和 watchSyncEffect 去实现的,我这里是为了让大家能够更容易的明白defineModel
的实现原理才举的ref
和watch
的例子。