创建vue3项目
- vue-cli 4.5.0以上
1 2 3
| vue create vue3-test 选择vue3项目即可 若需创建ts项目及自定义配置项可选Manually select features, 然后按提示操作创建,最后不要保存位默认配置。
|
- 创建vite+ts项目
1 2 3 4 5 6 7
| npm create vite@latest 或 yarn create vite 或 pnpm create vite
注意:node版本要大于14.18,最好16以上
|
初始化工程变化
main.js变化
1 2 3 4 5 6
| import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
|
.vue文件template不在必须要根标签包裹
1 2 3 4 5 6
| <template> <p></p> <span></span> </template>
|
组合式API
区别于vue2中options api(配置式)形式,如data,methods,computed…等.
组合式api统一写在setup函数中.
setup必须有返回值
setup执行早于beforeCreate, this为undefine
setup的入参:
- props: 即组件传参;需要正常定义props才能接收到
- context: 上下文;组件传参,插槽相关
- attrs 组件传参,没用props接收时,有值
- slots 插槽vnode
1 2 3 4
| <template slot="a"></template>
<template v-slot:a></template>
|
- emit 自定义事件触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { h } from 'vue' export default { name: 'App', props: ['msg'], emits: ['hello'], setup(props) { let name = 'victor'
function showSomething () { alert(name) }
return { name, showSomething }
} }
|
vue2配置式api的问题是模块比较分散,不同功能混合,(data中放的所以功能的数据,methods中放的所以功能的方法),分散,不集中.
vue3组合式api可通过书写顺序或hooks明显区分功能模块
数据响应式
setup中直接变量声明方式创建的数据并非响应式.需要
- ref: 处理基本数据类型(引用类型也可)
- reactive: 专门处理引用类型(深层响应)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { ref, reactive } from 'vue'
setup () { let name = ref('victor') let age = ref(18) let person = ref({ name: 'gss', age: 18 })
let person_reactive = reactive({ name: 'v', age: 20 })
function edit () { name.value = 'gss' person.value.name = 'victor' person_reactive.name = 'g' } }
|
经过ref函数处理后,数据变为响应式,但需要注意处理后的数据不再是原本类型,而是引用对象(RefImpl);里面包裹原有数据的值在value上;所以需要.value读写.
经过reactive函数处理后,数据变为proxy对象,响应式,能直接 . 读取
ref函数实现响应式原理同vue2,Object.defineProperty();
但对于对象的内层则是用Proxy实现;实际上是调用了reactive方法实现的.
打印上述person和person.value, person_reactive和person.value一样,如下图

