Skip to content

Vue 3 基础

动态组件

使用 <component>is 属性实现动态组件:

vue
<template>
  <!-- value 改变时切换 -->
  <component :is="value">
</template>

自定义指令

使用时机

只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。

指令的任务是,在表达式的值变化时响应式地更新 DOM。自定义指令允许我们扩展 Vue 的功能,直接操作 DOM。

注册

自定义指令的注册分为局部注册和全局注册两种方式:

局部注册

<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以当作自定义指令使用。

如下:

vHighlight 可以在模板中以 v-highlight 的形式使用

vue
<script setup>
// 在模板中启用 v-highlight
const vHighlight = {
  mounted: (el) => {
    el.classList.add('is-highlight')
  }
}
</script>

<template>
  <p v-highlight>This sentence is important!</p>
</template>

全局注册

js
const app = createApp({})

// 使 v-highlight 在所有组件中都可用
app.directive('highlight', {
  /* ... */
})

指令钩子

自定义指令对应的函数中,存在以下几种钩子函数(即生命周期函数):

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

参数说明

  • el:指令绑定的 DOM 元素
  • binding:一个对象,包含以下属性:
    • value:传入指令的值
    • oldValue:之前的值(仅在 beforeUpdateupdated 时存在)
    • arg:传递给指令的参数(如 v-my-directive:foo 中的 foo
    • modifiers:修饰符对象(如 v-my-directive.foo.bar{ foo: true, bar: true }
    • instance:使用该指令的组件实例
    • dir:指令的定义对象
  • vnode:绑定元素的底层 VNode
  • prevVnode:之前的渲染中指令所绑定元素的 VNode(仅在 beforeUpdateupdated 时存在)

示例

Ruoyi 中复制文本指令
js
/**
* v-copyText 复制文本内容
* Copyright (c) 2022 ruoyi
*/

export default {
  beforeMount(el, { value, arg }) {
    if (arg === "callback") {
      el.$copyCallback = value;
    } else {
      el.$copyValue = value;
      const handler = () => {
        copyTextToClipboard(el.$copyValue);
        if (el.$copyCallback) {
          el.$copyCallback(el.$copyValue);
        }
      };
      el.addEventListener("click", handler);
      el.$destroyCopy = () => el.removeEventListener("click", handler);
    }
  }
}

function copyTextToClipboard(input, { target = document.body } = {}) {
  const element = document.createElement('textarea');
  const previouslyFocusedElement = document.activeElement;

  element.value = input;

  // Prevent keyboard from showing on mobile
  element.setAttribute('readonly', '');

  element.style.contain = 'strict';
  element.style.position = 'absolute';
  element.style.left = '-9999px';
  element.style.fontSize = '12pt'; // Prevent zooming on iOS

  const selection = document.getSelection();
  const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);

  target.append(element);
  element.select();

  // Explicit selection workaround for iOS
  element.selectionStart = 0;
  element.selectionEnd = input.length;

  let isSuccess = false;
  try {
    isSuccess = document.execCommand('copy');
  } catch { }

  element.remove();

  if (originalRange) {
    selection.removeAllRanges();
    selection.addRange(originalRange);
  }

  // Get the focus back on the previously focused element, if any
  if (previouslyFocusedElement) {
    previouslyFocusedElement.focus();
  }

  return isSuccess;
}

获取 DOM 元素

模板中使用 ref 属性获取 DOM 元素。注意:需要在组件挂载后!!!

vue
<template>
  <div ref="divRef"></div>
</template>

<script setup>
import { ref } from 'vue';

const divRef = ref<HTMLElement | null>(null);

onMounted(() => {
  // 组件挂载后,divRef.value 才是 DOM 元素
  if (divRef.value) {
    console.log(divRef.value.offsetHeight); // 获取高度示例
  }
});
</script>

结合 TypeScript 使用

props

vue
<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})

props.foo // string
props.bar // number | undefined
</script>
vue
<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>

------------- 👇 也可以将类型写入接口中 ---------------

<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()
</script>

使用基于类型的声明时,无法声明 props 的默认值。需要通过响应式 Props 解构(3.5+版本)或 withDefaults (3.4及更低版本)编译器宏声明默认值。

ts
interface Props {
  msg?: string
  labels?: string[]
}

const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()
ts
interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

emits

vue
<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])

// 基于选项
const emit = defineEmits({
  change: (id: number) => {
    // 返回 `true` 或 `false`
    // 表明验证通过或失败
  },
  update: (value: string) => {
    // 返回 `true` 或 `false`
    // 表明验证通过或失败
  }
})

// 基于类型
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
  change: [id: number]
  update: [value: string]
}>()
</script>

v-bind 绑定多个属性

v-bind 是最基本且常用的内置指令。除了最常用在模版内的缩写 :attribute 外,可以使用 v-bind="object" 对象语法绑定多个属性。

示例:

vue
<template>
  <div v-bind="attributes">Hello, world!</div>
</template>

<script setup>
  const attributes = {
    id: 'my-element',
    class: 'highlight',
    style: 'color: red; font-size: 20px;'
  };
</script>

局部刷新效果

原理:组件重载

  1. 局部刷新的组件绑定 key
  2. 监听数据变化,变化后修改 key
  3. key 发生变化,组件重载,实现刷新效果

生命周期

Vue3 中 setup 替代了 Vue2 的 beforeCreatecreated

更多参考:生命周期钩子 | Vue.js

如有转载请标注本站地址