Vue3备忘清单


入门

介绍

  • Vue 是一套用于构建用户界面的渐进式框架
  • Vue 3.x 官方文档 (cn.vuejs.org)
  • Vue Router 4.x 官方文档 (router.vuejs.org)
  • Vue 2 官方文档 (v2.cn.vuejs.org)
  • 注意:Vue 3.x 版本对应 Vue Router 4.x 路由版本

通过 CDN 使用 Vue

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">{{ message }}</div>
<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref("Hello Vue3")
      return {
        message
      }
    }
  }).mount('#app')
</script>

使用 ES 模块构建版本

<div id="app">{{ message, ref }}</div>
<script type="module">
  import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  createApp({
    setup() {
      const message = ref("Hello Vue3")
      return {
        message
      }
    }
  }).mount('#app')
</script>

创建应用

  • 已安装 16.0 或更高版本的 Node.js
  • 以下指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具
    $ npm init vue@latest
    ✔ Project name: … <your-project-name>
    ✔ Add TypeScript? … No/Yes
    ✔ Add JSX Support? … No/Yes
    ✔ Add Vue Router for Single Page Application development? … No/Yes
    ✔ Add Pinia for state management? … No/Yes
    ✔ Add Vitest for Unit testing? … No/Yes
    ✔ Add Cypress for both Unit and End-to-End testing? … No/Yes
    ✔ Add ESLint for code quality? … No/Yes
    ✔ Add Prettier for code formatting? … No/Yes
    
    Scaffolding project in ./<your-project-name>...
    Done.
  • 安装依赖并启动开发服务器
    $ cd <your-project-name>
    $ npm install
    $ npm run dev
  • 当你准备将应用发布到生产环境时,请运行:
    $ npm run build
    # 此命令会在 ./dist 文件夹中为你的应用创建一个生产环境的构建版本

应用实例

  • 初始化应用
    import { createApp, ref } from 'vue'
    
    const app = createApp({
      setup() {
        const message = ref("Hello Vue3")
        return {
          message
        }
      }
    })
    app.mount('#app')
  • 挂载应用
    <div id="app">
      <button @click="count++">
        {{ count }}
      </button>
    </div>

模板语法

文本插值

<span>Message: {{ msg }}</span>
<!-- 使用的是 Mustache 语法 (即双大括号),每次 msg 属性更改时它也会同步 -->

原始 HTML

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
<!-- 双大括号{{}}会将数据解释为纯文本,使用 v-html 指令,将插入 HTML -->

Attribute 绑定

<div v-bind:id="dynamicId"></div>
<!-- 简写 -->
<div :id="dynamicId"></div>

布尔型 Attribute

<button :disabled="isButtonDisabled">
  Button
</button>

动态绑定多个值

  • 通过不带参数的 v-bind,你可以将它们绑定到单个元素上
    <script setup>
      import comp from "./Comp.vue"
      import {ref} from "vue"
      const a = ref("hello")
      const b = ref("world")
    </script>
    
    <template>
      <comp v-bind="{a, b}"></comp>
    </template>
  • 如果你是使用的 setup 语法糖。需要使用 defineprops 声名(可以直接使用a/b)
    const props = defineProps({
      a: String,
      b: String
    })

使用 JavaScript 表达式

