Skip to content
On this page

Vue3高级知识

1、API变更

TIP

Vue3 的全局 API 已经发生了变化。

  • Vue2没有app的概念,我们定义的应用只是通过new Vue()创建的根 Vue 实例, Vue3需要通过createApp()方法来创建一个应用实例,也就是根实例
  • Vue3 中移除了Vue.component使用app.component来注册全局组件
  • Vue3 中移除了Vue.use使用app.use来注册全局插件
  • Vue3 中移除了Vue.directive使用app.directive来注册全局指令
  • Vue3 中移除了Vue.filter使用app.config.globalProperties来代理使用

1.1、创建根实例

js
import { createApp } from 'vue'
const app = createApp(App)
app.mount('#app')

1.2、注册全局组件

js
import { createApp } from 'vue';
import MyComponent from './MyComponent.vue';

const app = createApp();
app.component('my-component', MyComponent);
app.mount('#app');

1.3、注册全局插件

vue
import { createApp } from 'vue';

createApp(App)
  .use(store)
  .use(router)
  .mount('#app')

1.4、注册指令

1.4.1、指令钩子

js
const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted() {},
  // 绑定元素的父组件更新前调用
  beforeUpdate() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated() {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount() {},
  // 绑定元素的父组件卸载后调用
  unmounted() {}
}

1.4.2、钩子参数

指令的钩子会传递以下几种参数:

js
- el:指令绑定到的元素。这可以用于直接操作 DOM。
- binding:一个对象,包含以下 property:
  - value:传递给指令的值。例如在 v-my-directive=1 + 1” 中,值是 2
  - oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
  - arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 “foo”。
  - modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
  - instance:使用该指令的组件实例。
  - dir:指令的定义对象。
- vnode:代表绑定元素的底层 VNode。
- prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

1.4.3、全局指令

js
const app = createApp(App)
app.directive('bg', {
 	mounted: (el) => el.style.backgroundColor = 'blue'
})

1.4.4、局部指令(非setup语法糖)

vue
<div>
  <input type="text" v-bg1>
</div>
js
<script lang="ts">
export default {
  // name: 'MyComponent',
  directives: {
      bg1: {
        mounted(el)=>el.style.backgroundColor = 'blue'
      }
  },
  setup() {

  }
}
</script>

示例:

1.4.5、局部指令(setup语法糖)

vue
<div>
  <input type="text" v-bg>
</div>
js
<script setup lang="ts">
import { ref } from 'vue'

const vBg = {
  mounted(el) {
    el.style.backgroundColor = 'red'
  }
}
</script>

ts版本

vue
<template>
  <Dialog  v-move-directive="{background:'green',flag:show}"></Dialog>
</template>

 
const vMoveDirective: Directive = {
  created: () => {
    console.log("初始化====>");
  },
  beforeMount(...args: Array<any>) {
    // 在元素上做些操作
    console.log("初始化一次=======>");
  },
  mounted(el: any, dir: DirectiveBinding<Value>) {
    el.style.background = dir.value.background;
    console.log("初始化========>");
  },
  beforeUpdate() {
    console.log("更新之前");
  },
  updated() {
    console.log("更新结束");
  },
  beforeUnmount(...args: Array<any>) {
    console.log(args);
    console.log("======>卸载之前");
  },
  unmounted(...args: Array<any>) {
    console.log(args);
    console.log("======>卸载完成");
  },
};

示例:

1.4.6、指令的参数和修饰符

示例1:

vue
<div v-params:params.lazy="1"></div>
{{paramsStr}}
js
<script setup lang="ts">
import { ref } from 'vue'

const paramsStr = ref('')
const vParams = {
  mounted(el, { value, oldValue, arg, modifiers }) {
    paramsStr.value = `value:${value} - oldValue:${oldValue} - arg:${arg} - modifiers:${JSON.stringify(modifiers)}`
  }
}
</script>

示例2:

