前言:今天无意翻看到从去年至今,我已经在掘金阅读过1000+的文章,但是又回想起现在自己还这么菜,不由得狠狠地拍了一下大腿:我真TM菜。
距离 Vue3.0-beta 版发布已经有一月有余。今天终于能腾出点时间来看看新版Vue给我们带来了哪些令人惊艳特性。
虽然掘金上已经有很多关于Vue3.0的优质文章,但是我还是先从如何升级至vue3.0-beta写起。
2.x 升级至 3.0
目前如果Vue3.0需要在使用vue-cli创建项目后,执行vue add vue-next安装插件来实现。
注意:如果你想正常使用Vue3.0请确保你的vue-cli已经升级到最新版。截止目前vue-cli版本为:v4.3.1,预览版:v4.4.0。
写法有变化吗?
肯定有,3.0最为直观的变化就是在写法上有了巨大的变化,将 2.x 中与组件逻辑相关的选项以 API 函数的形式重新设计。将setup()函数作为组件选项的入口,详情可以查看 Vue-Composition-API ,这种写法看上去很像React的写法。那么这种变化是不是会增加Vue的学习成本呢?答案是否定的。下面将介绍2.x和3.0的部分组件选项写法对比。
声明变量
2.x 中声明变量:
<template>
<div>
<div> {{message}} </div>
<h2> userinfo </h2>
<div>name: {{userInfo.name}}</div>
<div>name: {{userInfo.age}}</div>
<div>name: {{userInfo.status}}</div>
<div>name: {{userInfo.dream}}</div>
</div>
</template>
<script>
export default {
name: 'BlindDate',
data() {
return {
message: 'hello girl!',
userInfo: {
name: 'lq9958',
age: 18,
status: 'single',
dream: 'find a girlfriend'
}
}
},
...,
}
</script>
3.0中声明变量:
//html
<template>
<div> {{message}} </div>
<h2> userinfo </h2>
<div>name: {{userInfo.name}}</div>
<div>name: {{userInfo.age}}</div>
<div>name: {{userInfo.status}}</div>
<div>name: {{userInfo.dream}}</div>
</template>
<script>
// 3.0中声明变量
import {ref reactive } from 'vue'
export default {
name:'BlindDate',
setup() {
const message = ref('hello girl!')
const userInfo = reactive({
name: 'lq9958',
age: 18,
status: 'single',
dream: 'find a girlfriend'
})
return {
message,
userInfo
}
},
...,
}
</script>
从上面可以看到,2.x中我们声明变量需要在data()函数中声明,但是在3.0中声明变量需要在setup()中声明(几乎所有2.x的组件选项都会写在这个函数中),同时还使用了两个函数ref、reactive(3.0中需要使用vue中任何api都需要这种按需导入的形式来使用相关api,这应该是为了让vue的各个模块独立出来,同时降低打包体积而考虑的),再看看template中的代码,是不是发现3.0中包含了多个根元素。是的,这也是3.0的新特性之一,允许tempalte中包含多个根元素,关于ref、reactive这两个函数的区别我会在稍后给出,接下来再看看其他的区别。
监听 watch
2.x 中的 wath
//html
<template>
<div class="hello">
<div>{{message}}</div>
<div class="message">{{changeMessage}}</div>
<button @click="handleMessage">Bless God</button>
</div>
</template>
<script>
export default {
data() {
message: 'have no girlfriend',
changedMessage: 'click the bottom btn will have a girlfriend!'
},
watch:{
message: {
handler: function(newVal, oldVal) {
this.changedMessage = newVal
}
}
},
methods:{
handleMessage: function(){
message.value = 'lied to you'
}
},
...
}
</script>
3.0中的 watch
//html
...
<script>
import { ref,watch, watchEffect } from 'vue'
export default {
...,
setup() {
let message = ref('have no girlfriend')
let changeMessage = ref('click the bottom btn will have a girlfriend!')
// simple effect
watchEffect(()=> {
changeMessage.value = message.value
})
watch(message,(newVal,oldVal) => {
changeMessage.value = newVal
})
// method
const handleMessage = function(){
message.value = 'lied to you'
}
return {
message,
handleMessage,
changeMessage
}
}
}
</script>
在3.0中使用watch同样需要先导入 watch函数,这里可以看到我在导入watch的同时还导入了一个watchEffect,这个watchEffect是干什么的呢?翻看源码后发现其实两者是一样的,只是后者的 callback 为 null。
watch 源码
位置:vue/dist/vue.global.js
function watch(source, cb, options) {
if ( !isFunction(cb)) {
warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
`supports \`watch(source, cb, options?) signature.`);
}
return doWatch(source, cb, options);
}
watchEffect 源码
位置:vue/dist/vue.global.js
function watchEffect(effect, options) {
return doWatch(effect, null, options);
}
是的,并没有什么复杂的处理逻辑,仅仅是判断了callback是否为函数,当你使用watch时,第二个参数不为函数时,vue 就会发出警告,推荐你使用watchEffect,因为watchEffect允许你不指定监听源,你代码中涉及到的属性,只要发生变化都会立即执行watchEffect,详情请看:watch和watchEffect的区别。
相比于2.x我们如果需要监听多个数据源时,会写一大串的属性和handler,3.0只在 watchEffect函数中写执行逻辑即可。这里我们可以看到我导出了一个method。是的,在3.0中我们不在需要在methods属性中书写我们的methods(纳尼?你在说啥)
所有的
methods都将定义在setup()中,这个我在上文已经提到过。
计算属性 computed
2.x的计算属性
<script>
export default {
data() {
return {
firstName: '黑',
lastName: '嘿黑'
}
},
computed:{
fullName: function() {
return this.firstName + this.lastName
}
},
...
}
</script>
3.0中的计算属性
<script>
import {ref, computed} from 'vue'
export default {
setup() {
let firstName = ref('jack')
let lastName = ref('jones')
let fullName = computed(() => {
return firstName + lastName
})
return {
firstName,
lastName,
fullName
}
}
}
</script>
computed 目前的变化目前不是很大。
生命周期
在2.x中我们知道除去 keep-alive的两个生命周期处理函数activated、deactivated以及一个捕获异常的errorCaptured(说实话平常我都没用过这个函数,哎,再狠狠地拍一下大腿:我真TM菜)。2.x和3.0的生命周期函数使用方式都是一致,这里我只举例 onMounted这一个函数。
2.0 中的 mounted
<script>
export default {
...,
mounted() {
// do some thing
}
}
</script>
3.0中的 onMounted
<script>
import { onMounted } from 'vue'
export default {
setup() {
onMounted(() => {
// do some thing
})
}
}
</script>
生命周期函数并没有什么实质性的变化,只是名字改变了而已,下面是2.x中的生命周期函数在3.0中的对应表:
| 2.x | 3.0 |
|---|---|
| beforeCreate | setup |
| created | setup |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| update | onUpdate |
| activated | onActivated |
| deactivated | onDeactivated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
| errorCaptured | onErrorCaptured |
如何获取DOM元素
2.x中获取DOM元素
//hmtl
<template>
<div ref="root">
test message
</div>
</template>
<script>
export default {
...,
mounted() {
let root = this.$refs.root
},
...
}
</script>
在3.0中,我们已经用不着 this了,那我们如歌获取DOM元素呢?
//hmtl
<template>
<div ref="root">
test message
</div>
</template>
<script>
import { ref,onMounted } from 'vue'
export default {
...,
setup() {
let root = ref(null)
onMounted(() => {
let rootEle = root.value
})
return {
root
}
}
...
}
</script>
有没有很惊喜?获取元素居然是这样的,在3.0中,vue 如果检测到声明的变量和模板中的ref值相等的话就会在初始化完成之后将元素赋值给这个变量,详情请看
Vue3.0中获取DOM元素
上面代码中有一行是这样的let rootEle = root.value,为什么获取到的元素不是上面的root 变量,而是要去使用.value来获取,这就要提到Vue3.0的响应式设计。
Vue3.0使用的ES6中的新API Proxy 和 Reflect 来完成响应式设计。
Vue3.0提供了两种创建响应式数据的方式,也就是文章开篇提到的 ref 和 reactive,这两者有什么区别?开发时应该使用哪一种?最直接的方式就是查看源码,虽然看不懂多少。
以下内容纯属个人见解,如有错误的地方,希望各位大佬指正,谢谢。
ref 源码
位置:@vue/reactivity/dist/reactivity.global.js
// createRef 函数 为 ref 函数的核心代码
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) {
return rawValue;
}
// 这里可以看到,如果 createdRef 的第二个参数不传入 vue 默认是会进行深度代理的
let value = shallow ? rawValue : convert(rawValue);
// vue 在这里对普通 value 进行了一次包裹,返回一个包含 __v_isRef 和 value 属性的对象
const r = {
__v_isRef: true,
get value() {
track(r, "get" /* GET */, 'value');
return value;
},
set value(newVal) {
if (shared.hasChanged(toRaw(newVal), rawValue)) {
rawValue = newVal;
value = shallow ? newVal : convert(newVal);
trigger(r, "set" /* SET */, 'value', { newValue: newVal } );
}
}
};
return r;
}
也就是说在不传入第二个参数时,默认会走 convert分支,那我们看看 convert里面都有些啥。
const convert = (val) => isObject(val) ? reactive(val) : val;
当传入的值是一个对象时,则走reactive 分支,否则返回原数据,这里的 reactive正是Vue3.0创建响应式数据的第二种方式,这里先不看,我们直接返回源数据,再来看看源码:
const r = {
__v_isRef: true,
get value() {
track(r, "get" /* GET */, 'value');
return value;
},
set value(newVal) {
if (shared.hasChanged(toRaw(newVal), rawValue)) {
rawValue = newVal;
value = shallow ? newVal : convert(newVal);
trigger(r, "set" /* SET */, 'value', { newValue: newVal } );
}
}
};
return r;
看到这里诸位应该明白了,当我们传入的值不是一个对象时,Vue会对其进行一次包装,返回一个包含 __v_isRef 和 value 属性的对象, 这里 value的值正是传入的值,这也是为什么我们在使用ref方式创建一个变量时,如果对其修改需要使用.value的方式来修改。
那我们再来看看reactive:
reactive 源码
位置:@vue/reactivity/dist/reactivity.global.js
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && target.__v_isReadonly) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
return target;
}
// target already has corresponding Proxy
if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) {
return isReadonly ? target.__v_readonly : target.__v_reactive;
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target;
}
const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers);
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed);
return observed;
}
当进入reactive分支后,Vue首先会判断该数值是否为一个对象,当不是一个对象时,Vue会发出警告并直接返回这个数值。当然,这个数值也就不是响应式的了。好了,对于Vue内部是如何去对 observed 对象做代理操作的今天暂时不深究,今天的目标暂时把 ref 和 reactive 区别搞清楚:ref在内部对不同类型的数值做了处理,当我们在了解ref内部流程后应该清楚,在创建一个基本数据类型的响应式数据还是应当使用ref,当需要创建的响应式数据不是基本数据类型时,应当是使用reactive。
关于Vue的今天暂时就写到这里吧,Vue3.0到来不止是其本身的改变,她的周边配套设施同样也会随其更改,下期打算写一些关于Vue-router的东西。第一次在掘金上发布文章,平时看到各位大佬文章中要背景有背景,要颜色有颜色。哎,最后再狠狠地拍下大腿:我真TM菜。
文章中使用的3.0版本为
v3.0.0-beta.14,目前3.0仓库仍然在频繁的更新,具体使用情况还是要等到官方文档出来才能知晓。
转载或摘抄须注明出处。