② 初识vue3.0:新特性讲解
1 vue3 新特性巡礼
1.1 性能提升
-
打包减小
-
初次渲染加快、更新加快
-
内存使用减少
得益于重写虚拟 DOM 的实现和 Tree shanking 的优化
1.2 Composition API
-
ref 和 reactive
-
computed 和 watch
-
新的生命周期函数
-
自定义函数 -- Hooks 函数
1.3 其他新增特性
-
Teleport -- 瞬移组件的位置
-
Suspense -- 异步加载组件的新福音
-
全局 API 的优化和修改
-
更多试验性特性
1.4 更好的 ts 支持
2 为什么会有 vue3
- vue2 遇到的难题
1. 随着功能的增长,复杂组件的代码变得难以维护


2. 随着复杂度的上升,带来的问题

mixin 的解决方案
const filterMixin = {
data() {
return {}
},
methods: {}
}
const paginationMixin = {
data() {
return {}
},
methods: {}
}
export default {
mixin: [ filterMixin, paginationMixin ]
}
mixin 的缺点
-
命名冲突
-
不清楚暴露出来变量的作用
-
重用到其他组件时会出现问题
3. vue2 对于 ts 的支持非常有限
3 vue3 - ref 的妙用
-
setup
-
ref:处理基本类型的数据
-
computed
import { ref, computed } from 'vue'
export default {
name: 'App',
setup() {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increase = () => {
count.value++
}
return {
count, double, increase
}
}
}
4 更近一步 - reactive
-
reactive:处理复杂类型的数据
-
reactive + toRefs(将对象属性转化为响应式属性)
使用 toRefs 保证 reactive 对象属性保持响应性
import { computed, reactive, toRefs } from 'vue'
interface DataProps {
count: number,
double: number,
increase: () => void
}
export default {
name: 'App',
setup() {
const data: DataProps = reactive({
count: 0,
increase: () => { data.count++ },
double: computed(() => data.count * 2)
})
// 只有 data 是响应式的 其属性非响应式
const refData = toRefs(data)
return {
...refData
}
}
}
5 vue3 响应式对象的新花样
响应式原理
- vue2
Object.definedProperty(data, 'count', {
get() {},
set() {}
})
- vue3
new Proxy(data, {
get(key) {},
set(key, value) {}
})
- vue3应用
import { computed, reactive, toRefs } from 'vue'
interface DataProps {
numbers: number[];
person: { name?: string };
}
export default {
name: 'App',
setup() {
const data: DataProps = reactive({
numbers: [0, 1, 2],
person: {}
})
data.numbers[0] = 5
data.person.name = 'viking'
const refData = toRefs(data)
return {
...refData
}
}
}
6 老瓶新酒 - 生命周期

