关于vue3你须要知道的事

1. 写在前面的话

vue3在今年内应该就会推出,尤大以前也针对vue3的设计和api给出了RFC和最新征求意见稿。本人阅读后总结了一些我的心得,但愿对你们有用。javascript

2. Vue3 Function-based API RFC

vue2在近几年不太长的生命周期内,经历了大小各类项目的历练,能够说很是易于上手,方便开发。同时也暴露了很多问题亟待解决:css

  1. 组件之间难以逻辑组合与复用
  2. vue2缺乏类型推导,即typescript支持
  3. 打包尺寸较大,vue2打包时会将全部api的核心代码打包,不论其在开发中是否有用到
  4. definePropterty()实现的数据绑定没法实现数据对象新增属性值的变化,且vue中的数组方法也是本身polyfill实现的

vue3基于上述弊端从新设计了内部的实现方式,而且同时从新设计了组件选项和api。主要有:html

从新设计了响应式数据对象,内部实现从defineProperty改成由浏览器原生支持的proxy实现。包括手动定义响应式数据(reactive)和包装对象(ref)两种api方式建立响应式数据。前端

  • 将数据变为响应式数据,能够追踪数据的变化,包括数组和对象的属性变化
  • 包装对象数据以变量的形式引用时(使用/修改)须要以a.value这种方式

示例代码:vue

// reactive响应式数据
import { reactive } from 'vue'
const state = reactive({
  count: 0
})

function increment() {
  state.count ++
}
复制代码
// ref包装对象
import { ref } from 'vue'
const count = ref(0);

// 以变量的形式引用包装对象
function increment() {
  count.value ++
}
复制代码

新增了watchEffect api,和vue2中的watch效果相似,监测响应式数据变化并执行反作用函数。java

  • watchEffect将会在建立的时候当即执行一次,以后会在renderer flush即DOM更新后再执行回调函数,组件销毁时watch中止观察。
  • watchEffect不须要将被依赖的数据源和反作用回调函数分开,只需写出反作用函数,便可自动将反作用中的全部响应式状态的 property 做为依赖进行追踪
import { reactive, ref, watchEffect } from 'vue'

const state = reactive({
  count: 0,
})
const titleCount = ref(0);

// 当state.count、titleCount变化时,watchEffect将会自动执行反作用回调函数
watchEffect(() => {
  document.body.innerHTML = `count is ${state.count}`
  document.title = `title count is ${titleCount}`
});
复制代码

从新设计了computed api,将会建立一个依赖其它状态的响应式状态。被依赖的状态能够是由reactive或ref建立响应式数据,生成的状态则是一个只读的包装对象,以变量的形式使用时须要以.value这种方式。react

import { reactive, ref, computed } from 'vue'

const state = reactive({
  count: 0,
})
const num = ref(0);

const double = computed(() => state.count * 2)
// 使用ref建立响应式对象时须要用.value取值计算
const triple = computed(() => num.value * 3)

watchEffect(() => {
  console.log(double.value) // -> 0
  console.log(triple.value) // -> 0
})

state.count++ // -> 2
num.value++ // -> 3
复制代码

新设计了setup()组件选项,代替以前的data组件选项。这个函数是组件开始逻辑的地方,它接收组件的props属性做为参数,而后开始调用,整个组件生命周期内只会执行一次。webpack

  • 在setup中建立响应式数据对象,并return出去暴露给模板的上下文使用
  • setup能够将函数方法返回出去,给模板使用
  • setup接收的组件属性props自己也是个可追踪的响应式数据
  • vue2中的生命周期通过重命名,只在setup中执行
import { ref } from 'vue'

const MyComponent = {
  setup(props) {
    const msg = ref('hello')
    const appendName = () => {
      msg.value = `hello ${props.name}`
    }
    return {
      msg,
      appendName
    }
  },
  template: `<div @click="appendName">{{ msg }}</div>`
}
复制代码

类型推导,须要使用defineComponent函数定义组件才能支持typescript类型推导web

import { defineComponent, ref } from 'vue'

const MyComponent = defineComponent({
  // props选项声明用来定义prop属性类型
  props: {
    msg: String
  },
  setup(props) {
    console.log(props.msg) // string 或 undefined

    // setup中返回的数据对象能够在模板中用于类型推断推断类型
    const count = ref(0)
    return {
      count
    }
  }
})
复制代码

在了解了上面的一些基本api后,咱们能够看一下尤大给出的一个基本组件写法:typescript

