Vue3新版本OnePiece,是时候学习Vue3了

1. 前言

在一系列的测试版本后,Vue3终于迎来了新版本,官方正式将其取名为Vue3 One Piece

image-20200920170937773

1.1 更好的tree-shaking

所谓tree-shaking,就是在你项目中没有用到的第三方包,没有用到的变量,没有用到的函数,没有用到的方法等等,都不会被打包进生产环境中。

与Vue 2相比,Vue 3在捆绑包大小通过tree-shaking减少了多达41%的体积,初始渲染加速多达55%,更新加快了133%,内存使用率方面表现出了显著的性能改进。

1.2 TypeScript

在Vue3中,如果你使用TypeScript,那么会拥有更好的类型推断,在Vue2.x时期其实TypeScript和Vue搭配其实并不是那么默契,Vue2.x并不能完全发挥出TypeScript的优势。而到了Vue3,Composition API可以很好地处理类型推断。

1.3 实验特性

<script setup>:用在单一vue文件中的语法糖,因为Vue3中,几乎所有的内容都要写在setup()中,所以官方引入了<script setup>优化体验,具体可以参考官方文档

<style vars>:可以在Vue3<style>标签中使用<script>标签中声明的变量,或者是其它的状态,具体可以参考官方文档

<Suspense>:当前正在开发中的一个组件,该组件允许async setup()在初始渲染或其它组件上等待嵌套的异步依赖项(异步组件或带有的组件),可能会在Vue3.1版本中发布。

1.4 Composition-api

说到Vue3就不得不说到composition-api,在Vue2中,提供大量的钩子函数,也就是说编写代码要按照一定的模板,比如数据就只能放在data()中,方法只能放在methods中,按照模板编写代码对于新手来讲可能是好事,但是一旦项目变大,维护起来就显得很困难。

下图的左边图示,即Vue2使用的Options-api,图中相同的颜色对应是组件的一种功能,可以看到为了实现一种功能,我们所写的代码是非常分散的。

如果组件逻辑复杂,代码量多,我们添加新代码不光要不停的上下滑动,而且在后期代码维护中,阅读起来也变得十分的困难,因为实现一种功能的代码并没有集中在一起。

file explorer (comparison)

而在Composition-api中,我们可以把实现一种功能的代码写在一起,甚至还可以把它们单独抽取在一个js文件或者一个函数中。

js文件中也可以引用Composition-api的生命周期函数。这将极大的提高代码的可维护性。

import { onMounted, ref, Ref } from "vue";

function count() {
  let count: Ref<number> = ref(1);
  const changeCount = () => {
    count.value += 2;
  };
  onMounted(() => {
    console.log("就算不在组件模板中,也可以调用生命周期函数");
  });
  return {count, changeCount};
}

export default {
  name: "App",
  setup() {
    return {
      // 通过展开语法,可以直接返回count, changeCount
      ...count()
    };
  }
};

2. Vue2.x存在的问题

2.1 mixin

mixin,官方翻译叫做混入,也就是将组件中通用的属性和方法抽离出来,在其它的组件中进行引入。

混入— Vue.js

也许很多人都没有使用过混入,虽然这种方式给编写组件时带来了极大的方便,但却也大大的提高了代码的维护难度,或许你自己写的代码,使用了很多混入,几周或者几个月后再次看这些代码,你可能并不知道代码中的一些属性和方法从什么地方进行调用。

而且不同的mixin中,命名空间还可能发生冲突,在项目中大量的使用混入后,其它人读到这个项目时,很可能一时半会不清楚这个变量在哪里声明过,那个方法又是在哪里调用过。

2.2 this

this这个问题,不管是1年,2年甚至10年的JavaScript使用经验,都有可能在这个地方翻车,虽然ES6时代来临后,this混乱的问题大大的改善了,但是还是不够,关于this,可以看这篇文章:Post not found: 面试/JavaScript:充满玄学的this指向,真的有点难 JavaScript:充满玄学的this指向,真的有点难

而在Vue3中,因为几乎所有的代码都写在setup()这个方法中,所以就不需要再通过this.语法来进行调用。

而在Vue2.x时代,我们在script标签中,调用data()computedmethods等一系列钩子中的属性和方法,都要通过this.xxx语法进行调用。

3. 新特性

3.1 响应式原理

vue3.0更换了响应式的原理,在vue2.0是通过数据劫持结合发布者-订阅者模式实现的,具体可以看:

面试/手动实现Vue双向绑定

