Vue 开发笔记
Vue 2/3 开发相关笔记,基于我开发的 directix、vue-mount-plugin 等项目经验整理。
自定义指令
Vue 指令是扩展 Vue 功能的重要方式。我开发了 directix 提供丰富的指令库。
指令基础
ts
// Vue 3 指令定义
const myDirective = {
mounted(el, binding) {
// 指令挂载时执行
console.log(binding.value)
},
updated(el, binding) {
// 更新时执行
},
unmounted(el, binding) {
// 卸载时执行,清理工作
}
}
// 注册
app.directive('my-directive', myDirective)Vue 2 vs Vue 3 指令差异
| Vue 2 | Vue 3 |
|---|---|
| bind | beforeMount |
| inserted | mounted |
| update | (移除,使用 updated) |
| componentUpdated | updated |
| unbind | unmounted |
常用指令实现
v-debounce 防抖
ts
const vDebounce = {
mounted(el, binding) {
const { value, arg = 300 } = binding
let timer: number | null = null
el.addEventListener('input', () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => value(), arg)
})
}
}v-copy 复制
ts
const vCopy = {
mounted(el, binding) {
el.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(binding.value)
console.log('复制成功')
} catch (err) {
console.error('复制失败', err)
}
})
}
}v-click-outside 点击外部
ts
const vClickOutside = {
mounted(el, binding) {
el._clickOutside = (event: Event) => {
if (!el.contains(event.target as Node)) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
}更多指令
查看 directix 获取完整指令库:
v-debounce- 防抖v-throttle- 节流v-copy- 复制到剪贴板v-longpress- 长按事件v-lazy- 图片懒加载v-ripple- 水波纹效果v-click-outside- 点击外部检测
Composition API
Vue 3 的 Composition API 提供了更好的代码组织方式。
响应式基础
ts
import { ref, reactive, computed, watch } from 'vue'
// ref - 基本类型
const count = ref(0)
count.value++
// reactive - 对象
const state = reactive({
name: 'saqqdy',
age: 18
})
// computed
const double = computed(() => count.value * 2)
// watch
watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`)
})自定义 Hooks
ts
// hooks/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
const update = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}Vue 2/3 通用 Hooks
使用 use-demi 实现 Vue 2/3 通用 Hooks:
ts
import { useLocalStorage, useMouse, useCounter } from 'use-demi'
// localStorage
const [value, setValue] = useLocalStorage('key', 'default')
// 鼠标位置
const { x, y } = useMouse()
// 计数器
const { count, inc, dec, reset } = useCounter(0)组件开发
动态组件加载
使用 vue-mount-plugin 动态挂载组件:
ts
import { createMount } from 'vue-mount-plugin'
// 创建挂载实例
const mount = createMount(Component, {
props: { title: 'Hello' },
on: {
close: () => mount.hide()
}
})
// 显示/隐藏
mount.show()
mount.hide()
// 销毁
mount.destroy()组件通信
ts
// 父子通信 - props/emit
const Child = defineComponent({
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const update = (val) => emit('update:modelValue', val)
return { update }
}
})
// 跨组件 - provide/inject
const key = Symbol()
// 父组件
provide(key, 'value')
// 子组件
const value = inject(key)插槽
vue
<!-- 父组件 -->
<template>
<Card>
<template #header>
<h2>标题</h2>
</template>
<template #default>
<p>内容</p>
</template>
<template #footer>
<button>确定</button>
</template>
</Card>
</template>
<!-- 子组件 Card.vue -->
<template>
<div class="card">
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
</div>
</template>性能优化
虚拟列表
大数据列表使用虚拟滚动:
vue
<template>
<VirtualList :items="items" :item-size="50">
<template #default="{ item }">
<div class="item">{{ item.name }}</div>
</template>
</VirtualList>
</template>懒加载
vue
<script setup>
const AsyncComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>缓存组件
vue
<template>
<KeepAlive>
<Component :is="currentComponent" />
</KeepAlive>
</template>相关项目
- directix - Vue 2/3 指令库
- vue-mount-plugin - 组件动态挂载
- use-demi - Vue 2/3 通用 Hooks
- uni-use - uni-app Hooks