import { ref, computed, watchEffect, onMounted } from 'vue'

const App = {
  template: ` <div> <span>count is {{ count }}</span> <span>plusOne is {{ plusOne }}</span> <button @click="increment">count++</button> </div> `,
  setup() {
    // reactive state
    const count = ref(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watchEffect(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
复制代码

这里只介绍了少数比较重要的vue3 api,并且不肯定最终api还会不会再改动,若是想要详细查询学习的同窗能够去查看官方api文档

3. vue3对比react hooks

尤大亲自说过vue3的最新设计很大程度上是借鉴了react hooks的思路。具体类似的地方至少有:

  1. 定义数据的写法方式相似,react使用useState,vue3使用ref,建立响应式数据
  2. setup设计借鉴了react hooks,抽取函数和复用逻辑的方式相似,将逻辑和数据抽取成为独立的函数进行复用。

官方文档也给出了一个将获取鼠标位置的功能抽取成独立函数的实例:

// 抽取获取鼠标x, y位置的成函数 mouse.js
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()
    // 其余逻辑...
    return { x, y }
  },
}
复制代码

若是是写过react hooks的同窗会发现,这与react hooks抽取公用函数的写法一模一样。换句话说,写过二者其一的话,都有助于咱们理解另外一个框架。

虽然vue3的写法相似于react hooks,可是其内部实现思路是不一样的:

  1. react hooks函数组件每次渲染的时候会从新执行一遍,而vue3的setup只会在组件初始化的时候执行一次。
  2. react hooks的useState建立组件状态值,处在一个独立的闭包空间内,每次使用的时候从这个独立空间从新获取值。而vue3的包装对象则是使用proxy将数据对象变成可追踪的响应式数据,这个思路跟vue2是同样的。

vue3的设计在尤大看来至少有一下优点:

  1. 总体上更符合 JavaScript 的直觉
  2. 不受调用顺序的限制,能够有条件地被调用
  3. 不会在后续更新时不断产生大量的内联函数而影响引擎优化或是致使 GC 压力
  4. 不须要老是使用useCallback来缓存子组件的回调防止过分更新
  5. 不须要关注useEffect/useMemo/useCallback的deps依赖数组是否正确的问题

4. 新api所带来的心智负担

在阅读api设计文档时确实发现一些容易让开发者搞晕的东西,尤大对此也进行了总结。对于我我的来讲有如下比较麻烦的地方:

  1. 使用ref建立的响应式数据难以区分何时须要使用.value方式访问
  2. 难以区分何时该使用reactive,何时该用ref建立响应式数据
  3. 写法不正确时,使用reactive建立的响应式数据有丢失响应式的可能
  4. 对setup赋予的职责过大,且建立响应式数据和响应函数的逻辑十分灵活,几乎没有限制。会致使不一样开发者写出来的代码可读性差距极大。

我我的但愿vue3正式推出的时候,文档可能提供api使用推荐写法,同时能提供相应的lint校验插件辅助,这样才好让不一样开发者写出通用可读的代码。

5. vue3的新一代构建工具vite

随着vue3而来的构建工具不是webpack,而是vite一种新型的项目构建工具。vite做用相似webpack,可是原理与webpack不一样。对比webpack有如下优点:

  1. 快读冷启动
  2. 瞬间热更新
  3. 真正的按需编译

我也研究了一下vite的技术原理:

  1. 浏览器原生支持(ie除外)的ESM(script module)api,经过在script标签中添加type="module"就能够在代码中使用export import语法,经过浏览器端直接导入、导出模块使用。
  2. ESM的原理是浏览器将import导入的模块转换成http请求加载对应的资源,须要什么模块就加载对应的模块。
  3. vite将开启一个本地的koa服务,劫持ESM中由import导入模块时的http请求,因此vite与webpack不一样,不会对代码中所须要的模块进行静态分析并抽取成bundle。
  4. vite劫持后import导入的模块的http请求后,判断请求文件类型,对不一样的模块文件使用不一样的方法进行解析处理,如对vue文件,使用的就是vue的自带的模板解析/css解析/等功能,对css文件/ts文件等也是有相应的语法解析工具进行处理。

6. 给计划使用vue3同窗的一点建议

  1. 能够对比性的使用react hooks试试看,目前react hooks也相对比较成熟了。
  2. 建议学习typescript,能够的项目可使用typescript开发试试。

做者简介: 宫晨光,人和将来大数据前端工程师。