- 为什么在template模板中不用.value读取数据呢
因为complie模板解析时能获取数据类型,RefImpl类型会自动.value
toRef & toRefs
将对象的某个属性单独做成响应式,并且保持和原对象的引用关系
简化模板插值写法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <p>{{person.name}}</p>
setup() { let person = reactive({ name: '**', ... }) return { person } }
<p>{{name}}</p> <p>{{job.type}}</p> <p>{{type}}</p>
setup() { let person = reactive({ name: '**', job: { type: 1 } ... }) let type = toRef(person.job, 'type') return { ...toRefs(person), type } }
|
shallowReactive & shallowRef
shallow: 浅层的
shallowReactive: 只响应对象的一层,不深层响应
shallowRef: 基础类型同ref, 不响应对象
1 2 3 4 5 6 7 8 9 10
| let obj = shallowReactive({ a:{ b:1 }, c:2 }) let num = shallowRef(18) let obj = shallowRef({ a:1 })
|
readonly & shallowReadonly
只读和浅层只读
1 2 3 4
| import { readonly, shallowReadonly } from 'vue' let person = {} person = readonly(person) person = shallowReadonly(person)
|
toRaw & markRow
还原和标记还原
- toRaw: 将reactive响应式数据还原普通数据
- markRow: 响应式数据添加新属性时,也是响应式,用此阻止新属性的响应
1 2 3 4 5
| let p = reactive({}) let p_raw = toRaw(p)
p.someObj = markRow({})
|
customRef
自定义ref,能够通过get,set实现相关逻辑控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { customRef } from 'vue' function myCustomData (value) { return customRef((track, trigger) => { return { get() { track() return value }, set(newValue) { value = newValue trigger() } } }) }
|
provide & inject
提供/注入, 实现祖/后代组件传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { provide } from 'vue' setup() { let p = {} provide('p', p) provide('fn', (cb) => {cb()}) }
import { inject } from 'vue' setup() { let = inject('p') let fn = inject('fn') fn(() => { console.log('后代组件相关逻辑,在祖组件中触发执行') }) }
|
判断是否响应式数据
- isRef
- isReactive
- isReadonly
- isProxy: 是否是reactive或readonly创建的数据
1 2 3 4 5 6
| let a = reactive({}) isReactive(a)
let b = toRaw(a) isReactive(b)
|
computed & watch & watchEffect
computed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { reactive, computed } from 'vue' setup () { let num = reactive({ a: 0, b: 0 }) num.c = computed(() => (num.a + num.b))
num.c = computed({ get() { return num.a + num.b }, set() {} })
return { num } }
|
watch & watchEffect
注意引用类型默认deep且不可取消,无法获取oldV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { ref, reactive, watch, watchEffect } from 'vue' setup () { let num = reactive({ a: 0, b: 0, obj: { a: 1 } })
let name = ref('victor') let age = 18 watch(name, (newV, oldV) => {}, { immediate: true }) watch([name, age], (newV, oldV) => { }, { immediate: true }) watch(num, (newV, oldV) => { }, { immediate: true }) watch(() => num.obj.a, (newV, oldV) => { })
watchEffect(() => { let n = name.value let _num = num.obj.a }) }
|
生命周期
vue2中的周期依然能在vue3中使用,除了beforeDestroy和destroyed改名为beforeUnmount和unmounted.
vue3中也能用组合式API形式调用.
setup中生命周期改动:
- 没有明确的beforeCreate和created, setup即相当于这两个周期
- 作为配置项还是能用beforeCreate和created的.
- setup早于beforeCreate
- 需引入对应周期函数,逻辑在回调函数中写
- 周期名都加on
1 2 3 4 5 6
| import { onMounted } from 'vue' setup() { onMounted (() => { console.log('onMounted') }) }
|
自定义hooks
hook: 一个函数, 是组合式api的封装. 类似vue2中mixin,因为vue3中配置项都是通过函数实现,所以hook非常方便.可以公共把数据操作,方法,包括用到的生命周期都能放到一个hook文件中.
步骤:
- 创建hooks文件夹
- 创建相关文件,命名一般以use开头(useSomething.js)
- 编写hook文件,暴露带有返回值的函数.
- 使用hook文件,引入-setup中调用即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { ref, onMounted } from 'vue' export default () => { let hookName = ref('victor')
const getHookName = () => { hookName.value += '!' } onMounted(() => { hookName.value += '!' }) return {hookName, getHookName} }
import useSomething from './hooks/useSomething' setup() { let { hookName, getHookName } = useSomething() return { hookName, getHookName } }
|
新组件
- fragment: 内置组件,不用手写,自动渲染处理,组件内不用div包裹的原因.没有外层div时就自动用fragment了
- teleport: 传送,将html结构传送到指定标签
1 2 3 4 5
| <teleport to="body"> <dialog>...</dialog> </teleport>
|
- suspense: 试验阶段,结合动态引入组件使用;基于插槽实现;可用于加loading状态;
- default插槽
- fallback插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <suspense> <template v-slot:default> <helloWord></helloWord> </template> <template v-slot:fallback> <div> loading </div> </template> </suspense>
import { defineAsyncComponent } from 'vue' const helloWord = defineAsyncComponent(() => (import('./helloWord')))
components:{ helloWord }
async setup() { cosnt p = new Promise((resolve, reject) => { resolve({ }) }) return p }
|
其他改变
- 全局api
- const app = createApp()

- 过度(transition)类名改变
- 移除keyCode按键编码
- 移除.native修饰符
- 因为现在子组件组定义事件也需要接收,所以当子组件接收click时就算自定义事件,不接收时就算原生事件,不需要再用.native修饰
- 移除filter: 建议用计算属性
参考:
官方文档