在 Vue 2.2 中,我们引入了 model 组件选项,允许组件自定义用于 v-model 的 prop 和事件。在 Vue 3 中,双向数据绑定的 API 已经标准化,以减少开发者在使用 v-model 指令时的混淆,并且更加灵活。
Vue2 中的 v-model 使用
在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 并触发 input 事件:
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :value="pageTitle" @change="pageTitle = $event" />
如果想要更改 prop 或事件名称,则需要在 ChildComponent 组件中添加 model 选项:
// ChildComponent.vue
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: String,
}
}
使用 v-bind.sync
对某个 prop 进行“双向绑定”,使用 update:xxx 抛出事件。
<ChildComponent :visible.sync="dialogVisible" />
// 相当于
<ChildComponent :visible="dialogVisible" @update:visible="visible = $event" />
// 数据更新
this.$emit('update:visible', false)
Vue3 中的 v-model 使用
在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
v-model 参数
若需要更改 model 的名称,可以为 v-model 传递一个参数,以作为组件内 model 选项的替代,也可以作为 .sync 修饰符的替代,而且允许我们在自定义组件上使用多个 v-model。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- 是以下的简写: -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
迁移策略
- 所有使用 .sync 的部分并将其替换为 v-model
- 对于所有不带参数的 v-model,请确保分别将 prop 和 event 命名更改为 modelValue 和 update:modelValue
<ChildComponent v-model="pageTitle" />
// ChildComponent.vue
export default {
props: {
modelValue: String // 以前是`value:String`
},
emits: ['update:modelValue'],
methods: {
changePageTitle(title) {
this.$emit('update:modelValue', title) // 以前是 `this.$emit('input', title)`
}
}
}
介绍
VueUse 是一个 Composition API 的工具集合,适用于 Vue 2.x 或者 Vue 3.x。常用 Hooks 如下:
useVModel
为组件创建自定义 v-model 的绑定, 组件接受 props 和 propName, 当该值被修改时, 组件会向父级发出更新事件, 即 props + emit -> ref.
import { useVModel } from '@vueuse/core'
const emit = defineEmits(['update:formModel'])
const modelValue = useVModel(props, 'formModel', emit)
console.log(modelValue.value) // props.formModel
modelValue.value = {} // emit('update:formModel', {})
useVModel 主要实现逻辑如下:
import { computed, getCurrentInstance } from 'vue'
export const useVModel = (props, propName) => {
const vm = getCurrentInstance().proxy
return computed({
set (value) {
vm.$emit(`update:${propName}`, value)
},
get () {
return props[propName]
}
})
}
除了 useVModel API 之外,还提供 useVModels API 相当于 toRefs(props), 数据改变会触发 emit.
import { useVModels } from '@vueuse/core'
const emit = defineEmits(['update:formModel', 'update:data'])
const { modelValue, data } = useVModels(props, emit)
console.log(data.value) // props.data
data.value = 'foo' // emit('data:data', 'foo')
useWindowScroll
监听窗口滚动距离, 从而实现吸顶效果.
import { useWindowScroll } from '@vueuse/core'
const { x, y } = useWindowScroll()
return { x, y }
useIntersectionObserver
检查元素是否在可视区域, 常用于数据懒加载, 实现无线信息流下拉加载更多.
<div ref="target">
<h1>Hello world</h1>
</div>
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export default {
setup() {
const target = ref(null)
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
targetIsVisible.value = isIntersecting
},
)
return {
target,
targetIsVisible,
}
},
}
- target: 被监听的 DOM 元素
- fn: 回调函数, 用于通知监听的动作, 其中第一个参数 isIntersecting 表示被监听的元素已经进入可视区
- options: 配置选项
stop 是停止观察是否进入或移出可视区域的行为
// src/hooks/useLazy/index.js
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export const useLazy = async (fn) => {
const target = ref(null) // 组件最外层元素
const result = ref([]) // 数据
const { stop } = useIntersectionObserver(target, ([{ isIntersecting }]) => {
if (isIntersecting) {
stop()
const { rows } = await fn()
result.value = rows
}
})
return { target, result }
}
使用如下:
<div ref="target"></div>
import { useLazy } from '@/hooks/useLazy'
const { result, target } = useLazy(fn)
// 接口请求
function fn () {...}
useFullscreen
切换全屏。
import { computed, unref } from 'vue'
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen()
const title = computed(() => {
return unref(isFullscreen) ? '退出全屏' : '全屏'
})
useRefHistory
跟踪对 ref 所做的每一步更改, 并将其存储在数组中, 从而实现撤销和重做功能。
import { ref } from 'vue'
import { useRefHistory } from '@vueuse/core'
const counter = ref(0)
const { history, undo, redo } = useRefHistory(counter)
counter.value += 1
await nextTick()
console.log(history.value)
useResizeObserver
拖拽元素的大小。
<div ref="el">{{ text }}</div>
import { ref } from 'vue'
import { useResizeObserver } from '@vueuse/core'
export default {
setup () {
const el = ref(null)
const text = ref('')
useResizeObserver(el, (entries) => {
const entry = entries[0]
const { width, height } = entry.contentReact
text.value = `width: ${width}, height: ${height}`
})
return { el, text }
}
}
useStorage
将 LocalStorage 和 SessionStorage 中的数据响应式。
import { useStorage } from '@vueuse/core'
const state = useStorage('userInfo', { name: 'zhangsan' })
state.value = null // 从 storage 中删除
createGlobalState API 可以全局存储数据。
// store.js
import { createGlobalState, useStorage } from '@vueuse/core'
export const useGlobalState = createGlobalState(
() => useStorage('vueuse-local-storage', 'initialValue'),
)
// component.js
import { useGlobalState } from './store'
export default defineComponent({
setup () {
const state = useGlobalState()
return { state }
}
})
useFetch
import { useFetch } from '@vueuse/core'
const { isFetching, error, data, execute, abort, canAbort } = useFetch(url, {
// 请求拦截
async beforeFetch({ url, options, cancel }) {...},
// 响应拦截
afterFetch(ctx) {},
// 拦截错误
onFetchError(ctx) {},
// 防止请求立即被触发
immediate: false
})
// 终止请求
setTimeout(() => {
canAbort.value && abort()
}, 1000)
// 在调用之前,防止触发请求
execute()
useDebounceFn
防抖函数。
import { useDebounceFn } from '@vueuse/core'
const handleSearch = useDebounceFn(search, 300)
// 查询方法
function search () {......}
onClickOutside
监听元素外部的点击事件。
<div ref="target">Hello world</div>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
export default {
setup() {
const target = ref(null)
function close (event) {
console.log(event)
}
onClickOutside(target, close)
return { target }
}
}
此外 onClickOutside 还可以作为组件使用,代码如下:
<template>
<onClickOutside @trigger="close">Click Outside of Me</onClickOutside>
</template>
<script setup>
import { onClickOutside } from '@vueuse/components'
function close () {...}
</script>
onKeyStroke
监听键盘事件。
<script setup>
import { onKeyStroke } from '@vueuse/core'
function handleClose () {...}
function handleEnter () {...}
function handleUp () {...}
function handleDown () {...}
onKeyStroke('Escape', handleClose);
onKeyStroke('Enter', handleEnter);
onKeyStroke('ArrowUp', handleUp);
onKeyStroke('ArrowDown', handleDown);
</script>
更多 Hooks 的使用,请查看官网:VueUse。