{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

仅支持表达式(例子都是无效)

// 这是一个语句,而非表达式
{{ var a = 1 }}
// 条件控制也不支持,请使用三元表达式
{{ if (ok) { return message } }}

调用函数

<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

指令 Directives

<p v-if="seen">Now you see me</p>

参数 Arguments

<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>

绑定事件

<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>

动态参数

<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
这里的 attributeName 会作为一个 JS 表达式被动态执行

动态的事件名称

<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething">

修饰符 Modifiers

<form @submit.prevent="onSubmit">
  ...
</form>
<!-- .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault() -->

指令语法

v-on:submit.prevent="onSubmit"
──┬─ ─┬──── ─┬─────  ─┬──────
  ┆   ┆      ┆        ╰─ Value 解释为JS表达式
  ┆   ┆      ╰─ Modifiers 由前导点表示
  ┆   ╰─ Argument 跟随冒号或速记符号
  ╰─ Name 以 v- 开头使用速记时可以省略

响应式基础

声明状态

<div>{{ state.count }}</div>
import { defineComponent, reactive } from 'vue';

// `defineComponent`用于IDE推导类型
export default defineComponent({
  // setup 用于组合式 API 的特殊钩子函数
  setup() {
    const state = reactive({ count: 0 });

    // 暴露 state 到模板
    return {
      state
    };
  },
});

声明方法

<button @click="increment">
  {{ state.count }}
</button>
import { defineComponent, reactive } from 'vue';

export default defineComponent({
  setup() {
    const state = reactive({ count: 0 });

    function increment() {
      state.count++;
    }

    // 不要忘记同时暴露 increment 函数
    return {
      state,
      increment
    };
  },
})

setup语法糖

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

const state = reactive({ count: 0 })

function increment() {
  state.count++
}
</script>

<template>
  <button @click="increment">
    {{ state.count }}
  </button>
</template>
// setup 语法糖用于简化代码,尤其是当需要暴露的状态和方法越来越多时

用 ref() 定义响应式变量

  • reactive只能用于对象、数组和 Map、Set 这样的集合类型,对 string、number 和 boolean 这样的原始类型则需要使用ref
    import { ref } from 'vue';
    
    const count = ref(0);
    
    console.log(count); // { value: 0 }
    console.log(count.value); // 0
    count.value++;
    console.log(count.value); // 1
    const objectRef = ref({ count: 0 });
    
    // 这是响应式的替换
    objectRef.value = { count: 1 };
    const obj = {
      foo: ref(1),
      bar: ref(2)
    };
    // 该函数接收一个 ref
    // 需要通过 .value 取值
    // 但它会保持响应性
    callSomeFunction(obj.foo);
    
    // 仍然是响应式的
    const { foo, bar } = obj;
  • 在 html 模板中不需要带 .value 就可以使用
    <script setup>
    import { ref } from 'vue';
    
    const count = ref(0);
    </script>
    
    <template>
      <div>
        {{ count }}
      </div>
    </template>

有状态方法

import { reactive, defineComponent, onUnmounted } from 'vue';
import { debounce } from 'lodash-es';

export default defineComponent({
  setup() {
    // 每个实例都有了自己的预置防抖的处理函数
    const debouncedClick = debounce(click, 500);

    function click() {
      // ... 对点击的响应 ...
    }

    // 最好是在组件卸载时
    // 清除掉防抖计时器
    onUnmounted(() => {
      debouncedClick.cancel();
    });
  },
});

响应式样式

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

<template>
  <button @click="open = !open">Toggle</button>
  <div>Hello Vue!</div>  
</template>

<style scope>
  div{
    transition: height 0.1s linear;
    overflow: hidden;
    height: v-bind(open ? '30px' : '0px');
  }
</style>

响应式进阶 —— watch 和 computed

监听状态

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

const count = ref(0)
const isEvent = ref(false)

function increment() {
  state.count++
}

watch(count, function() {
  isEvent.value = count.value % 2 === 0
})
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
  <p>
    is event: {{ isEvent ? 'yes' : 'no' }}
  </p>
</template>

立即监听状态

watch(count, function() {
  isEvent.value = count.value % 2 === 0
}, {
  // 上例中的 watch 不会立即执行,导致 isEvent 状态的初始值不准确。
  // 配置立即执行,会在一开始的时候立即执行一次
  immediate: true
})

计算状态

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

const text = ref('')
// computed 的回调函数里,会根据已有并用到的状态计算出新的状态
const capital = computed(function(){
  return text.value.toUpperCase();
})
</script>

<template>
  <input v-model="text" />
  <p>to capital: {{ capital }}</p>
</template>

组件通信

defineProps

  • 子组件定义需要的参数

    <script setup>
    import { defineProps } from 'vue';
    
    // 这里可以将 `username` 解构出来,
    // 但是一旦解构出来再使用,就不具备响应式能力
    defineProps({
      username: String
    })
    </script>
    <template>
      <p>username: {{ username }}</p>
    </template>
  • 父组件参入参数

    <script setup>
    const username = 'vue'
    </script>
    <template>
      <children :username="username" />
    </template>

defineEmits

  • 子组件定义支持 emit 的函数
    <script setup>
    import { defineEmits, ref } from 'vue';
    
    const emit = defineEmits(['search'])
    const keyword = ref('')
    const onSearch = function() {
      emit('search', keyword.value)
    }
    </script>
    
    <template>
      <input v-model="keyword" />
      <button @click="onSearch">search</button>
    </template>
  • 父组件绑定子组件定义的事件
    <script setup>
    const onSearch = function(keyword){
      console.log(keyword)
    }
    </script>
    
    <template>
      <children @search="onSearch" />
    </template>

defineExpose

  • 子组件对父组件暴露方法
    <script setup>
    import { defineExpose, ref } from 'vue';
    
    const keyword = ref('')
    const onSearch = function() {
      console.log(keyword.value)
    }
    
    defineExpose({ onSearch })
    </script>
    
    <template>
      <input v-model="keyword" />
    </template>
  • 父组件调用子组件的方法
    <script setup>
    import { ref } from 'vue'  
    
    const childrenRef = ref(null)
    const onSearch = function() {
      childrenRef.value.onSearch()
    }
    </script>
    
    <template>
      <children ref='childrenRef' />
      <button @click="onSearch">search</button>
    </template>

Provide / Inject

  • 在应用中使用 ProvideKey
    import type { InjectionKey, Ref } from 'vue'
    
    export const ProvideKey = Symbol() as InjectionKey<Ref<string>>
  • 父组件为后代组件提供数据
    <script setup lang="ts">
    import { provide, ref } from 'vue'
    import { ProvideKey } from './types'
    
    const text = ref<string>('123')
    provide(ProvideKey, text)
    </script>
    
    <template>
      <input v-model="text" />
    </template>
  • 后代组件注入父组件提供的数据
    <script setup lang="ts">
    import { inject } from 'vue'
    import { ProvideKey } from './types'
    
    const value = inject(ProvideKey)
    </script>
    
    <template>
      <h4>{{value}}</h4>
    </template>

Vue 中使用 TypeScript

为组件的 props 标注类型

  • 当使用 <script setup> 时,defineProps() 宏函数支持从它的参数中推导类型
    <script setup lang="ts">
    const props = defineProps({
      foo: { type: String, required: true },
      bar: Number
    })
    
    props.foo // string
    props.bar // number | undefined
    </script>
  • 对同一个文件中的一个接口或对象类型字面量的引用:
    interface Props {/* ... */}
    
    defineProps<Props>()
  • Props 解构默认值
    export interface Props {
      msg?: string
      labels?: string[]
    }
    
    const props = withDefaults(defineProps<Props>(), {
      msg: 'hello',
      labels: () => ['one', 'two']
    })
  • 使用目前为实验性的响应性语法糖
    <script setup lang="ts">
    interface Props {
      name: string
      count?: number
    }
    
    // 对 defineProps() 的响应性解构
    // 默认值会被编译为等价的运行时选项
    const {
      name, count = 100
    } = defineProps<Props>()
    </script>

为组件的 emits 标注类型

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

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

为 ref() 标注类型

  • ref 会根据初始化时的值推导其类型:
    import { ref } from 'vue'
    import type { Ref } from 'vue'
    
    const year: Ref<string | number> = ref('2023')
    
    year.value = 2023 // 成功!

为 reactive() 标注类型

import { reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

const book: Book = reactive({
  title: 'Vue 3 指引'
})

为 computed() 标注类型

  • 你还可以通过泛型参数显式指定类型:
    const double = computed<number>(() => {
      // 若返回值不是 number 类型则会报错
    })

为事件处理函数标注类型

<script setup lang="ts">
function handleChange(event) {
  // `event` 隐式地标注为 `any` 类型
  console.log(event.target.value)
}
</script>

<template>
  <input
    type="text"
    @change="handleChange" />
</template>
  // 显式地为事件处理函数的参数标注类型
function handleChange(event: Event) {
  const target = event.target as HTMLInputElement
  console.log(target.value)
}

为 provide / inject 标注类型

import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'

const key = Symbol() as InjectionKey<string>
// 若提供的是非字符串值会导致错误
provide(key, 'foo')
// foo 的类型:string | undefined
const foo = inject(key)

为模板引用标注类型

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const el = ref<HTMLInputElement | null>(null)

onMounted(() => {
  el.value?.focus()
})
</script>

<template>
  <input ref="el" />
</template>

为组件模板引用标注类型

// MyModal.vue
<script setup lang="ts">
import { ref } from 'vue'

const isContentShown = ref(false)
const open = 
      () => (isContentShown.value = true)

defineExpose({
  open
})
</script>
  • 使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类
    // App.vue
    <script setup lang="ts">
    import MyModal from './MyModal.vue'
    
    type Modal = InstanceType<typeof MyModal>
    
    const modal = ref<Modal | null>(null)
    
    const openModal = () => {
      modal.value?.open()
    }
    </script>

选项式 API 为组件的 props 标注类型

import { defineComponent } from 'vue'

export default defineComponent({
  // 启用了类型推导
  props: {
    name: String,
    id: [Number, String],
    msg: { type: String, required: true },
    metadata: null
  },
  mounted() {
    // 类型:string | undefined
    this.name
    // 类型:number|string|undefined
    this.id
    // 类型:string
    this.msg
    // 类型:any
    this.metadata
  }
})
  • 使用 PropType 这个工具类型来标记更复杂的 props 类型
    import { defineComponent } from 'vue'
    import type { PropType } from 'vue'
    
    interface Book {
      title: string
      author: string
      year: number
    }
    
    export default defineComponent({
      props: {
        book: {
          // 提供相对 `Object` 更确定的类型
          type: Object as PropType<Book>,
          required: true
        },
        // 也可以标记函数
        callback: Function as PropType<(id: number) => void>
      },
      mounted() {
        this.book.title // string
        this.book.year // number
    
        // TS Error: argument of type 'string' is not
        // assignable to parameter of type 'number'
        this.callback?.('123')
      }
    })

选项式 API 为组件的 emits 标注类型

import { defineComponent } from 'vue'

type Payload = { bookName: string }

export default defineComponent({
  emits: {
    addBook(payload: Payload) {
      // 执行运行时校验
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // 类型错误
      })
      // 类型错误
      this.$emit('non-declared-event')
    }
  }
})