vue
<div v-color="{ bgColor: 'red', color: 'yellow' }">this is a v-color directive</div>
js
<script setup lang="ts">
const vColor = {
  mounted(el, { value }) {
    el.style.backgroundColor = value.bgColor
    el.style.color = value.color
  }
}
</script>
this is a v-color directive

1.4.7、函数简写

TIP

mountedupdated时触发相同行为,而不关心其他的钩子函数

vue
<template>
  <A v-move="{ background: value }"></A>
</template>
   
<script setup lang='ts'>
import { ref, Directive, DirectiveBinding } from 'vue'
let value = ref<string>('')
type Dir = {
   background: string
}
const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
   el.style.background = binding.value.background
}
</script>

1.5、注册过滤器

1.5.1、全局过滤器

vue
<div >¥{{$filters.numFormat(111)}}</div>
js
const app = createApp(App)
app.config.globalProperties.$filters={
  numFormat(value) {
    return value+''
  }
}

1.5.2、局部过滤器

vue
<span>{{ filters('hello') }}</span>
js

示例:

HELLO

1.6、按键修饰符

Vue3 内置了许多键码别名,你可以使用这些键码别名代替真实的键码值。下面是常用的键码别名列表:

  • .enter: 回车键
  • .tab: Tab键
  • .delete (捕获“删除”和“退格”键)
  • .esc: Esc
  • .space: 空格
  • .up: 上键
  • .down: 下键
  • .left: 左键
  • .right: 右键
  • .ctrl: Ctrl
  • .alt: Alt
  • .shift: Shift
  • .meta (Windows 键和 Command 键)

WARNING

Vue3不在支持keyCode需要使用对应的alias

1.6.1、单个修饰符

vue
<input type="text" v-on:keyup.enter="enterAction" :style="{ border: '1px solid skyblue' }" />
或者
<input type="text" @keyup.enter="enterAction" :style="{ border: '1px solid red' }" />
js
const enterAction = () => {
  alert('keyup.enter')
}

示例:

1.6.2、多个修饰符

vue
<div @keydown.ctrl.alt.space="onSpace"></div>

1.6.3、自定义键ma按键修饰符

1.7、事件监听

WARNING

在Vue3中$on$off$once被移除,事件监听推荐使用三方库mitt

安装

js
npm install mitt -S

使用

js
import mitt from 'mitt'

export const emitter = mitt()

emitter.on('foo', () => {
  
})

// emitter.emit('foo')

跨组件交互

js
import emitter from './compoennt'

emitter.emit('foo')

2、数据响应式

2.1、ref

TIP

在Vue中,ref是一个用于创建响应式数据的函数。它接收一个参数,即需要创建引用的值,并返回一个响应式对象。

2.1.1、锚点

vue
<div ref="ref1"></div>

<script setup>
import { ref } from 'vue'
const ref1 = ref()
</script>

2.1.2、自定义ref

vue
import { customRef } from 'vue'
function MyRef<T>(value: T) {
  return customRef((track, trigger) => {
    return {
      get() {
        // 追踪value
        track();
        return value;
      },
      set(newValue: T) {
        value = newValue;
        // 触发更新
        trigger();
      }
    }
  });
}

使用

vue
let keyWord = MyRef<string>("1111");

3、插槽Slot

TIP

插槽(Slot)是一种用于在父组件中插入子组件内容的机制。通过插槽,您可以定义一些占位符,在父组件中填充具体的内容,以实现动态组件的复用和定制

3.1、默认插槽

父组件

js
<template>
  <div>
    <ChildComponent>
      <p>这是插槽内容</p>
    </ChildComponent>
  </div>
</template>

子组件

js
<template>
  <slot></slot>
</template>

3.2、具名插槽

父组件

js
<ChildComponent>
  <template v-slot:header>
    <h3>这是头部插槽</h3>
  </template>
  
  <template #content>
    <p>这是内容插槽</p>
  </template
</ChildComponent>

子组件

js
<template>
  <div>
    <p>下面是两个具名插槽:</p>
    <slot name="header"></slot>
    <slot name="content"></slot>
  </div>
