Skip to content

Vue 开发笔记

Vue 2/3 开发相关笔记,基于我开发的 directixvue-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 2Vue 3
bindbeforeMount
insertedmounted
update(移除,使用 updated)
componentUpdatedupdated
unbindunmounted

常用指令实现

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>

相关项目

MIT Licensed