在 setup 中使用的 hook 名称和原来生命周期的对应关系
-
beforeCreate
-> use setup() -
created
-> use setup() -
beforeMount
->onBeforeMount
-
mounted
->onMounted
-
beforeUpdate
->onBeforeUpdate
-
updated
->onUpdated
-
beforeUnmount
->onBeforeUnmount
-
unmounted
->onUnmounted
-
errorCaptured
->onErrorCaptured
-
renderTracked
->onRenderTracked
每次渲染后重新收集响应式依赖时执行 -
renderTriggered
->onRenderTriggered
每次触发页面重新渲染时执行
import { onMounted, onUpdated, onRenderTriggered, onRenderTracked } from 'vue'
export default {
name: 'App',
setup() {
onMounted(() => {
console.log('mounted')
})
onUpdated(() => {
console.log('updated')
})
onRenderTriggered(() => {
console.log('renderTriggered') // 点击按钮时触发
})
onRenderTracked(() => {
console.log('renderTracked') // 页面刷新时触发
})
}
}
7 侦测变化 - watch
- 一般用法
setup() {
const greetings = ref('')
const updateGreeting = () => {
greetings.value += 'Hello!'
}
watch(greetings, (newVal, oldVal) => {
console.log(newVal, oldVal);
document.title = 'updated '
})
return {
updateGreeting,
}
}
- 侦听 reactive 方法下的数据 -- getter 写法 -- 使用箭头函数
setup() {
const data: DataProps = reactive({
count: 0,
})
const greetings = ref('')
const updateGreeting = () => {
greetings.value += 'Hello!'
}
watch([greetings, () => data.count], (newVal, oldVal) => {
console.log(newVal, oldVal);
document.title = 'updated ' + greetings.value + data.count
})
// 只有 data 是响应式的 其属性非响应式
const refData = toRefs(data)
return {
updateGreeting,
...refData
}
}
8 模块化开发 -- 鼠标追踪器
hooks > useMousePosition.ts
import { reactive, toRefs, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
const data = reactive({
x: 0,
y: 0
})
const updateMouse = (e: MouseEvent) => {
data.x = e.pageX
data.y = e.pageY
}
onMounted(() => {
document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
const refData = toRefs(data)
return { ...refData }
}
export default useMousePosition
App.vue
import useMousePosition from './hooks/useMousePosition'
export default {
setup() {
const { x, y } = useMousePosition()
return {
x, y,
}
}
}
优点
-
可以清楚地知道 x y 的来源,这两个参数是干什么的
-
可以给 x y 设置任何别名,避免了命名冲突的风险
-
这段逻辑可脱离组件存在,只有逻辑代码不需要模板
9 模块化难度上升 - useURLLoader
hooks > useURLLoader.ts
import { ref } from 'vue'
import axios from 'axios'
function useURLLoader(url: string) {
const result = ref(null)
const loading = ref(true)
const loaded = ref(false)
const error = ref(null)
axios.get(url).then(rawData => {
loading.value = false
loaded.value = true
result.value = rawData.data
}).catch(e => {
error.value = e
loading.value = false
})
return {
result, loading, loaded, error
}
}
export default useURLLoader
App.vue
import useURLLoader from './hooks/useURLLoader'
export default {
setup() {
const { result, loading, loaded } = useURLLoader('https://dog.ceo/api/breeds/image/random')
return {
result, loading, loaded
}
}
}
10 模块化结合 typescript - 泛型改造
hooks > useURLLoader.ts
function useURLLoader<T>(url: string) {
const result = ref<T | null>(null)
// ...
}
export default useURLLoader
App.vue
- 展示狗狗图片
import useURLLoader from './hooks/useURLLoader'
interface DogResult {
message: string;
status: string;
}
export default {
name: 'App',
setup() {
const { result, loading, loaded } = useURLLoader<DogResult>('https://dog.ceo/api/breeds/image/random')
watch(result, () => {
if(result.value) {
console.log(result.value.message);
}
})
return {
result, loading, loaded
}
}
}
- 展示猫猫图片
import useURLLoader from './hooks/useURLLoader'
interface CatResult {
id: string;
url: string;
width: number;
height: number;
}
export default {
name: 'App',
setup() {
const { result, loading, loaded } = useURLLoader<CatResult[]>('https://api.thecatapi.com/v1/images/search?limit=1')
watch(result, () => {
if(result.value) {
console.log(result.value[0].url);
}
})
return {
result, loading, loaded
}
}
}
11 Typescript 对 vue3 的加持
使用 defineComponent 包裹组件 + setup 函数参数
- components > HelloWorld
import { defineComponent } from 'vue';
export default defineComponent({
name: 'HelloWorld',
props: {
msg: {
required: true,
type: String
}
},
setup(props, context) {
console.log(props.msg)
const { attrs, slots, emit } = context
}
});
12 组件 Teleport - 瞬间移动
Teleport
上有一个to
的属性,它接受一个 css query selector 作为参数,代表要把这个组件渲染到哪个 dom 元素中
12.1 原始 modal
组件
1. components > Modal.vue
<teleport to="#modal">
<div id="center">
<h1>this is a modal</h1>
</div>
</teleport>
2. App.vue
<!-- ... -->
<Modal />
<!-- ... -->
<script lang="ts">
import Modal from './components/Modal.vue'
export default {
components: { Modal }
// ...
}
</script>
3. public > index.html
<div id="app"></div>
<div id="modal"></div>
4. 渲染结果

12.2 实现 modal
的打开关闭
-
控制组件的显示与否 -- isOpen
-
自定义 content --
slot
-
关闭 modal -- 使用
emit
触发
1. components > Modal.vue
-
文档化 emits-- 一目了然要触发的事件
-
支持运行时检验
-
支持 自动补全
<teleport to="#modal">
<div id="center" v-if="isOpen">
<h1>
<slot>
this is a modal
</slot>
</h1>
<button @click="buttonClick">Close</button>
</div>
</teleport>
export default defineComponent({
props: {
isOpen: Boolean
},
// 更明确的显示组件的自定义事件
emits: {
'close-modal': null
},
setup(props, context) {
const buttonClick = () => {
context.emit('close-modal')
}
return {
buttonClick
}
}
})
2. App.vue
<Modal :isOpen="modalIsOpen" @close-modal="onModalClose">
My Modal !!!
</Modal>
<button @click="openModal">open Modal</button>
setup() {
const modalIsOpen = ref(false)
const openModal = () => {
modalIsOpen.value = true
}
const onModalClose = () => {
modalIsOpen.value = false
}
return {
modalIsOpen, openModal, onModalClose
}
}
13 组件 Suspense - 异步请求好帮手
- 组件使用
Suspense
,在 setup 中需要返回一个Promise
13.1 简单用法
1. components > AsyncShow.vue
<template>
<h1>{{ result }}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return new Promise ((resolve) => {
setTimeout(() => {
return resolve({
result: 42
})
}, 3000);
})
}
})
</script>
2. App.vue
<Suspense>
<template #default>
<async-show />
</template>
<template #fallback>
<h1>Loading !...</h1>
</template>
</Suspense>
import AsyncShow from './components/AsyncShow.vue'
export default {
components: { AsyncShow }
}
13.2 使用 async await
1. components > DogShow.vue
<template>
<img :src="result && result.message" />
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
async setup() {
const rawData = await axios.get('https://dog.ceo/api/breeds/image/random')
return {
result: rawData.data
}
}
})
</script>
2. App.vue
<Suspense>
<template #default>
<div>
<async-show />
<dog-show />
</div>
</template>
<template #fallback>
<h1>Loading !...</h1>
</template>
</Suspense>
import AsyncShow from './components/AsyncShow.vue'
import DogShow from './components/DogShow.vue'
export default {
components: { AsyncShow, DogShow }
}
13.3 Suspense 中的错误捕获
import { onErrorCaptured } from 'vue'
setup() {
const error = ref(null)
onErrorCaptured((e: any) => {
error.value = e
return true
})
return {
error
}
}
14 Provide / Inject
14.1 父子组件传值 -- prop