</template>

3.3、作用域插槽

父组件

js
<ChildComponent>
  <template v-slot:header="{user}">
    <h3>这是头部插槽</h3>
  </template>
  或者
  <template #header="{user}">
    <h3>这是头部插槽</h3>
  </template>
</ChildComponent>

子组件

js
<template>
  <div>
    <p>下面是两个具名插槽:</p>
    <slot name="header" :user="item"></slot>
  </div>
</template>

4、组件

4.1、render渲染函数

4.1.1、组件渲染

sfc组件

vue
<script>
import { defineComponent, h } from 'vue';

export default defineComponent({
  name: 'MyComponent',
  data() {
    return {
      message: 'Hello, world!'
    };
  },
  render() {
    return h('div', [
      h('h1', this.message),
    ])
  }
});
</script>

也可以直接定义

js
<script setup lang="ts">
import { ref, h } from 'vue'
const MyComponent2 = {
  render() {
    return h('div', [
      h('h1', { style: { color: 'red', marginBottom: '20px' } }, 'Hello, world!'),
    ])
  }
}
</script>

示例:

Hello, world!

4.1.2、事件处理

vue
<script>
import { defineComponent, h } from 'vue';

export default defineComponent({
  name: 'MyComponent',
  data() {
    return {
      message: 'Hello, world!'
    };
  },
  render() {
    return h('div', [
      h('h1', this.message),
      h('button', {
        style: {
          color: 'red',
          border: '1px solid skyblue',
          padding: '5px 20px',
        },
        onClick: () => {
          alert('---');
          this.message = 'Hello, world!!!!';
        }
      }, 'Click me')
    ])
  }
});
</script>

示例:

Hello, world!

4.1.3、slots

vue
<MyComponent3>
  <span>default</span>
</MyComponent3>
js
<script setup lang="ts">
import { ref, h } from 'vue'
const MyComponent3 = {
  render() {
    return h('div', [
      h('div', [
        this.$slots.default()
      ])
    ])
  }
}
</script>

示例:

default

4.2、函数组件

4.2.1、组件渲染

vue
<script>
import { h } from 'vue'
function Heading(props, { attrs,slots }) {
  return h(`h${props.level}`, attrs, slots)
}
// Heading.props = ['level']
Heading.props = {
  level: {
    type: String,
    default: '1'
  }
}
export default Heading
</script>
vue
<FunctionComponent level="1">你好</FunctionComponent>
<FunctionComponent level="2">你好</FunctionComponent>
<FunctionComponent level="3">你好</FunctionComponent>
<FunctionComponent level="4">你好</FunctionComponent>
<FunctionComponent level="5">你好</FunctionComponent>

示例:

你好

你好

你好

你好

你好

4.2.2、事件处理

vue
<script>
import { h } from 'vue'
function Heading(props, { attrs,slots }) {
  return h(`h${props.level}`, {
    ...attrs,
    onClick: () => {
      console.log('click')
    }
  }, slots)
}
// Heading.props = ['level']
Heading.props = {
  level: {
    type: String,
    default: '1'
  }
}
export default Heading
</script>

示例:

你好

4.3、组件白名单

TIP

Vue3中检测自定义元素发生在模版编译时,如果添加一些自定义元素或者忽略警告需要配置isCustomElement选项

4.3.1 vue-cli

js
rules: [
  {
    test: /\.vue$/,
    use: 'vue-loader',
    options: {
      compilerOptions: {
        isCustomElement: tag => tag === 'my-component',
      }
    }
  }
]

4.3.2 vite

TIP

在vite项目中配置vueCompilerOptions选项

修改vite.config.js

js
module.exports = {
  vueCompilerOptions: {
    isCustomElement: tag => tag === 'my-component',
  }
}

4.4、异步组件

4.4.1、定义一个异步组件

vue
import { defineAsyncComponent } from 'vue'

const asyncPage = defineAsyncComponent(() => import('./async.vue'))

