前言
4月21日,尤大大在B站分享了Vue.js3.0 Beta的最新进展,从2019年6月份的第一次发布的Vue3.0 Function-based API RFC,再到2019年10月份的Vue.js3.0 pre-alpha,经历一次次的更新,如果你还没有对3.0的更新内容有所了解,那么如何升职加薪,迎娶白富美,走上人生巅峰。
对于有理想有追求的小伙伴不要慌,认真读完这篇文章,相信你对于Vue3.0不再迷茫。废话不多说,手摸手带你开始学习!
设计目标
作为Vue这种全球热门的前端框架,每一次大版本的更新都会对开发人员带来巨大的影响,如果没有足够的“好处”,很难让已经习惯了Ctrl CV的同学们去接受。那么Vue3.0带来了哪些改变呢?
亮点
- 更快
- 基于 Proxy 的变动侦测,性能整体优于 getter / setter
- Virtual DOM 重构
- 编译模板的优化
- 更高效的组件初始化
- update性能提高1.3~2倍
- SSR速度提高了2~3倍
- 更小:Tree shaking support,可以将未使用模块“剪辑”,仅打包需要的,并不像2.x提供了所有的API,
- Composition API:可以实现更灵活且无副作用的复用代码。在2.x实现逻辑复用最常用的是 Mixins,然而存在两个问题:数据来源不清晰、命名空间容易冲突。那Composition API好在哪,后面会以实际代码介绍
- 加强TypeScript支持:Vue3.0的源码基本都是由TS编写,Vue 3 + TypeScript 插件正在开发,有类型检查,自动补全等功能。所以还是推荐大家在项目开发过程中使用ts
关于正式版
Vue的发布版本:Alpha - Beta - RC - 正式
所以还会经历RC版本才会有正式版可用,2020年估计不太可能会出了,不过也不用着急,正式版相比于beta主要是开发配套基础功能,比如脚手架、vue-router、以及生态插件等;
不过对于我们开发者来讲,最关心的还是它的语法,这块变动还是比较大的。下面来跟着项目看一下到底有哪些改动。
吐槽
“一堆东西丢在setup里,我还不如直接用react”
“代码结构还没有2.x版本清晰,更不好维护了”
“不就是抄react么”
个人认为吐槽Vue3.0的原因主要有两点
-
习惯了基于选项的开发方式,但是代码只是看上去更整洁。
- 一个复杂的组件往往需要同时处理多个不同的逻辑任务,每个逻辑任务所涉及的代码在选项 API 下是被分散在多个选项之中的。举例来说,从服务端抓取一份数据,可能需要用到 props, data(), mounted 和 watch。极端情况下,如果我们把一个应用中所有的逻辑任务都放在一个组件里,这个组件必然会变得庞大而难以维护,因为每个逻辑任务的代码都被选项切成了多个碎片分散在各处。
- 对比之下,基于函数的 API 让我们可以把每个逻辑任务的代码都整理到一个对应的函数中。当我们发现一个组件变得过大时,我们会将它切分成多个更小的组件;同样地,如果一个组件的 setup() 函数变得很复杂,我们可以将它切分成多个更小的函数。而如果是基于选项,则无法做到这样的切分,因为用 mixin 只会让事情变得更糟糕。
-
关于抄袭react。首先如果从原理的角度对于关于react hook和vue hook会发现其实还是有很大区别。
当然不得不承认Composition-Api灵感来源于react hooks,但是也谈不上抄不抄的,开发都提倡开源精神,优秀的框架也不是一开始就能赢得所有人的青睐,也是一次一次版本的迭代中逐渐更好的。使用框架的目的不就是为了广发开发人员么,何必非得搞门派之争。
基于 Composition Api 学习Vue3.0
创建项目
npm install -g @vue/cli //升级cli
vue create vue3.0-test // 创建项目,其实这里还是用的vue2.x
npm i @vue/composition-api -S
-
// mian.js import Vue from 'vue' import VueCompositionApi from '@vue/composition-api' Vue.use(VueCompositionApi)
2.x的API可正常使用
Composition-API
setup()函数
-
执行时间
setup() 是 Vue3.0 中引入的一个新的组件选项,这是一个组件的入口,处于2.0的beforeCreate 和 Created 之间,setup 返回的是一个对象,里面的所有被返回的属性值,都会被合并到 Vue2.0 的 render 渲染函数里面,支持返回 JSX 代码片段。
-
接收 props 数据
props: {
msg: String
},
setup (props) {
console.log(props.msg)
const data = reactive({
showList: 1
})
console.log(data.showList) // 1
return {
showList
}
}
在 setup() 里的方法不能通过 this 来访问实例上的数据,而是通过直接读取 data 来访问。 这里传进来的 props 对象是响应式的 —— 它可以被当作数据源去观测,当后续 props 发生变动时它也会被框架内部同步更新。但对于用户代码来说,它是不可修改的(会导致警告)。
-
context
setup 函数的第二个形参是一个上下文对象,这个上下文对象中包含了一些有用的属性,这些属性在 vue 2.x 中需要通过 this 才能访问到,在 vue 3.x 中,它们的访问方式如下:
const MyComponent = { setup(props, context) { context.attrs context.slots context.parent context.root context.emit context.refs } }
ref
ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 .value 属性:
<template>
<div>{{showRef}}</div>
<div>{{name}}</div>
</template>
// 注意每个用到的api都要引入
import { ref } from '@vue/composition-api'
export default {
name: 'Home',
setup () {
const showRef = ref('test')
//要访问 ref()创建出来的响应式数据对象的值,必须通过 .value 属性才可以
console.log(showRef.value) // test
console.log(name.value) // error
return {
showRef,
name: ref('zs')
}
}
}
reactive
reactive 它主要是处理你的对象让它经过 Proxy 的加工变为一个响应式的对象,类似于 Vue2.0 版本的 data 属性,需要注意的是加工后的对象跟原对象是不相等的,并且加工后的对象属于深度克隆的对象。
<template>
<div>{{showList}}--{{showData2}}</div>
</template>
// 注意每个用到的api都要引入
import { toRefs, reactive } from '@vue/composition-api'
export default {
name: 'Home',
setup () {
const data = reactive({
showData1: 1,
showData2: 2
})
})
return {
...toRefs(data)
}
}
}
ref和reactive都能创建响应式数据,那有什么区别呢?
ref()单独地为某个数据提供响应式能力;而 reactive()给一整个对象赋予响应式能力。
但是在具体的用法上,通过 reactive() 包装的对象会有一个坑。如果想要保持对象内容的响应式能力,在 return 的时候必须把整个 reactive() 对象返回出去,同时在引用的时候也必须对整个对象进行引用而无法解构,否则这个对象内容的响应式能力将会丢失。
“对象的特性”是赋予给整个“对象”的,它里面的内容如果也想要拥有这部分特性,只能和这个对象捆绑在一块,而不能单独拎出来。
如果无法使用解构取出 reactive() 对象的值,每次都需要通过 . 操作符访问它里面的属性会是非常麻烦的,所以官方提供了 toRefs() 函数来为我们填好这个坑。只要使用 toRefs() 把 reactive() 对象包装一下,就能够通过解构单独使用它里面的内容了,而此时的内容也依然维持着响应式的特性。
对于我个人来说,会更倾向于使用 reactive() 搭配 toRefs() 来使用,因为经过 ref() 封装的数据必须通过 .value 才能访问到里面的值,写法上要注意的地方相对更多一些。
computed
- 创建只读计算属性
import { toRefs, reactive, computed } from '@vue/composition-api'
export default {
name: 'Home',
setup () {
const data = reactive({
showList: 1,
onShowList: computed(() => {
return data.showList + 1
})
})
console.log(data.onShowList) // 2
data.onShowList++ // error
return {
...toRefs(data),
}
}
}
- 创建可读可写的计算属性
setup () {
const data = reactive({
showList: 1,
onShowList: computed({
// read
get: () => data.showList + 1,
// write
set: val => {
data.showList = val - 2
}
})
})
触发set函数
data.onShowList = 10
console.log(data.onShowList) // 10-2+1=9
return {
...toRefs(data)
}
}
watchEffect
watchEffect与2.x watch类似,但是不需要分离监视的数据源和副作用回调。
import { toRefs, reactive, watchEffect } from '@vue/composition-api'
export default {
name: 'Home',
setup () {
const data = reactive({
showList: 1,
watchShowList: 0,
})
watchEffect(() => {
data.watchShowList = data.showListAdd
})
return {
...toRefs(data),
}
}
}
</script>
function
没有了methods选项,那事件处理该怎么调用方法?
setup () {
const data = reactive({
showList: 1,
showListAdd: 0,
})
function getShowList () {
data.showListAdd = data.showList++
}
function getMethods () {
getShowList()
}
return {
...toRefs(data),
getShowList,
getMethods
}
}
LifeCycle Hooks
import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api";
export default {
setup() {
const data = reactive({
showList: 1
})
onMounted(() => {
console.log('mounted!')
});
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
return {
...toRefs(data)
};
}
};
vue 2.x 的生命周期函数与新版 Composition API 之间的映射关系:
(生命周期函数需要按需引入)
- beforeCreate -> use setup()
- created -> use setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
逻辑复用
来举栗子感受一下:跟踪鼠标的位置
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
import { useMousePosition } from './mouse'
export default {
setup() {
const { x, y } = useMousePosition()
// other logic...
return { x, y }
}
}
Vue2.x的逻辑复用方式:
-
渲染上下文中公开的属性的来源不清楚。例如,当使用多个mixin读取组件的模板时,可能很难确定从哪个mixin注入了特定的属性。
-
命名空间冲突。Mixins可能会在属性和方法名称上发生冲突,而HOC可能会在预期的prop名称上发生冲突。
-
性能。HOC和无渲染组件需要额外的有状态组件实例,这会降低性能。
相比之下,使用Composition API:
-
暴露给模板的属性具有明确的来源,因为它们是从合成函数返回的值。
-
合成函数返回的值可以任意命名,因此不会发生名称空间冲突。
-
没有创建仅用于逻辑重用的不必要的组件实例。
有兴趣详细了解的小伙伴可以移步vue-composition-api
总结
总的来说,基于api的开发方式更灵活了,更上“档次”了,有木有~,期待正式版早点发布
最后,文章也只是我个人在学习过程中的一点心得,欢迎大家一起交流经验。如果文章对你有帮助的话,麻烦各位小伙伴动动小手点个赞吧 ~