本文从v-model
指令的基本原理入手,逐步介绍到Vue 3.x composable
的组合使用。
<input
type="text"
:value="localStateProperty"
@change="localStateProperty = $event.target.value"
>
可以简化为
<input type="text" v-model="localStateProperty">
在组件中依然如此
<message-editor
:modelValue="message"
@update:modelValue="message = $event"
>
可以简化为
<message-editor v-model="message">
思想就是:传递一个prop给子组件,同时子组件反馈一个事件,父组件利用这个事件更新这个prop。
的确很麻烦,不过也没办法。prop是父组件的数据,子组件没法更改,只能通知父组件,让父组件亲自去处理。
上面是Vue2.x时代的普遍做法,那么Vue 3.x提供的新特性——组合API,会不会有更优雅的解决方式呢?一步步来看。
利用computed
中的set属性,能够简化template为v-model="message"
.
<template>
<label>
<input
type="text"
v-model="message"
>
<label>
</template>
<script>
import { computed } from 'vue'
export default {
props: {
'modelValue': String,
},
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
return {
message,
}
}
}
</script>
现在template很简洁,但setup()
有很多代码。这些代码是通用的,可以提取出来,当作boilerplate
.
composable的引入
composable
是从setup()
中抽离出的函数。
以后我们想实现上面的行为,就可以直接import,简化setup()
.
import { useModelWrapper } from '../utils/modelWrapper'
export default {
props: {
'modelValue': String,
}
setup(props, { emit }) {
return {
message: useModelWrapper(props, emit),
}
}
}
// modelWrapper.js
import { computed } from 'vue'
export function useModelWrapper(props, emit) {
return computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
}
进一步扩展功能,自定义名称
这样props可以为任意名称
import { computed } from 'vue'
export function useModelWrapper(props, emit, name = 'modelValue') {
return computed({
get: () => props[name],
set: (value) => emit(`update:${name}`, value)
})
}
<template>
<label>
<input type="text" v-model="message" >
<label> <label>
<input type="checkbox" v-model="isDraft"> Draft
</label>
</template>
<script>
import { useModelWrapper } from '../utils/modelWrapper'
export default {
props: {
modelValue: String,
draft: Boolean
},
setup(props, { emit }) {
return {
message: useModelWrapper(props, emit, 'modelValue'),
isDraft: useModelWrapper(props, emit, 'draft')
}
}
}
</script>
进一步封装,composable可以为另一个composable提供抽象
下面一段composable 实现了,一旦用户5s没有打字,消息就清空的功能。这里的参数message需要是reactive的。
// useMessageReset.js
function useMessageReset(message) {
let timeoutId
const reset = () => {
if (timeoutId) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => (message.value = ''), 5000)
watch(message, () => message.value !== '' && reset())
}
}
这样组件中就可以联合使用这两个composable
import { useModelWrapper } from '../utils/modelWrapper'
import { useMessageReset } from '../utils/messageReset'
export default {
props: { modelValue: Boolean },
setup(props, { emit }) {
const message = useModelWrapper(props, emit)
useMessageReset(message)
return { message }
}
}
一层层的封装,组合API的强大之处在逐渐体会。