4.4.2、带配置的异步组件

vue
import ErrorComponent from './ErrorComponent.vue'
import LoadingComponent from './LoadingComponent.vue'

const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./async.vue'),
  delay: 100,
  timeout: 200,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})

5、示例

5.1、自定义渲染器

TIP

Vue3中支持自定义渲染器,可以自定义组件的渲染

该例子是将数据渲染到canvas上

html
// index.html
<body>
  <div id="demo" />
</body>
vue
// CanvasApp.vue
<template>
  <piechart @click="handleClick" :data="state.data" :x="200" :y="200" :r="200"></piechart>
</template>

<script>
import { reactive } from 'vue'

export default {
  name: 'CanvasApp',
  setup() {
    const state = reactive({
      data: [
        { name: '大专', count: 200, color: 'brown' },
        { name: '本科', count: 100, color: 'blue' },
        { name: '硕士', count: 50, color: 'green' },
        { name: '博士', count: 30, color: 'red' }
      ]
    })
    const handleClick = () => {
      state.data.push({
        name: '高中', count: 30, color: 'orange'
      })
    }
    return {
      state,
      handleClick
    }
  }
}
</script>
js
// main.js
import { createApp, createRenderer } from 'vue'

const nodeOps = {
  // 处理元素创建
  createElement(tag, data, children) {
    return {tag}
  },
  // 处理元素插入
  insert(child, parent, anchor) {
    // 1、如果是子元素,不是真实dom,只需将子元素插入到父元素中
    child.parent = parent
    if (!parent.childs) {
      parent.childs = [child]
    } else {
      parent.childs.push(child)
    }
    // 2、如果是真实dom,需要进行绘制
    if (parent.nodeType === 1) {
      draw(child)
      // 事件处理
      if (child.onClick) {
        canvas.addEventListener('click', () => {
          child.onClick()
          setTimeout(() => {
            draw(child)
          }, 0)
        })
      }
    }
    return
  },
  remove(el) {},
  createText(text) {},
  setText(el, text) {},
  setElementText(el, text) {},
  parentNode(el) {},
  nextSibling(el) {},
  querySelector(el, selector) {},
  setCopeIn(el, text) {},
  cloneNode(el) {},
  insertStateContent(el, text) {},
  // 属性更新
  patchProps(el, key, preValue, nextValue) {
    el[key] = nextValue
  },
}

// 绘制
const draw = (el, noClear) => {
  if (!noClear) {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
  }
  if (el.tag === 'piechart') {
    let { data, x, y, r } = el;
    let total = data.reduce((a, b) => a + b, 0);
    let start = 0,
        end = 0
    data.forEach((item, index) => {
      end += item.count / total * 360
      drawPieChart(start, end, item.color, x, y, r)
      drawPieChartText(item.name, (start + end)/2, x, y, r)
      start = end
    })
  }
  el.childs && el.childs.forEach(child => {
    draw(child, true)
  })
}

const d2a = (n) => {
  return n * Math.PI / 180
}

const drawPieChart = (start, end, color, cx, cy, r) => {
  let x = cx + Math.cos(d2a(start)) * r
  let y = cy + Math.sin(d2a(start)) * r
  ctx.beginPath()
  ctx.moveTo(cx, cy)
  ctx.lineTo(x, y)
  ctx.arc(cx, cy, r, d2a(start), d2a(end), false)
  ctx.fillStyle = color
  ctx.fill()
  ctx.stroke()
  ctx.closePath()
}

const drawPieChartText = (text, position, cx, cy, r) => {
  ctx.beginPath()
  let x = cx + Math.cos(d2a(position)) * r/1.25 - 20
  let y = cy + Math.sin(d2a(position)) * r/1.25
  ctx.fillstyle = '#000'

  ctx.font = '20px 微软雅黑'
  ctx.fillText(text, x, y)
  ctx.closePath()
}

const renderer = createRenderer(nodeOps)
let ctx, canvas;