深入响应式

而vue3.0是通过**代理(Proxy)**实现的双向绑定,简单的说,在Vue2.x中,如果一个对象添加了一个新属性,在界面上是不会发生改变的,而在Vue3中,界面就会发生改变。

3.2 Hooks

Vue设计之初就参考了Angular和React,可以说vue是站在这两个巨人的肩膀上发展起来的,我对React的了解不多,但是去年有一件刷爆了前端的事情就是React Hooks的发布,虽然国内用react的公司远远没有使用vue的公司多(至少我是这么认为的),但是你经常浏览掘金等类似的网站,那么肯定对React Hooks这个名词不陌生。

4. 生命周期

vue3.0的生命周期的名称几乎全部修改了。

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

4.1 新增钩子函数

  • onRenderTracked
  • onRenderTriggered

两个钩子函数都接收一个 DebuggerEvent,与 watchEffect 参数选项中的 onTrackonTrigger 类似:

export default {
  onRenderTriggered(e) {
    debugger
    // 检查哪个依赖性导致组件重新渲染
  },
}

5. 开始使用

5.1 setup()

灵魂函数,作为vue3.0最重要的函数,作为在组件内使用Composition API的入口点。在Vue3中,几乎所有的方法和函数以及变量声明都写在setup()中。

它会在组件实例被创建后,props初始化完毕后调用。即在beforeCreate之前被调用。

如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文:

<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>

<script>
import { ref, reactive } from "vue";

export default {
  setup() {
    const count = ref(0);
    const object = reactive({ foo: "bar" });

    // 暴露给模板
    return {
      count,
      object
    };
  }
};
</script>

注意 setup 返回的 ref 在模板中会自动解开,不需要写 .value

5.1.1 参数

propssetup()的第一个参数为props,并且props的对象是响应式的。

export default {
  props: {
    name: String,
  },
  setup(props) {
    console.log(props.name)
  },
}

注意:不要结构props,一旦结构,props会失去响应性。

export default {
  props: {
    name: String,
  },
  setup({ name }) {
    watchEffect(() => {
      console.log(`name is: ` + name) // Will not be reactive!
    })
  },
}

如果引入了Eslint,一旦结构props就会报错:

image-20200920222520449

并且props是不可变的,一旦尝试修改props的值,会触发警告。

context:从原来 2.x 中 this 选择性地暴露了一些 property。

const MyComponent = {
  setup(props, context) {
    context.attrs
    context.slots
    context.emit
  },
}

注意:在setup()中,不能使用this.xxx

5.2 reactive和ref

响应式api,与Vue2.x系列最大的区别,最开始使用的时候可能会有一点点的不习惯,如果要让属性具有响应式,就必须从vue中引入这两个方法,并且用其中的一种方法进行声明。

import { ref, reactive } from "vue";

5.2.1 reactive

接收一个普通对象然后返回该普通对象的响应式代理。

const obj = reactive({ count: 0 })

5.2.2 ref

接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性。

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

5.2.3 区别

ref:接收一个属性,在方法中访问它的值需要使用xxx.value

reactive:接收一个对象,在方法中可以直接访问。

5.3 computed

计算属性,也是变化比较大的一个,在vue2.x中,我们是这样使用计算属性。

computed:{
  data(){
    return this.data;
  }
},

而在Vue3中,需要使用getter,setter函数。

const data = computed({
  get: () => {
    return d.value + 1;
  },
  set: (val) => {
    d.value = val - 1
  }
});

5.4 readonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理,注意:该代理是深层次的,内部的所有属性都是只读的。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})

// original 上的修改会触发 copy 上的侦听
original.count++

// 无法修改 copy 并会被警告
copy.count++ // warning!

5.5 watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 打印出 0

setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

再调用一次监听函数,即可以停止监听。

const stop = watchEffect(() => {
  /* ... */
})

// 之后
stop()

6. vite

关于vite,可以看这里:前端开发环境下的新工具:vite

目前vite暂时只支持Vue3,vite这玩意实在是太厉害了,估计再过一段时间,vite会在前端开发中占据一席之地。

就目前来说,vite没有什么明显的缺点,如果非要说缺点的话,就是目前vite仅仅只支持Vue3。

7. 最后

以上的内容只是Vue3的冰山一角,不过因为composition api的原因,Vue对于中大型项目不再显得那么吃力。

另外,官方文档远比本篇文章介绍的要详细,推荐直接食用官方文档。

Vue3的官方文档

composition api