# Vue3 RFCS导读

为了对即将到来的vue3有个全面的认识,通读Vue3 rfcs (opens new window)(意见修改稿)是有必要的。但原版英文内容比较长,通读时间比较耗时。这里笔者根据原文内容总结输出,方便大家对Vue3细节改动有个全局的认识。更多详细设计请在每个章节链接进去查看。

# 1. componsition api (opens new window)

核心变动,增加setup选项,更多动机和设计看官方教程 (opens new window)

// 基础示例
<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>

# 2. slot统一

  1. 模板上slot和slot-scope统一为v-slot
  2. 模板上支持v-slot简写为#
  3. this.$slots和this.$scopedSlots统一为this.$slots,并且暴露为funciton函数。(ps:集合了this.$slots和this.$scopedSlots各自特点)

相关rfcs:

  1. https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md
  2. https://github.com/vuejs/rfcs/blob/master/active-rfcs/0002-slot-syntax-shorthand.md
  3. https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md

# 3. 动态指令参数 (opens new window)

指令参数支持动态设置

<div v-bind:[key]="value"></div>
<div v-on:[event]="handler"></div>

# 4. v-model变动

  1. v-model支持参数: 使用v-model:arg语法 代替:arg.sync
  2. 不带参数的v-model,默认事件名为update:modelValue,而不是以前的事件名:input。(ps:主要还是统一上一条变动)

相关rfcs:

  1. https://github.com/vuejs/rfcs/blob/master/active-rfcs/0005-replace-v-bind-sync-with-v-model-argument.md
  2. https://github.com/vuejs/rfcs/blob/master/active-rfcs/0011-v-model-api-change.md
<!--模版里不再有.sync语法-->
<MyComponent v-model:title="title" />

# 5. functional api去除 (opens new window)

关键点:

  1. 不再需要有functional选项api
  2. 函数式组件不再是对象,而变成类React函数式
  3. 入参有变化
// 基本展示
import { h } from 'vue'

const FunctionalComp = (props, { slots, attrs, emit }) => {
  return h('div', `Hello! ${props.name}`)
}

# 6. 全局api tree shaking (opens new window)

所有挂载在Vue对象的方法,都单独出去了,比如Vue.nextTick、Vue.observable

# 7. render api修改 (opens new window)

关键点:

  1. h函数全局导入
  2. h函数参数统一,不管是有状态的组件还是函数式组件
  3. VNodes数据结构优化展平(ps:非常实用的改动,写jsx简单了)
// 全局导入 `h`函数
import { h } from 'vue'

export default {
  render() {
    return h(
      'div',
      // // vnode数据结构更直观
      {
        id: 'app',
        onClick() {
          console.log('hello')
        }
      },
      [
        h('span', 'child')
      ]
    )
  }
}

# 8. 增加createApp api (opens new window)

创建app实例,而不是像以前共享同一个Vue实例

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

app.config.globalProperties.customProperty = () => {}

app.mount(App, '#app')

# 9. 自定义指令api变更 (opens new window)

自定义指令跟vue hook命名一致

const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // new
  unmounted() {}
}

# 10. 废弃keycode修饰符 (opens new window)

因为KeyboardEvent.keyCode (opens new window)已经被废弃,故在vue3中移除

# 11. 移除filter api (opens new window)

使用方法或computed代替filter

<!-- before -->
{{ msg | format }}

<!-- after -->
{{ format(msg) }}

# 12. 组件过渡类名重命名 (opens new window)

from/to名称对称,更好理解

  1. v-enter 重命名为 v-enter-from
  2. v-leave 重命名为 v-leave-from
.v-enter-from, .v-leave-to {
  opacity: 0;
}
.v-enter-active {
  opacity: 0.5;
}
.v-leave-from, .v-enter-to {
  opacity: 1
}
.v-leave-active {
  opacity: 0.5;
}

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter-from 会替换为 my-transition-enter-from

# 13. 响应式data api只支持function方式 (opens new window)

以前2.x响应式data定义,支持object和function方式(当然大部分人约定俗成的都使用function方式),在vue3中强制data只能用function。

因为如果使用object定义data时,当有多个组件实例共用同一个引用类型data,容易造成错乱。

import { createApp, h } from 'vue'

createApp().mount({
  data() { // data一定是个函数
    return {
      counter: 1,
    }
  },
  render() {
    return [
      h('span', this.counter),
      h('button', {
        onClick: () => { this.counter++ }
      }),
    ]
  },
}, '#app')

# 14. 移除$on, $off, $once (opens new window)

Vue3不再提供事件发布接口,如果有发布事件需要,可以使用mitt (opens new window)库代替

# 15. 嵌套路由meta自动合并 (opens new window)

以前处理页面权限时,常使用meta作为配置方案,当匹配到单个页面时,判断to.meta.requiresAuth即可。但在嵌套路由页面时,子路由页面一般没有再设置requiresAuth,所以在Vue2.x中只能通过to.matched获得匹配数组再逻辑判断。

Vue3提供了嵌套路由meta的自动合并,使得逻辑判断更加简单。注意这里的合并是Object.assign浅拷贝。

加入给定嵌套路由如下:

{
  path: '/parent',
  meta: { requiresAuth: true, isChild: false },
  children: [
    { path: 'child', meta: { isChild: true }}
  ]
}

导航到/parent/child时,to.meta属性变为:

{ requiresAuth: true, isChild: true }

# 16. css中scope变更 (opens new window)

使用::v-deep()代替Vue2.x中的 >>>/deep/

<style scoped>
/* deep selectors */
::v-deep(.foo) {}

/* targeting slot content */
::v-slotted(.foo) {}

/* one-off global rule */
::v-global(.foo) {}
</style>

# 17. falsy属性转换策略 (opens new window)

在Vue2.x template模板中,属性为“falsy”值(undefined,null,false)时,会被“removeAttribute”,源码可看这里 (opens new window)

在Vue3中,“falsy”中去掉了false。当为false时,会作为attribute=false,当为undefined或null,跟2.x一致会removeAttribute。

# 18. 异步组件api (opens new window)

新语法defineAsyncComponent支持

import { defineAsyncComponent } from "vue"

// simple usage
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))

// with options
const AsyncFooWithOptions = defineAsyncComponent({
  loader: () => import("./Foo.vue"),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000
})

# 19. 移除内联模板api (opens new window)

冷门的api,这个api可以看Vue官方内联模板使用说明 (opens new window)

# 20. emit事件数据校验 (opens new window)

增加新的 emits 选项api,可以对emit触发的事件数据,进行校验

const Comp = {
  emits: {
    submit: payload => {
      // validate payload by returning a boolean
    }
  },

  created() {
    this.$emit('submit', {
      /* payload */
    })
  }
}

# 21. Vue Test Utils提升异步流 (opens new window)

Vue单元测试时,允许使用await语法触发re-render

const wrapper = mount(Component)
await wrapper.find('.element').trigger('click')
// 不再需要如下,在下一个事件循环中拿到dom值
//  await wrapper.vm.$nextTick()
expect(wrapper.find('.finish').attributes('disabled')).toBeFalsy()

# 22. router路由改动

  • 路由新增router.push返回Promise值,router.afterEach、router.onError也返回Promise值
  • 更方便的动态增、删、查路由信息,对应api:router.addRoute、router.removeRoute、router.hasRoute、router.getRoutes

相关rfcs:

  • https://github.com/vuejs/rfcs/blob/master/active-rfcs/0033-router-navigation-failures.md
  • https://github.com/vuejs/rfcs/blob/master/active-rfcs/0029-router-dynamic-routing.md