选项式 API 为计算属性标记类型

  • 计算属性会自动根据其返回值来推导其类型:
    import { defineComponent } from 'vue'
    
    export default defineComponent({
      data() {
        return {
          message: 'Hello!'
        }
      },
      computed: {
        greeting() {
          return this.message + '!'
        }
      },
      mounted() {
        this.greeting // 类型:string
      }
    })
  • 在某些场景中,你可能想要显式地标记出计算属性的类型以确保其实现是正确的:
    import { defineComponent } from 'vue'
    
    export default defineComponent({
      data() {
        return {
          message: 'Hello!'
        }
      },
      computed: {
        // 显式标注返回类型
        greeting(): string {
          return this.message + '!'
        },
    
        // 标注一个可写的计算属性
        greetingUppercased: {
          get(): string {
            return this.greeting.toUpperCase()
          },
          set(newValue: string) {
            this.message = newValue.toUpperCase()
          }
        }
      }
    })

选项式 API 为事件处理函数标注类型

import { defineComponent } from 'vue'

export default defineComponent({
  methods: {
    handleChange(event: Event) {
      console.log((event.target as HTMLInputElement).value)
    }
  }
})

选项式 API 扩展全局属性

import axios from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: typeof axios
    $translate: (key: string) => string
  }
}
  • 类型扩展的位置
    • 我们可以将这些类型扩展放在一个 .ts 文件,或是一个影响整个项目的 *.d.ts 文件中
      // 不工作,将覆盖原始类型。
      declare module 'vue' {
        interface ComponentCustomProperties {
          $translate: (key: string) => string
        }
      }
      
      // 正常工作。
      export {}
      declare module 'vue' {
        interface ComponentCustomProperties {
          $translate: (key: string) => string
        }
      }

