主要特点
Proxy代理
不再使用 defineProperty
(getter/setter)监听对象单个属性,性能有所提升,且解决了数组/对象新增属性时监听失效的问题。
注意:因 IE 11 不支持 Proxy,且 Babel 也无法将 Proxy 转为 ES5,Vue3 无法兼容 IE11。
组合式API
- Vue2的语法风格称为 选项式api,Vue3中增加了组合式api:
setup
,用于解决 mixins 的痛点(命名冲突、数据来源不清晰、隐式的跨 mixin 交流),将服务于每种功能的 data、computed、methods、watch 代码尽量书写在一起。 - 当两种语法风格同时使用时,同名属性、方法等以组合式API为准。
优化算法
Vue3 优化了diff 算法(不再比对所有dom)和 渲染算法(闭包缓存),性能更强大
项目构建
- Vue2 通过
vue-cli
(webpack)构建 - Vue3 通过
@vue/cli
(基于webpack)构建,或直接通过 Vite 构建
npm init vue@latest
或npm create vue@latest
(npm create 、npm innit 都是 npm init 的别名)
创建实例
Vue2通过
const app = new Vue({render: h => h(App)}).$mount('#app')
创建实例
Vue3通过
const app = Vue.createApp(App).mount('#app')
创建实例
全局配置
- Vue2中,
.component
、.directive
、.use
都是Vue类的方法
可以通过向Vue.prototype
上添加内容实现全局属性、方法
- Vue3中,
.component
、.directive
、.use
都是Vue实例的方法
可以通过向app.config.globalProperties
上添加内容实现全局属性、方法
可以通过app.config.errorHandler = (err) => {}
实现全局错误处理器
模板语法
动态参数(Vue2.6以上也已支持)
注意动态名不能出现空格和引号,大写会变成小写,动态参数值应该是字符串或null
(表示移除该动态参数绑定)
<a v-bind:[attribute_name]="url"> ... </a>
<a :[attribute_name]="url"> ... </a>
<a v-on:[event_name]="doSomething"> ... </a>
<a @[event_name]="doSomething"> ... </a>
事件修饰符
-
.stop .prevent .self .capture .once .passive
事件修饰符
其中passive
对应addEventListener
中第三个参数里的passive:true
,开发者保证监听器不会调用preventDefault()
,不用等待js脚本而立刻响应行为,通常用在touch
或者wheel
上,确保了流畅的滚动体验。 -
.enter .tab .delete (捕获“Delete”和“Backspace”两个按键) .esc .space .up .down .left .right
键盘按键修饰符 -
.ctrl .alt .shift .meta
系统按键修饰符,并可通过.exact
修饰(用于保证没有其他键按下) -
.left .right .middle
鼠标按键修饰符
三种风格
1. 选项式API
2. 使用setup方法的组合式API
- 可以和选项式API的内容在同一个
export default
里混用,但由于setup
方法在 props 之后,beforeCreate、data property、computed property 、methods 、refs 解析之前调用,因此无法访问选项式API的data
等内容,且其中this
并非指向当前组件实例。 - 只有
return
的内容才能在模板中访问 - setup 函数包含两个参数:
- 第一个参数通常命名为 props,为一个 reactive 实例,通常配合
toRef
或toRefs
使用,防止响应性丢失。对应props
和defineProps
- 第二个参数通常命名为 context,是一个非响应式的普通对象,包含如下属性:
- attrs 属性,非响应式对象,对应
$attrs
和useAttrs
- slots 插槽,非响应式对象,对应
$slots
和useSlots
- emit 触发事件,方法,对应
$emit
和defineEmits
- expose 暴露公共允许父组件访问的公共内容,对应
expose
和defineExpose
- attrs 属性,非响应式对象,对应
- 第一个参数通常命名为 props,为一个 reactive 实例,通常配合
import { ref } from 'vue'
export default {
// `setup` 是一个特殊的钩子,专门用于组合式 API。
setup(props,{attrs, slots, emit, expose}) {
const count = ref(0)
// 将 ref 暴露给模板
return {
count
}
}
}
3. 使用单文件组件的<script setup>的组合式API
- 顶层的
import
导入组件、声明的变量/函数等,可无需return
,直接在模板中使用 - 需通过
import
导入组合式API所需内容,如ref
、useAttrs
、useSlots
、nextTick
等 - 使用编译宏,如
defineProps
、defineEmits
、defineExpose
、defineModel
nextTick
- 选项式API中,可以使用
this.$nextTick
(同Vue2) - 组合式API中,需要引入
nextTick
实现:
import { createApp, nextTick } from 'vue'
const app = createApp({
setup() {
const message = ref('Hello!')
const changeMessage = async newMessage => {
message.value = newMessage
// 这里获取DOM的value是旧值
await nextTick()
// nextTick 后获取DOM的value是更新后的值
console.log('Now DOM is updated')
}
}
})
v-if 和 v-for
- v-if 和 v-for 可以在一个元素上同时使用(不建议),但 v-if 优先级更高,因此其中无法引用 v-for 里的变量
- v-if 不再需要 key,v-for 的 key 可以配置在 template 中而非实际元素
- key现在可以正确配置给<template>,而非其中的子元素
生命周期
- 使用
beforeUnmount
、unmounted
代替Vue2中的beforeDestroy
、destroyed
组合式API的生命周期钩子
- 组合式 API 上的生命周期钩子与选项式 API 的名称相同,但需要添加前缀
on
- 不存在
onCreated
和onBeforeCreate
钩子,相关内容应直接在setup
中实现 - 生命周期函数不一定要在
setup
中顶层调用来注册,但不能在异步内调用(可能注册时已经错过生命周期)
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
// 在我们的组件中
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`
return {
repositories,
getUserRepositories
}
}
computed 计算属性
计算属性值会基于其响应式依赖被缓存,仅会在其响应式依赖更新时才重新计算
计算属性默认只读,可以通过提供 getter
和 setter
来创建可写的计算属性(一般用不到,仅用于如模拟实现v-model等特殊场景):
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
//const fullName = computed(()=>firstName.value + ' ' + lastName.value)
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
注意:如果每次返回的是个新对象,则新旧值始终不同,总是触发更新。此时可以手动比较新旧值来优化:
const computedObj = computed((oldValue) => {
const newValue = {
isEven: count.value % 2 === 0
}
if (oldValue && oldValue.isEven === newValue.isEven) {
return oldValue
}
return newValue
})
watch 侦听器
侦听器可以监听单个响应式对象、多个来源组成的数组、一个 getter 函数。
支持以下配置项
-
once: true
只触发一次 -
deep: true
深层监听对象 -
immediate: true
使侦听器创建时立即触发回调。 -
flush
决定侦听器回调的触发时机
"pre"
:默认值。侦听器回调会在父组件更新 (如有) 后、所属组件的 DOM 更新前被调用。这意味着在侦听器回调中访问到的所属组件的DOM是更新前的状态。
"post"
:当UI渲染更新后再触发回调
"sync"
:响应式依赖改变时立即触发回调(谨慎使用,可能会有性能或数据一致性问题)
选项式API中可以通过this.$watch
配置:
var unwatch = this.$watch('someObject', callback, {
deep: true
})
unwatch()//通过调用返回值来主动卸载侦听器
选项式API也可以将方法放在handle
属性中,并附加配置:
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// 注意:在嵌套的变更中,
// 只要没有替换对象本身,
// 那么这里的 `newValue` 和 `oldValue` 相同
},
deep: true,
}
}
}
组合式API中通过watch方法配置:
import { ref, watch } from 'vue'
const x = ref(0)
const y = ref(0)
//单个ref
watch(x, (newValue, oldValue) => {
console.log('The new counter value is: ' + x.value)
},
{ deep: true }
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
// 监听一个 getter 函数
watch(
() => x + y,
(sum) => {
console.log(`sum is: ${sum}`)
}
)
注意,组合式API中不能直接侦听响应式对象的属性值(因为这个值本身不是响应式对象),例如可以这样:
const obj = { count: ref(0) }
watch(obj.count, (count) => {
console.log(`Count is: ${count}`)
})
但如果用const obj = ref({ count: 0 })
,就只能这样了:
watch(
() => obj.count,
(count) => {
console.log(`Count is: ${count}`)
}
)
组合式API额外提供了语法糖:watchEffect 和 watchSyncEffect
自动跟踪回调函数中同步方法内的所有响应式依赖,且自带immediate:true
:
import { ref, watchEffect } from "vue";
const obj = ref({ age: 18 });
const unbind = watchEffect(()=>{
console.log(obj.value.age)
})
watchEffect(callback, { flush: 'sync' })
可进一步使用别名watchSyncEffect
解除侦听
-
$watch
、watch
、watchEffect
函数返回值都是一个可用于解除侦听的函数。 - 用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。异步语句内创建的侦听器只能手动卸载,以防内存泄露。
ref 模板引用
- 选项式API通过
this.$refs.XXX
来引用模板 - 组合式API声明一个和模板里的 ref 同名的 ref 来存放引用:
<script setup>
import { ref, onMounted } from 'vue'
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
- 当 ref 和 v-for 一起使用的时候,可以得到包含了对应数据源的这些子组件的数组,但顺序不一定相同
约束可用内容
- 如果使用了选项式API,默认父元素可以调用所有子组件内容(此时同Vue2,被引用的实例等价于子组件中的this指向)。也可以通过配置
expose
属性名数组,约束可用内容。 - 如果使用了
setup
方法,默认父元素可以调用所有子组件内容。也可以使用setup
方法参数里的expose
方法,约束可用内容。
export default {
// 只有 `publicMethod` 在公共实例上可用
expose: ['publicMethod'],
setup(props, { expose }) {
var aa = 100;
var bb = 200;
expose({ aa });
return { aa, bb };
},
methods: {
publicMethod() {
// ...
},
privateMethod() {
// ...
}
}
}
- 如果使用了
<script setup>
,则其中内容默认都是私有的,必须通过编译宏defineExpose
暴露:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
JSX/TSX
Vue3支持JSX和TSX(但语法和React的不同)
SSR 服务端渲染(Nuxt)
服务端渲染优势:更快的首屏加载速度、更好的SEO
Typescript
defineComponent
为了让 TypeScript 正确地推导出组件选项内的类型,我们需要通过 defineComponent()
这个全局 API 来定义组件:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
// 启用了类型推导
props: {
message: String
},
setup(props) {
props.message // 类型:string | undefined
}
})
</script>
泛型
-
<script setup>
中使用 generic 属性声明泛型:
<script setup lang="ts" generic="T">
defineProps<{
items: T[]
selected: T
}>()
</script>
- 其他情况使用
defineComponent
函数签名:
const Comp = defineComponent(
<T extends string | number>(props: { msg: T; list: T[] }) => {
// 就像在 <script setup> 中一样使用组合式 API
const count = ref(0)
return () => {
// 渲染函数或 JSX
return <div>{count.value}</div>
}
},
// 目前仍然需要手动声明运行时的 props
{
props: ['msg', 'list']
}
)