function createCanvasApp(App) {
  const app = renderer.createApp(App)
  const mount = app.mount
  app.mount = function (selector) {
    // 创建canvas并插入画布
    canvas = document.createElement('canvas')
    ctx = canvas.getContext('2d')
    // 设置画布的宽高
    canvas.width = 600
    canvas.height = 600
    document.querySelector(selector).appendChild(canvas)
    // 执行挂载
    mount(canvas)
  }
  return app
}

createCanvasApp(CanvasApp).mount('#demo')

5.2、自定义reactive

vue
const myReactive = <T extends object>(obj: T) => {
  return new Proxy(obj, {
    get(target, key, receiver) {
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      return Reflect.set(target, key, value, receiver)
    }
  })
}

5.3、自定义effect

vue
<div>{{ effectValue }}</div>
<button :style="{ color: 'red', border: '1px solid skyblue' }" @click="changeName">改变name</button>

const myReactive = <T extends object>(obj: T) => {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key)
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return res
    }
  })
}

let activeEffect; 
const myEffect = (fn: Function) => {
  const _effect = () => {
    activeEffect = _effect
    fn()
  }
  _effect()
}

// 收集依赖
const targetMap = new WeakMap()
const track = (target, key) => {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  dep.add(activeEffect)
}

// 触发依赖更新
const trigger = (target, key) => {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }
  let dep = depsMap.get(key)
  if (!dep) {
    return
  }
  dep.forEach(effect => {
    effect()
  })
}

const effectValue = ref()
const myObj = myReactive({
  name: 'zw',
  age: 18
})
myEffect(() => {
  effectValue.value = myObj.name + myObj.age
})
const changeName = () => {
  myObj.name = 'min'
}

示例:

zw18

5.4、自定义computed

vue
<div>{{ computedValue.value }}</div>
<button :style="{ color: 'red', border: '1px solid skyblue' }" @click="changeName">改变name</button>

const myReactive = <T extends object>(obj: T) => {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver) as object
      track(target, key)
      if (res != null && typeof res == 'object') {
        return myReactive(res)
      }
      return res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return res
    }
  })
}

interface Options {
   scheduler?: Function
}

let activeEffect; 
const myEffect = (fn: Function, options:Options) => {
  const _effect = () => {
    activeEffect = _effect
    const res = fn()
    return res
  }
  _effect.options = options
  _effect()
  return _effect
}

// 收集依赖
const targetMap = new WeakMap()
const track = (target, key) => {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  dep.add(activeEffect)
}

// 触发依赖更新
const trigger = (target, key) => {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }
  let dep = depsMap.get(key)
  if (!dep) {
    return
  }
  dep.forEach(effect => {
    if (effect.options && effect.options.scheduler) {
      effect.options.scheduler()
    } else {
      effect()
    }
  })
}

const myComputed = (getter: Function) => {
  let _value = myEffect(getter, {
    scheduler: () => { _dirty = true }
  })
  let catchValue
  let _dirty = true
  class ComputedRefImpl {
    get value() {
      if (_dirty) {
        catchValue = _value()
        _dirty = false
      }
      return catchValue
    }
  }
  return new ComputedRefImpl()
}

const effectValue = ref()
const myObj = myReactive({
  name: 'zw',
  age: 18
})
myEffect(() => {
  effectValue.value = myObj.name + myObj.age
  // console.log(myObj.name, myObj.age)
})
const changeName = () => {
  myObj.name = 'min'
  // console.log('--------',myObj.name)
}
const computedValue = myComputed(() => myObj.name + myObj.age)

示例:

zw18

5.5、自定义Event Bus