选项式 API 扩展自定义选项

  • 某些插件,比如 vue-router,提供了一些自定义的组件选项,比如 beforeRouteEnter:
    import { defineComponent } from 'vue'
    
    export default defineComponent({
      beforeRouteEnter(to, from, next) {
        // ...
      }
    })
  • 如果没有确切的类型标注,这个钩子函数的参数会隐式地标注为 any 类型。我们可以为 ComponentCustomOptions 接口扩展自定义的选项来支持:
    import { Route } from 'vue-router'
    
    declare module 'vue' {
      interface ComponentCustomOptions {
        beforeRouteEnter?(
          to: Route,
          from: Route,
          next: () => void
        ): void
      }
    }

API 参考

全局 API - 应用实例

createApp()

  • 创建一个应用实例 #

createSSRApp()

  • 以 SSR 激活模式创建一个应用实例 #

app.mount()

  • 将应用实例挂载在一个容器元素中 #

app.unmount()

  • 卸载一个已挂载的应用实例 #

app.provide()

  • 提供一个可以在应用中的所有后代组件中注入使用的值 #

app.component()

  • 注册或获取全局组件 #

app.directive()

  • 注册或获取全局指令 #

app.use()

  • 安装一个插件 #

app.mixin()

  • 全局注册一个混入 #

