入门
介绍
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 } }
- 我们可以将这些类型扩展放在一个 .ts 文件,或是一个影响整个项目的 *.d.ts 文件中
选项式 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()
- 注入一个由祖先组件提供的值 #