vue3 的常用 hooks
大约 3 分钟
vue3 的常用 hooks
获取有序的 refs 数组
import { ref, Ref, onBeforeUpdate } from 'vue'
export function useRefs<T = Element>() {
const refs = ref([]) as Ref<T[]>
const cache: Array<(el: unknown) => void> = []
onBeforeUpdate(() => {
refs.value = []
})
const setRefs = (index: number) => {
if (!cache[index]) {
cache[index] = (el: unknown) => {
refs.value[index] = el as T
}
}
return cache[index]
}
return [refs, setRefs] as const
}
useMyGlobalState
全局数据共享
import { effectScope } from 'vue'
const useMyGlobalState = <
T extends (...args: any) => any,
P extends Parameters<T>
>(
fn: T
) => {
let initialized = false
let state: any
let scope = effectScope(true)
return ((...arg: P) => {
if (!initialized) {
state = scope.run(() => fn(...(arg as any[])))
initialized = true
}
return state
}) as T
}
export { useMyGlobalState }
useResizeObserver
/**
* @param target - 监听的目标元素
* @param callback - 回调函数
* @param options - 选项
*/
import {
ComponentPublicInstance,
Ref,
computed,
toValue,
watch,
getCurrentScope,
onScopeDispose,
} from 'vue'
type MybeElementRef =
| HTMLElement
| Ref<HTMLElement | undefined>
| ComponentPublicInstance
const useSupported = (key: keyof typeof window) =>
typeof window !== 'undefined' && key in window
const useResizeObserver = (
target: MybeElementRef | MybeElementRef[],
callback: ResizeObserverCallback,
options: ResizeObserverOptions = {}
) => {
// 判断一下是否支持的resizeObserver
const isSupported = useSupported('ResizeObserver')
const scope = getCurrentScope()
let observer: ResizeObserver | undefined
const unrefElemnt = (target: MybeElementRef): HTMLElement => {
const el = toValue(target)
return (el as ComponentPublicInstance)?.$el ?? el
}
const cleanup = () => {
if (observer) {
observer.disconnect()
observer = undefined
}
}
const targets = computed(() =>
Array.isArray(target)
? target.map((el) => unrefElemnt(el))
: [unrefElemnt(target)]
)
// 监听数组变化然后重新监听
const stopWatch = watch(
targets,
(els) => {
cleanup()
if (isSupported) {
observer = new ResizeObserver(callback)
for (const _el of els) {
_el && observer!.observe(_el, options)
}
}
},
{ immediate: true, flush: 'post', deep: true }
)
const stop = () => {
cleanup()
stopWatch()
}
if (scope) {
onScopeDispose(stop)
}
}
export { useResizeObserver }
useIntersectionObserver
监听元素是否进入到可视区域
useIntersectionObserver
import {
ComponentPublicInstance,
ObjectDirective,
Ref,
computed,
defineComponent,
getCurrentScope,
h,
onScopeDispose,
reactive,
ref,
toValue,
watch,
} from 'vue'
type MybeElementRef =
| Element
| Ref<HTMLElement | undefined>
| ComponentPublicInstance
| undefined
| Document
const defaultWindow = window
const useSupported = (key: keyof typeof window) =>
typeof window !== 'undefined' && key in window
const noop = () => {}
const useIntersectionObserver = (
target: MybeElementRef | MybeElementRef[],
callback: IntersectionObserverCallback,
options: IntersectionObserverInit & { immediate?: boolean } = {}
) => {
const {
root,
rootMargin = '0px',
threshold = 0.1,
immediate = true,
} = options
const isSupported = useSupported('IntersectionObserver')
const scope = getCurrentScope()
const unrefElement = (target: MybeElementRef): HTMLElement => {
const el = toValue(target)
return (el as ComponentPublicInstance)?.$el ?? el
}
const targets = computed(() => {
const _target = toValue(target)
return (Array.isArray(_target) ? _target : [_target]).map((el) =>
unrefElement(el)
)
})
let cleanup = noop
const isActive = ref(immediate)
const stopWatch = isSupported
? watch(
() => [targets.value, unrefElement(root!), isActive.value] as const,
([targets, root]) => {
cleanup()
if (!isActive.value) return
if (!targets.length) return
const observer = new IntersectionObserver(callback, {
root: unrefElement(root),
rootMargin,
threshold,
})
targets.forEach((el) => el && observer.observe(el))
cleanup = () => {
observer.disconnect()
cleanup = noop
}
},
{ immediate, flush: 'post' }
)
: noop
const stop = () => {
cleanup()
stopWatch()
isActive.value = false
}
if (scope) {
onScopeDispose(stop)
}
return {
isSupported,
isActive,
pause() {
cleanup()
isActive.value = false
},
resume() {
isActive.value = true
},
stop,
}
}
UseElementVisibilityComponents
import {
ComponentPublicInstance,
ObjectDirective,
Ref,
computed,
defineComponent,
getCurrentScope,
h,
onScopeDispose,
reactive,
ref,
toValue,
watch,
} from 'vue'
const UseElementVisibility = defineComponent({
name: 'UseElementVisibility',
props: ['as'],
setup(props, { slots }) {
const elementIsVisible = ref(false)
const target = ref<HTMLElement>()
useIntersectionObserver(target, ([{ isIntersecting }]) => {
if (isIntersecting && elementIsVisible.value === false) {
elementIsVisible.value = isIntersecting
}
})
const data = reactive({
isVisible: elementIsVisible,
})
return () => {
if (slots.default)
return h(props.as || 'div', { ref: target }, slots.default(data))
}
},
})
vIntersectionObserver
import {
ComponentPublicInstance,
ObjectDirective,
Ref,
computed,
defineComponent,
getCurrentScope,
h,
onScopeDispose,
reactive,
ref,
toValue,
watch,
} from 'vue'
type BindingValueFunction = IntersectionObserverCallback
type BindingValueArray = [BindingValueFunction, IntersectionObserverInit]
const vIntersectionObserver: ObjectDirective<
HTMLElement,
BindingValueFunction | BindingValueArray
> = {
mounted(el, binding) {
if (typeof binding.value === 'function')
useIntersectionObserver(el, binding.value)
else useIntersectionObserver(el, ...binding.value)
},
}
useVMdole
双向绑定hooks
import { UnwrapRef, getCurrentInstance, nextTick, ref, watch } from 'vue'
function cloneFnJSON<T>(source: T): T {
return JSON.parse(JSON.stringify(source))
}
const isDef = <T = any>(val?: T): val is T =>
typeof val !== 'undefined' && val !== null
interface UseVMoelOptions<V = any> {
defaultValue?: V
/**
* @description 触发前的校验器
*/
shouldEmit?: (v: V) => boolean
}
/**
*
* @param props 传入的props
* @param key 要进行v-model的key
* @param emits 传入的emits
* @param options 选项
* @returns 响应式数据
*/
const useVModel = <P extends object, K extends keyof P, E extends Function>(
props: P,
key: K,
emits: E,
options: UseVMoelOptions<P[K]> = {}
) => {
const { defaultValue, shouldEmit } = options
const vm = getCurrentInstance()
const _emit = emits || vm?.emit
// 复制
const cloneFn = (value: any) => {
return isDef(value)
? typeof value === 'object'
? cloneFnJSON(value)
: value
: value
}
// 获取内容
const getValue = () =>
isDef(props[key]) ? cloneFn(props[key]) : defaultValue
// 拼接事件名字
const eventName = `update:${key.toString()}`
const proxy = ref<P[K]>(getValue()!)
// 是否更新
let isUpdating = false
const triggerEmit = (value: P[K]) => {
if (shouldEmit) {
if (shouldEmit(value)) _emit(eventName, value)
} else {
_emit(eventName, value)
}
}
watch(
() => props[key!],
(v) => {
if (!isUpdating) {
isUpdating = true
;(proxy as any).value = cloneFn(v) as UnwrapRef<P[K]>
nextTick(() => (isUpdating = false))
}
}
)
watch(
proxy,
(v) => {
if (!isUpdating && v !== props[key!]) triggerEmit(v as P[K])
},
{ deep: true }
)
return proxy
}
export default useVModel