- 为什么要升级 Vue3?
- vue2 VS vue3(编码体感)
- vue3 比较直观的新特性
- option api VS composition api
- 指令:vue2 VS vue3
- 组件通信:vue2 VS vue3
- 插槽 vue2 VS vue3
- 代码复用 vue2 VS vue3
- script setup 补充
- 强大的 style 特性
Vue3 已经发布有一段时间了,同时也得到了各大厂商和社区的支持和众多开发者喜爱,周边生态也正在逐步完善。可谓是一片欣欣向荣的美景。本文意在通过梳理 Vue2 常用 api 通过差异化对比 Vue3,帮助你快速掌握 Vue3
注:本文假设你已经有一定vue2
实操经验,不会过多描述api
细节。
如果想更系统学习请看Vue3 教程
为什么要升级 Vue3?
两个关键的因素导致了我们考虑重写 Vue 新的主要版本:
- 主流浏览器对新的
JavaScript
语言特性的普遍支持。 - 当前
Vue
代码库随着时间的推移而暴露出来的设计和体系架构问题。
更多细节,我们可以听听祖师爷在知乎的回答尤雨溪亲笔:重头来过的 Vue 3 带来了什么?
vue2 VS vue3(编码体感)
就我个人编码习惯而言,相对于vue2
的Option api
的直观明了,vue3
的Composition api
可以更好的组织代码,支持自定义hooks
来达到代码复用,从而取代mixin
,代码风格上,也可以把类似的业务逻辑写到一个代码块,从而避免代码分散,走读代码需要上下反复横跳。更友好的支持了TS
以及众多新特性的加入,也让vue3
写起来更爽,更利于代码维护和拓展。结合vite
使用,更是极大提升了开发体验。总体来说,让我们拥抱vue3
吧,给vue
团队点个赞👍👍👍
vue3 比较直观的新特性
- 全新的
Composition api
让我们换一种方式组织代码; <script setup>
语法糖,使得代码更简洁;- 可以省去
template
的根元素包裹标签; - 提供了新的内置组件
<teleport>
,支持了组件可以挂载到任意dom
节点下; - 提供了在
css
中使用v-bind
来引入script
变量,又一个强大的黑魔法小技巧; - 使用
createApp
来创建应用实例; - 更友好的
TS
支持; - 使用
proxy
代理方式来替换掉defineproperty
; - 全局和内部
API
已经被重构为支持tree-shake
。
option api VS composition api
composition api
是vue3
的一大特色,vue3
对外暴露了大量函数供以我们按需引用,随意组合。
在option api
中,我们通过实例化Vue
并将行为对象作为参数传入:
new Vue({ data(){ return {} }, methods:{}, computed:{}, created(){}, ... })
在Composition api
我们可以通过setup
作为入口函数,并返回一个对象数据暴露给模板使用:
<template> <div @click="hi">{{ msg }}</div> </template> <script> export default { setup() { const msg = ref('Hello!') function hi() { console.log(msg) } // 暴露给模板 return { msg, hi } } } </script>
setup
还支持另一种写法,更加简化了代码:
<template> <div @click="hi">{{ msg }}</div> </template> <script setup> const msg = ref('Hello!') function hi() { console.log(msg) } </script>
当使用 <script setup>
的时候,任何在 <script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import
引入的内容) 都能在模板中直接使用。
响应数据的定义
vue2
中把响应数据定义到data
函数的return
中:
data(){ return { ... } }
vue3
中我们可以通过ref
和reactive
来定义响应数据:
const num = ref(0) const obj = reactive({name:'molly'})
ref:
- 在模板中可以直接使用
ref
定义的数据,在js
中则需要使用.value
来取值或赋值; - 使用
geter
,seter
方式实现响应式; - 建议使用
ref
来定义基础数据。
reactive:
- 可使用
toRefs
将其解包,在模板中直接使用对应的attribute
; - 使用
proxy
代理方式实现响应式; - 建议使用
reactive
来定义复杂数据。
生命周期钩子
vue2
中提供了 11 个生命周期钩子,我们可在选项对象上直接定义使用。
vue3
中把生命周期钩子单独抽离成了一个个对应的hooks
函数,以onXXX
的形式调用。值得注意的是生命周期钩子注册函数只能在setup
期间同步使用,这就意味着beforeCreate
和created
等同于setup
执行阶段。其他的周期用法基本一致,例如:mounted
对应onMounted
:
import { onMounted } from 'vue' setup() { onMounted(() => { console.log('mounted!') }) }
computed
两个版本的computed
基本保持一致,不同的是vue3
将computed
抽离成一个hooks
函数使用:
// vue2 computed:{ num:()=>this.num *2 } // vue3 const numVal = computed(() => num.value *2)
watch
vue2:
watch
监听一个特定数据源的结果变化,回调函数得到的参数为新值和旧值。除了监听data
中的数据
还可以监听props
、$route
、$emit
、computed
。可通过选项参数deep
来深度监听,指定 immediate: true
将立即触发回调:
watch:{ obj:(val,old)=>{ deep: true, immediate: true } }
vue3
vue3
的计算属性得到了很大的加强,支持监听多个数据源和执行副作用:
// 监听单个数据源 const count = ref(0) watch(count, (val, old) => { /* ... */ }) // 假设 count 是一个对象,也支持监听整个对象,而无需指定 deep 属性
// 监听多个数据源 const count = ref(0) const obj = reactive({name:'molly'}) watch([() => obj.name, count], ([newName, newCount], [oldName, oldCount]) => { /* ... */ }) // 监听多个数据源时,watch 函数的第一个参数可传入数据源数组,第二个回调函数的参数也是一个数组
vue3
还新增了watchEffect
,表示立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
与watch
相比,watchEffect
不需要传入指定监听的数据源,它会自动收集依赖。也没有新值旧值的概念,只要依赖发生变化,就会重新执行函数:
const count = ref(0) watchEffect(() => console.log(count.value)) setTimeout(() => { count.value++ }, 100)
watchEffect
会返回一个用于停止这个监听的函数。
当watchEffect
在组件的setup
函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
const stop = watchEffect(() => { /* ... */ }) stop()
filters
vue2
中我们可以很方便的使用 filters 过滤器来处理一些文本格式转换,对数据进行加工。
filters: { sum(num1,num2) { return num1+num2 } }
请注意: 在vue3
中,将移除过滤器,且不再支持。官方更推荐我们使用方法调用或计算属性来替换filters
。
至于为啥要移除这个api
官方的解释是:虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript
”的假设,这不仅有学习成本,而且有实现成本。
我的理解是:api
功能设计重复,filters
能干的事儿,计算属性和函数调用也能干,且干的更好。所以尤大大含泪移除filters
,可怜的filters
只能被抛弃。
人生亦是如此,职场中优胜劣汰,希望我们永远都不会是那个被优化掉的filters
😭😭😭。
components
vue2
中我们需要通过选项components
进行组件注册,而在vue3
中,我们可以直接使用组件:
//vue2 import A from './a.vue' components:{ A }
//vue3 <template> <A/> </template> <script setup> import A from './a.vue' </script>
指令:vue2 VS vue3
两个版本的指令用法基本相同,这里我只列出vue3
差异部分。
v-model
vue2
中我们实现一个自定义v-model
可以这样写:
// vue2 props:{ title:{ type:String, default: 'molly' } }, model: { prop: 'title', event: 'change', }, methods:{ change(val){ this.$emit('input',val) } },
vue3
中可以定义v-model
参数名,同时还支持设置多个v-model
,简直美滋滋~
// vue3 props:{ title:{ type:String }, num:{ type:Number }, }, setup(props,{emit}){ function change1(val){ emit('update:title',val) } function change2(val){ emit('update:num',val) } return { change1, change2 } } // 在父组件中使用 <Son v-model:title="molly" v-model:num="18" />
新增指令
v-memo
,该指令记住一个模板的子树。元素和组件上都可以使用。该指令接收一个固定长度的数组作为依赖值进行记忆比对。如果数组中的每个值都和上次渲染的时候相同,则整个该子树的更新会被跳过。相当于内存换时间,相同渲染内容则从内存读取,这对于长列表场景很有用。
其他变更
- 对于
v-if
/v-else
/v-else-if
的各分支项key
将不再是必须的,因为现在vue3
会自动生成唯一的key
; <template v-for>
的key
应该设置在<template>
标签上 (而不是设置在它的子节点上);- 作用于同一个元素上时,
v-if
会拥有比v-for
更高的优先级; v-on
的.native
修饰符已被移除;v-for
中的ref
不再注册ref
数组;- 在使用
v-bind="object"
与组件独立属性重名时,那么绑定的声明顺序将决定它们如何被合并,以后者为标准。
<!-- 模板 --> <div id="red" v-bind="{ id: 'blue' }"></div> <!-- 结果 --> <div id="blue"></div> <!-- 模板 --> <div v-bind="{ id: 'blue' }" id="red"></div> <!-- 结果 --> <div id="red"></div>
组件通信:vue2 VS vue3
在vue2
中提供了多种api
供以我们进行组件通信:
props
/$emit
/$on
$attrs
/$listeners
provide
/inject
$parent
/$children
/ref
在vue3
中依然支持大部分api
,并做了适当调整
- 移除了
$on
、$off
、$once
实例方法 - 移除了
$children
实例property
$attrs
现在包含了所有传递给组件的attribute
,包括class
和style
- 在
<script setup>
中必须使用defineProps
和defineEmits
API 来声明props
和emits
,它们具备完整的类型推断并且在<script setup>
中是直接可用的
插槽 vue2 VS vue3
在插槽这一块两个版本基本保持一致,没有太多的改动,还是保留原来的使用方式。vue3
做了一点点小更新
this.$slots
现在将插槽作为函数公开;- 移除
this.$scopedSlots
。
代码复用 vue2 VS vue3
vue2
中代码复用的手段很多,主要有以下几种方式
- 组件抽离
- 自定义指令
- 实例全局挂载
- 插件封装
- mixin
- extend
vue3
中同样涵盖以上手段,并做了相应优化与更新。
- 实例全局挂载这一手段在
vue2
中,我们一般是简单粗暴的通过prototype
将行为对象挂载到vue
原型上,这种方式虽然简单明了,但是也存在全局污染的问题。Vue.prototype.$xxx = {name:'molly'}
对应到 vue3 中,则不允许我们这样子干,取而代之的是
app.config.globalProperties
- mixin 也是代码复用的一大利器,不过相应的也暴露出一些问题。当 mixin 被滥用或大量使用时,会导致依赖关系不明确,不易维护。在 vue3 中更推荐我们使用自定义 hooks 的方式来复用代码。
实现一个自定义 hooks
我们可以把一个功能相关的数据和逻辑都抽离出来放到一起维护,例如实现一个累加器:
import {ref, onUnmounted} from 'vue' export function useAccumulator(){ const count = ref(0) let timer = null timer = setInterval(()=>{ count.value ++ },1000) onUnmounted(()=>{ clearInterval(timer) }) return {count} }
我们定义了一个累加器的hooks
函数,在组件入口就可以像普通函数一样使用useAccumulator()
:
import {useAccumulator} from '../utils' let {count} = useAccumulator()
script setup 补充
贴上官网的描述
<script setup>
是在单文件组件 (SFC) 中使用组合式 API的编译时语法糖。相比于普通的 <script>
语法,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯
Typescript
声明props
和抛出事件。 - 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
<script setup>
为我们带来了极大的便利,优势如下:
- 所有顶层绑定变量内容都能在模板中直接使用。
<script setup>
范围里的值也能被直接作为自定义组件的标签名使用,相当于我们可以不用通过components
注册组件。- 支持使用
:is
来绑定动态组件。 - 支持顶层
await
,先比JavaScript
一步实现这个特性,就是爽。
强大的 style 特性
强大的style
特性是vue3
的又一个神兵利器。
深度选择器
为了使组件之间样式互不影响,我们可以这样写<style scoped>
,当处于scoped
下想做深度选择时,可以使用:deep()
伪类:
<style scoped> .a :deep(.b) { ... } </style>
插槽选择器
默认情况下,作用域样式不会影响到<slot/>
内容,可以使用:slotted
伪类来选择到插槽:
:slotted(div) { color: red; }
全局选择器
可通过:global
伪类来实现全局样式:
:global(.red) { color: red; }
css module 的支持
可通过<style module>
方式将 css 类作为$style
对象暴露出来给组件使用,同时也支持自定义名称:<style module=“molly” >
<template> <p :>red</p> </template> <style module="molly"> .red { color: red; } </style>
状态驱动的动态 css
这是我认为最方便的一个特性,可以让我们少写很多代码,非常爽~
在style
中允许我们使用v-bind
来将 css 值动态关联到组件上:
<template> <div >hello</div> </template> <script> export default { data() { return { color: 'red' } } } </script> <style> // v-bind 可以直接引入 script 中的响应数据作为值 .text { color: v-bind(color); } </style>
总结至此应该可以轻松上手 vue3 项目了,感谢阅读。