14.2 解决多层级传值 -- Provide/Inject
- Provide/Inject:提供了一种在组件之间共享此类值的方式,而不必通过组件树的每个层级显式地传递 props
目的是为共享那些被认为对于一个组件树而言是“全局”的数据

1. provide -- 提供
export default {
provide: {
message: 'hello!'
}
}
- 应用级提供
import { createApp } from 'vue'
const app = createApp({})
app.provide('message', 'hello!')
2. inject -- 注入
export default {
inject: ['message'],
data() {
return {
fullMessage: this.message
}
}
}
- 注入别名
export default {
inject: {
localMessage: {
from: 'message'
}
}
}
- 注入默认值
export default {
inject: {
message: {
from: 'message',
default: 'default value'
},
user: {
default: () => ({ name: 'John' })
}
}
}
3. 使用响应性
- 使用
computed()
函数提供一个计算属性 -- 使注入响应性地链接到提供者
import { computed } from 'vue'
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
return {
// explicitly provide a computed property
message: computed(() => this.message)
}
}
}
15 全局 API 修改
15.1 入口文件
1. vue2 的全局配置
import Vue from 'vue'
import App from './App.vue'
Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)
Vue.prototype.customProperty = () => {}
new Vue({
render: h => h(App)
}).$mount('#app')
不足
- 单元测试中,全局配置容易污染全局环境
- 在不同 App 中,难以共享一份有不同配置的 Vue 对象
2. vue3 的全局配置
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// app 就是一个 App 的实例,设置任何的配置是在不同的 app 实例上面的,不会像vue2 一样发生任何的冲突
app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
app.config.globalProperties.customProperty = () => {}
// 当配置结束以后,我们再把 App 使用 mount 方法挂载到固定的 DOM 的节点上。
app.mount(App, '#app')
15.2 全局配置 Vue.config -> app.config
15.3 全局注册类 API
- Vue.component -> app.component
- Vue.directive -> app.directive
15.4 行为扩展类
- Vue.mixin -> app.mixin
- Vue.use -> app.use
15.5 Global API Treeshaking
Tree Shaking -- 用于描述移除 js 上下文中的未引用代码
1. vue2
import Vue from 'vue'
Vue.nextTick(() => {})
const obj = Vue.observable({})
2. vue3
import Vue, { nextTick, observable } from 'vue'
Vue.nextTick // undefined
nextTick(() => {})
const obj = observable({})
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)