vue
type BusClass<T> = {
    emit: (name: T) => void
    on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol 
type List = {
    [key: BusParams]: Array<Function>
}
class Bus<T extends BusParams> implements BusClass<T> {
    list: List
    constructor() {
        this.list = {}
    }
    emit(name: T, ...args: Array<any>) {
        let eventName: Array<Function> = this.list[name]
        eventName.forEach(ev => {
            ev.apply(this, args)
        })
    }
    on(name: T, callback: Function) {
        let fn: Array<Function> = this.list[name] || [];
        fn.push(callback)
        this.list[name] = fn
    }
}
 
export default new Bus<number>()

5.6、拖拽(指令示例)

vue
<div class="drag">
  <div v-move class="box">
    <div class="header"></div>
    <div>内容</div>
  </div>
</div>

const vMove: Directive = {
  mounted(el: HTMLElement) {
    // header
    const moveEl = el.firstElementChild as HTMLElement
    const mouseDown = (e: MouseEvent) => {
      //鼠标点击物体那一刻相对于物体左侧边框的距离=点击时的位置相对于浏览器最左边的距离-物体左边框相对于浏览器最左边的距离
      const x = e.clientX - el.offsetLeft
      const y = e.clientY - el.offsetTop
      // 监听拖拽事件
      const mouseMove = (e: MouseEvent) => {
        el.style.left = e.clientX - x + 'px'
        el.style.top = e.clientY - y + 'px'
      }
     
      document.addEventListener("mousemove", mouseMove)
      document.addEventListener("mouseup", () => {
        document.removeEventListener("mousemove", mouseMove)
      })
    }
    // 监听鼠标按下
    moveEl.addEventListener("mousedown", mouseDown)
  },
}

示例:

内容

5.7、按钮权限控制(自定义指令)

vue
<button v-has-show="'shop:create1'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:delete'">删除</button>

// 按钮权限
const permission = [
  'zw:shop:edit',
  'zw:shop:create',
  'zw:shop:delete'
]
const vHasShow: Directive<HTMLElement, string> = (el, bingding) => {
  if (!permission.includes(`zw:${bingding.value}`)) {
    el.style.display = 'none'
  }
}

示例:

5.8、图片懒加载(自定义指令)

vue
<div :style="{ width: '100%', height: '400px', overflow: 'auto' }">
  <div v-for="item in arr">
    <img height="400" :data-index="item" v-lazy="item" width="360" alt="">
  </div>
</div>

// 图片懒加载
const arr = [
  'https://images.unsplash.com/photo-1682685797507-d44d838b0ac7?ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80',
  'https://images.unsplash.com/photo-1691951171253-128bde131aaa?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1472&q=80',
  'https://plus.unsplash.com/premium_photo-1674582279349-901af56ed59b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1379&q=80'
]
const vLazy: Directive<HTMLElement, string> = async(el,bingding) => {
  const url = await import('/public/logo.png')
  el.src = url.default
  const observer = new IntersectionObserver(entries => {
    if (entries[0].intersectionRatio > 0 && entries[0].isIntersecting) {
      setTimeout(() => {
        el.src = bingding.value
        observer.unobserve(el)
      }, 2000)
    }
  })
  observer.observe(el)
}

示例:

5.9、自定义插件

https://xiaoman.blog.csdn.net/article/details/123300264

Loading.vue

vue
<template>
    <div v-if="isShow" class="loading">
        <div class="loading-content">Loading...</div>
    </div>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue';
const isShow = ref(false)//定位loading 的开关
 
const show = () => {
    isShow.value = true
}
const hide = () => {
    isShow.value = false
}
//对外暴露 当前组件的属性和方法
defineExpose({
    isShow,
    show,
    hide
})
</script>
    
<style scoped lang="less">
.loading {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.8);
    display: flex;
    justify-content: center;
    align-items: center;
    &-content {
        font-size: 30px;
        color: #fff;
    }
}
</style>

Loading.ts

ts
import {  createVNode, render, VNode, App } from 'vue';
import Loading from './index.vue'
 
export default {
    install(app: App) {
        //createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
        const vnode: VNode = createVNode(Loading)
        //render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
        render(vnode, document.body)
        // Vue 提供的全局配置 可以自定义
        app.config.globalProperties.$loading = {
            show: () => vnode.component?.exposed?.show(),
            hide: () => vnode.component?.exposed?.hide()
        }
 
    }
}

