今年7月,尤大大宣布 Vue 3 进入 RC 阶段,这意味着 Vue 3 内核 API 与实现已趋稳定。
Vue作为一种渐进式框架, 借鉴了 React 的组件化和虚拟 DOM ,借鉴了 Angular 的模块化和双向数据绑定。就框架的 API 而言,对比之下,它更加轻便简洁。对于Vue的好处这里不再赘述。
相对vue2.x,Vue3做了很多重要的变更,特别是Composition API的引入。我们知道,Vue 现有 API 是通过「选项」组织代码的,随着功能的增长,复杂组件的代码会变得越来越难维护,很多逻辑都无法复用。开发的时候复杂的组件千行代码template/data/methods来回翻,看到你头晕,而vue3可以让你在接手别人的代码时更容易理清逻辑关系。下面直接进入主题,我们来看看Vue3都有哪些新特性,相对于之前版本又有哪些变更。
一、3.0 对比 2.x 的重要变更
Vue 3.0 相对与之前的版本,有 6 个方面的重要变更:
Performance(性能)
性能上,主要是优化了虚拟 DOM,所以也就有了更加优化的编译,同时实现了更加高效的组件初始化。
- Rewritten virtual dom implementation (重写了虚拟 DOM)
- Compiler-informed fast paths (优化编译)
- More efficient component initialization (更高效的组件初始化)
- 1.3-2x better update performance (1.3~2 倍的更新性能)
- 2-3x faster SSR (2~3 倍的 SSR 速度)
Tree-shaking support (支持 Tree-shaking)
在大部分情况下,我们并不需要 vue 中的所有功能,但是在之前的 vue 版本中,我们没有一个合适的办法用来除去不需要的功能,而 Vue3 中,为了满足体积更小的需求,支持 Tree-shaking,也就意味着我们可以按需求引用的内置的指令和方法。
- Most optional features (e.g. v-model, <transition>) are now tree-shakable (大多数可选功能(如 v-model、<transition>)现在都是支持 Tree-shaking 的。)
- Bare-bone HelloWorld size: 13.5kb. 11.75kb with only Composition API support.
- All runtime features included: 22.5kb. More features but still lighter than Vue 2.
Composition API
Composition API 主要是提高了代码逻辑的可复用性,并且将 Reactivity 模块独立出来,这也使得 vue 3 变得更加灵活地与其他框架组合使用。
- Usable alongside existing Options API (可与现有选项 API 一起使用)
- Flexible logic composition and reuse (灵活的逻辑组成和重用)
- Reactivity module can be used as a standalone library (Reactivity 模块可以作为独立的库使用)
Fragment, Teleport, Suspense
- Fragment
在书写vue2时,由于组件必须只有一个根节点,很多时候会添加一些没有意义的节点用于包裹。Fragment组件就是用于解决这个问题的(这和React中的Fragment组件是一样的)。
- No longer limited to a single root node in templates (<template> 中不再局限于单一的根节点)
-Manual render functions can simply return Arrays (render 函数可以返回数组)
- “Just works”
- Teleport
Teleport其实就是React中的Portal。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。
- Previously known as <Portal>(原名为 <Portal>)
- More details to be shared by @Linusborg
- Suspense
同样的,这和React中的Supense是一样的。Suspense 让你的组件在渲染之前进行“等待”,并在等待时显示 fallback 的内容。
- Wait on nested async dependencies in a nested tree
- Works with async setup() (与 async 函数 setup() 配合使用)
- Works with Async Components (与 Async 组件配合使用)
Better TypeScript support (更好的 TypeScript 支持度)
vue3.0 对 TS 的支持度更高了,同时也支持 TSX 的使用;API 在 JS 与 TS 中的使用相同;类组件仍然可用,但是需要我们引入 vue-class-component@next,该模块目前还处于 alpha 测试阶段。
- Codebase written in TS w/ auto-generated type definitions
- API is the same in JS and TS
- In fact, code will also be largely the same
- TSX support
- Class component is still supported (vue-class-component@next is currently in alpha)
Custom Renderer API (自定义的 Renderer API)
自定义 render 会提供一个 API 用来创建自定义的 render,因此不再需要为了自定义一些功能而 fork Vue 的代码。这个特性给 Weex 和 NativeScript Vue 这样的项目提供了很多便利。
- NativeScript Vue integration underway by @rigor789
- Users already experimenting w/ WebGL custom renderer that can be used alongside a normal Vue application (Vugel)
二、Composition API 等核心特性
在vue3中引入了Composition API(组合API),使用纯函数分隔复用代码,让逻辑变得清晰。
关于VCA,有人说跟react的hooks很像,我们来看看作者尤大的介绍:
Lifecycle Hooks
新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用.
beforeCreate-> use setup()created-> use setup()- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
import {onBeforeMount, onMounted} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
},
}
setup
setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口。
- setup 函数相当于 vue2.x 中 beforeCreate 和 created, 在 beforeCreate 之后、created 之前执行
- setup第一个形参,接收 props 数据
- setup第二个形参是一个上下文对象, 在 setup() 函数中无法访问到 this, 可以用这个context来访问
export default {
props: {
str: String
},
setup(props, context) {
console.log(props) // str
console.log(context) // attrs, slots, parent, root, emit, refs
},
}
reactive
reactive() 函数接收一个普通对象,返回一个响应式的数据对象。
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({count: 0}) // 创建响应式数据对象
return state //将响应式数据对象 return 出去,供 template 使用
}
}
ref
ref() 函数根据给定的值创建一个响应式的数据对象,返回值是一个对象,这个对象上只包含一个 .value 属性
import { ref } from 'vue'
export default {
setup() {
const count = ref(0) // 创建响应式数据对象 count,初始值为 0
console.log(count.value) // 在setup内访问count值需要.value 属性才可以,但在template中可以直接访问
return {
count
}
},
}
toRefs
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
import { reactive, toRefs } from 'vue'
export default {
setup() {
const state = reactive({count: 0, name:'weedsFly'}) // 用reactive集中创建多个响应式对象
const add = () => { // methods写在setup内
state.count++
}
return {
// ...state, // 使用展开运算符后 用reactive创建的响应式数据 变成了 固定的值
...toRefs(state), // 可以用toRefs函数 将传进来的非响应式对象 转成 ref() 类型的响应式数据
add
}
},
}
ref引用
通过 ref() 还可以引用页面上的元素或组件,这点和vue2的ref概念类似。
1.元素引用
import { ref, onMounted } from 'vue'
export default {
setup() {
const h1Ref = ref(null) // 创建一个 DOM 引用
onMounted(() => { // 在 DOM 首次加载完毕之后,才能获取到元素的引用
h1Ref.value.style.color = 'pink' // h1Ref.value 是原生DOM对象
})
return {
h1Ref
}
}
}
2.组件引用
//父组件:
import { ref } from 'vue'
export default {
setup() {
const compRef = ref(null) // 创建一个组件的 ref 引用
showCompData = () => { // 展示子组件中 count 的值
console.log(compRef.value.count)
}
return {
compRef,
showCompData
}
}
}
//子组件:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0) // 定义响应式的数据
return {
count
}
}
}
computed
omputed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。
1.computed创建只读的计算属性(传入一个 function 函数,可以得到一个只读的计算属性)
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const computedCount = computed(() => count.value + 1)
computedCount.value = 9 // computed value is readonly.
return {
count,
computedCount
}
},
}
2.computed创建可读可写的计算属性
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const computedCount = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
computedCount.value = 100 // 为计算属性赋值的操作,会触发 set 函数
console.log(count.value) // 触发 set 函数后,count 的值会被更新
return {
count,
computedCount,
computedCount
}
},
}
watch
1.监视单个数据源变动
- 监视单个reactive创建的数据源
import { reactive, watch } from 'vue'
export default {
setup() {
const state = reactive({count: 100})
watch(
() => state.count,
(newVal, oldVal) => { console.log(newVal, oldVal)},
{lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
)
setTimeout(() => {
state.count++
}, 1500)
}
}
- 监视单个ref创建的数据源
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(100)
watch(
count,
(newVal, oldVal) => { console.log(newVal, oldVal)},
{lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
)
setTimeout(() => {
count++
}, 1500)
}
}
2.监视多个数据源
- 监视多个reactive创建的数据源
import { reactive, watch } from 'vue'
export default {
setup() {
const state = reactive({count: 100, name: 'Laiyj'})
watch(
[() => state.count, () => state.name],
([newCount, newName], [oldCount, oldName]) => {
console.log(newCount, oldCount)
console.log(newName, oldName)
},
{lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
)
setTimeout(() => {
state.count++
state.name = 'Lucy'
}, 1000)
}
}
- 监视多个ref创建的数据源
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(100)
const name = ref('Laiyj')
watch(
[count, name],
([newCount, newName], [oldCount, oldName]) => {
console.log(newCount, oldCount)
console.log(newName, oldName)
},
{lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
)
setTimeout(() => {
count++
name = 'Lucy'
}, 1000)
}
}
3.清除watch监视
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(100)
const stop = watch( // 创建监视,并得到 停止函数
count,
(newVal, oldVal) => {
console.log('I am watching.')
console.log(newVal, oldVal)
},
{lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
)
setTimeout(() => {
count++
}, 1000)
const clearWatch = () => {
stop()
}
return {
count,
clearWatch
}
}
}
4.在 watch 中清除无效的异步任务(与节流防抖同效)
import { ref, watch } from 'vue'
export default {
setup() {
const keyword = ref('')
const asyncPrint = (val) => { // 执行异步任务,并得到关闭异步任务的 timerId
return setTimeout(() => {
console.log(val)
}, 1000)
}
watch(
keyword,
(newVal, oldVal, onClean) => {
const timeId = asyncPrint()
onClean(() => {clearTimeout(timeId)}) // 如果 watch 监听被重复执行了,则会先清除上次未完成的异步任务
}
)
return {
keyword
}
}
}
provide & inject
- provide() 和 inject() 可以实现嵌套组件之间的数据传递。
- 这两个函数只能在 setup() 函数中使用。
- 父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。
//父组件:
import { provide, watch } from 'vue'
export default {
setup() {
const color = ref('red')
provide('themeColor', color) // 父组件通过 provide 函数向子级组件共享数据(不限层级) provide('要共享的数据名称', 被共享的数据)
const changeColor = (val) => {
color.value = val
}
return {
color,
changeColor
}
}
}
// 子级组件:
import { inject, watch } from 'vue'
export default {
setup() {
const color = inject('color') // 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
return {
color
}
}
}
以上只罗列了几个比较基础重要的API,vue3还有很多还需要多多学习 : )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。