app.version

  • 当前应用所使用的 Vue 版本号 #

app.config

  • 获得应用实例的配置设定 #

app.config.errorHandler

  • 为应用内抛出的未捕获错误指定一个全局处理函数 #

app.config.warnHandler

  • 为 Vue 的运行时警告指定一个自定义处理函数 #

app.config.performance

  • 在浏览器开发工具中追踪性能表现 #

app.config.compilerOptions

  • 配置运行时编译器的选项 #

app.config.globalProperties

  • 注册全局属性对象 #

app.config.optionMergeStrategies

  • 定义自定义组件选项的合并策略的对象 #

全局 API - 通用

version

  • Vue 版本号 #

nextTick()

  • 等待下一次 DOM 更新后执行回调 #

defineComponent()

  • 在定义 Vue 组件时提供类型推导的辅助函数 #

defineAsyncComponent()

  • 定义一个异步组件 #

defineCustomElement()

  • 和 defineComponent 接受的参数相同,不同的是会返回一个原生自定义元素类的构造器 #

组合式 API - setup()

组合式 API - 响应式: 核心

ref()

  • 返回一个 ref 对象 #

computed ()

  • 定义一个计算属性 #

reactive()

  • 返回一个对象的响应式代理 #

readonly()

  • 返回一个原值的只读代理 #

watchEffect()

  • 立即运行一个函数,同时监听 #

watchPostEffect()

  • watchEffect() 使用 flush: ‘post’ 选项时的别名。 #

watchSyncEffect()

  • watchEffect() 使用 flush: ‘sync’ 选项时的别名。 #

watch()

  • 侦听一个或多个响应式数据源 #

组合式 API - 响应式: 工具

isRef()

  • 判断是否为 ref #

unref()

  • 是 ref,返回内部值,否则返回参数本身 #

toRef()

  • 创建一个属性对应的 ref #

toRefs()

  • 将对象上的每一个可枚举属性转换为 ref #

isProxy()

  • 检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理 #

isReactive()

  • 检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。 #

isReadonly()

  • 检查传入的值是否为只读对象 #

组合式 API - 响应式: 进阶

shallowRef()

  • ref() 的浅层作用形式。 #

triggerRef()

  • 强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。 #

customRef()

  • 创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。 #

shallowReactive()

  • reactive() 的浅层作用形式。 #

shallowReadonly()

  • readonly() 的浅层作用形式。 #

toRaw()

  • 根据一个 Vue 创建的代理返回其原始对象。 #

markRaw()

  • 将一个对象标记为不可被转为代理。返回该对象本身。 #

effectScope()

  • 创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。 #

getCurrentScope()

  • 如果有的话,返回当前活跃的 effect 作用域。 #

onScopeDispose()

  • 在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。 #

组合式 API - 生命周期钩子

onMounted()

  • 组件挂载完成后执行 #

onUpdated()

  • 状态变更而更新其 DOM 树之后调用 #

onUnmounted()

  • 组件实例被卸载之后调用 #

onBeforeMount()

  • 组件被挂载之前被调用 #

onBeforeUpdate()

  • 状态变更而更新其 DOM 树之前调用 #

onBeforeUnmount()

  • 组件实例被卸载之前调用 #

onErrorCaptured()

  • 捕获了后代组件传递的错误时调用 #

onRenderTracked()

  • 组件渲染过程中追踪到响应式依赖时调用 #

onRenderTriggered()

  • 响应式依赖的变更触发了组件渲染时调用 #

onActivated()

  • 若组件实例是 <KeepAlive> 缓存树的一部分,当组件被插入到 DOM 中时调用 #

onDeactivated()

  • 若组件实例是 <KeepAlive> 缓存树的一部分,当组件从 DOM 中被移除时调用 #

onServerPrefetch()

  • 组件实例在服务器上被渲染之前调用 #

组合式 API - 依赖注入

provide()

  • 提供一个可以被后代组件中注入使用的值 #

inject()

  • 注入一个由祖先组件提供的值 #

文章作者: Damao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Damao !
  目录