main.ts

ts
import Loading from './components/loading'
 
let app = createApp(App)
 
app.use(Loading)
 
type Lod = {
    show: () => void,
    hide: () => void
}
//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core' {
    export interface ComponentCustomProperties {
        $loading: Lod
    }
}
 
app.mount('#app')

使用:

vue
<template>
 
  <div></div>
 
</template>
 
<script setup lang='ts'>
import { ref,reactive,getCurrentInstance} from 'vue'
const  instance = getCurrentInstance()  
instance?.proxy?.$Loading.show()
setTimeout(()=>{
  instance?.proxy?.$Loading.hide()
},5000)
 
 
// console.log(instance)
</script>
<style>
*{
  padding: 0;
  margin: 0;
}
</style>

5.10、自定义Vue use

ts
import type { App } from 'vue'
import { app } from './main'
 
interface Use {
  install: (app: App, ...options: any[]) => void
}
 
const installedList = new Set()
 
export function MyUse<T extends Use>(plugin: T, ...options: any[]) {
    if(installedList.has(plugin)){
      return console.warn('重复添加插件',plugin)
    }else{
        plugin.install(app, ...options)
        installedList.add(plugin)
    }
}

5.11、图片下载(自定义hook)

vue
<img class="imgHook" src="/public/logo.png" alt="">

// 图片下载hook
type ImgOptions = {
  el: string
}

type Return = {
  BaseUrl: string | null
}

const imgBase64Hook = (option: ImgOptions): Promise<Return> => {
  return new Promise((resolve, reject) => {
    onMounted(() => {
      const file: HTMLImageElement = document.querySelector(option.el) as HTMLImageElement
      file.onload = () => {
        resolve({
          BaseUrl: toBase64(file)
        })
      }
    })
    const toBase64 = (el: HTMLImageElement): string => {
      const canvas: HTMLCanvasElement = document.createElement('canvas')
      const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
      canvas.width = el.width
      canvas.height = el.height
      ctx.drawImage(el, 0, 0, canvas.width,canvas.height)
      return canvas.toDataURL('image/png')
    }
  })
}

// 使用
imgBase64Hook({
  el: '.imgHook'
}).then((res) => {
  console.log(res)
})

示例:

5.12、监听元素变化(自定义hook+指令)

ts
import { App, defineComponent, onMounted } from 'vue'
 
function useResize(el: HTMLElement, callback: (cr: DOMRectReadOnly,resize:ResizeObserver) => void) {
    let resize: ResizeObserver
        resize = new ResizeObserver((entries) => {
            for (let entry of entries) {
                const cr = entry.contentRect;
                callback(cr,resize)
            }
        });
        resize.observe(el)
}
 
const install = (app: App) => {
    app.directive('resize', {
        mounted(el, binding) {
            useResize(el, binding.value)
        }
    })
}
 
useResize.install = install
 
export default useResize

6、自定义元素(defineCustomElement)

告知vue这是一个自定义Component 跳过组件检查

ts
/*vite config ts 配置*/
vue({
   template:{
     compilerOptions:{
         isCustomElement:(tag)=> tag.includes('my-')
      }
    }
})

父组件

vue
<template>
    <div>
        <my-btn :title=" JSON.stringify(name) "></my-btn>
    </div>
</template>
 
<script setup lang='ts'>
import { ref, reactive, defineCustomElement } from 'vue'
//自定义元素模式  要开启这个模式,只需要将你的组件文件以 .ce.vue 结尾即可
import customVueVue from './components/custom-vue.ce.vue'
const Btn = defineCustomElement(customVueVue)
customElements.define('my-btn', Btn)
const name = ref({a:1})
</script>

子组件

vue
<template>
  <div>
    hello {{title}}
  </div>
</template>
 
<script setup lang='ts'>
import { ref, reactive } from 'vue'
defineProps<{
    title:string
}>()
</script>

Released under the MIT License.