原文:https://github.com/vuejs/rfcs


引言


Vue 3.0即将来领,目前已经发布了alpha版本,除了服务端渲染,其它工作已经全部完成。尤大大也升级了vue-loader,提供了一个可以使用.vue组件的测试模板。除了最令人期待的composition-api,还有哪些RFC呢,我们直接来看看。


概览

https://github.com/vuejs/rfcs中的一部分RFC已在2.6.x中被合入,比如v-slot


0001-new-slot-syntax

0001和0002几乎可以说是同一个提案,针对slot-scope过于冗杂的问题,提出了v-slot的替代方案,已在2.6.x中被合入。


0003-dynamic-directive-arguments

0003号提案-在指令参数中支持动态值。在之前的版本中,由于指令参数是静态的,所以我们在传递props或者事件的时候,key必须是一个静态字符,当然我们也可以通过无参数对象绑定来利用动态键(如下)。

<div v-bind="{ [key]: value }"></div>

然而对于使用者来说这种使用方式太扯淡了。并不是所有人都知道这种方式。0003提案提出了支持动态值的方案。我们可以想一下这种方式来使用

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

<!-- v-bind shorthand with dynamic key -->
<div :[key]="value"></div>

<!-- v-on with dynamic event -->
<div v-on:[event]="handler"></div>

<!-- v-on shorthand with dynamic event -->
<div @[event]="handler"></div>


这个提案也在2.6.x中被合入。


0004-global-api-treeshaking

0004号提案-通过命名导出公开尽可能多的 api,使 Vue的一些全局API可以通过tree-sharking按需引入。

这是一个为性能考虑的提案,为了使尽可能少的引入不被使用的API,意味着在3.0中我们无法自由的使用nextTick这一类全局的API了,同样的一些使用了全局API的插件等都需要为此做兼容。

import { nextTick, observable } from 'vue'

nextTick(() => {})

const obj = observable({})


0005-replace-v-bind-sync-with-v-model-argument

0005号提案-删除 v-bind 的. sync 修饰符并用 v-model 上的参数替换它。


来来去去的sync修饰符,从vue1.0引入,2.0删除,2.3再次引入,如今又被提议删除。sync之前被用于props的双向绑定,在2.3后已语法糖的形式引入。


提案希望移除.sync,用v-model来替换

<MyComponent v-bind:title.sync="title" />
替换为
<MyComponent v-model:title="title" />


好消息是,由于之前已经有过一次移除的经验,所以本身sync使用的并不多(并且官方也并不推荐使用),所以理论上迁移成本不高。



0006-slots-unification

0006号提案-统一普通槽和作用域槽的概念,它们都只是 v3中的槽。


和0001和0002提案类似,将slots统一,0001提案已经提出使用v-slot来统一插槽,并且在2.6中已实现,本提案更多的是提议将插槽的内部实现统一,在之前的版本中具名插槽与作用域插槽内部实现是不一致的。


0007-functional-async-api-change

0007号提案-

1.针对函数组件的提案,要求函数组件必须写成普通函数(抛去单文件中的函数组件声明)

2.针对异步组件,必须通过专用的 API 方法创建异步组件


针对之前过于复杂的函数组件,0007号提案提出简化函数组件,不过函数组件本身使用不多,所以这个提案的影响仿佛不是很大。

import { h } from 'vue'

const FunctionalComp = props => {
  return h('div', `Hello! ${props.name}`)
}
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => import('./Foo.vue'))


0008-render-function-api-change


0008号提案-渲染函数API的变更,主要体现在三点:

  1. h全局导入,而不是传递给渲染函数作为参数
  2. 渲染函数参数已更改,并使stateful组件和functional组件之间保持一致
  3. VNode现在具有拉平的props结构


这个针对render函数的RFC对于使用单文件组件和模板编译的使用者来说,几乎没有影响,毕竟loader这边会去处理这一层转换,但是对于习惯使用render函数或者JSX的用户来讲,大概还是有一定影响的。


我们看一下vue2.x使用render函数

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子节点数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

比较重要的是我们需要显式的传递createElement这个方法(或者使用通用的简写h),为什么需要显式的传递h呢,由于我们的VNode的创建是依赖于组件实例的,比如你这个组件注册了子组件或者指令,那么这个子组件和指令就绑定在你这个组件实例上了,当你调用createElement的时候才能找到对应的组件或者指令,所以createElement函数需要关心是哪个组件实例在调用它,而无法变成一个通用的方法


h全局导入取决于3.0将VNode变为了context free。当然这也意味着我们无法直接通过context找到组件和指令了,这对于之前的写法的打击是致命性的,对此,Vue提供了几个全局的方法,来绑定指令和组件。

import { h, resolveComponent, resolveDirective, withDirectives } from 'vue'

export default {
  render() {
    const comp = resolveComponent('some-global-comp')
    const fooDir = resolveDirective('foo')
    const barDir = resolveDirective('bar')

    // <some-global-comp v-foo="x" v-bar="y" />
    return withDirectives(
      h(comp),
      [fooDir, this.x],
      [barDir, this.y]
    )
  }
}


0009-global-api-change

0009号提案-全局api的变更。

0009号提案的最大一点在于新设计应用程序启动和全局 API。问题来源于Vue 当前的一些全局 API 和配置会永久性地改变了全局状态,比如Vue.use和Vue.mixin这类的全局API,当我们使用这类API的时候,尽管我们是希望创建两个不一样的app,但是app1和app2还是公用了一个全局配置。

Vue.mixin({ /* ... */ })
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })


为此,0009号提案提出引入了一个新的全局 API,createApp来代替原有的new Vue操作(??,仿佛离React又近了一步)。

import { createApp } from 'vue'

const app = createApp({
  /* root component definition */
})

调用 createApp 将返回一个应用实例。 应用程序实例提供应用程序上下文。 由应用程序实例挂载的整个组件树共享相同的应用程序上下文,它提供了以前在 Vue 2.x 中是“全局”的配置。


对应的,一部分之前挂载在Vue上的全局配置和方法,都会映射到app上,比如Vue.config,Vue.use等,详细的参见https://github.com/vuejs/rfcs/blob/master/active-rfcs/0009-global-api-change.md


在挂载上,与2.x区别不大,仍然可以使用mount方法挂载,当然需要注意的是在2.x 中,根实例使用目标元素的 outerHTML 作为模板,并替换目标元素本身。然而在3.x 中,根实例使用目标元素的 innerHTML 作为模板,只替换目标元素的子元素。意味着以下这条在3.0中仍会保留#app这个dom。

const rootInstance = app.mount(App, '#app')

这个提案对一些全局的指令插件等会造成一些比较大的影响,毕竟无法直接使用Vue.use等方法了。



0010-???

神秘消失的0010号提案。。


0011-v-model-api-change

和0005号提案相关,0011号提案提出v-model的一些更改。


提案对于之前的v-model提出了一下问题,比如当我们需要在一个非input类型的dom上(其实就是不存在value这个attr的dom上)绑定v-model时,是没有作用的,另外当我们需要绑定多个v-model的时候,也是不适用的。


说白了,之前v-model的设计更多只是单纯为input这类表单型涉及双向绑定的dom设计的,在现在的情况下,我们更需要将v-model做扩展。


所以基于0005号提案,将v-model改造成了更贴近v-bind的使用(比v-bind多了一个双向绑定的功能)。我们可以这样使用v-model

<InviteeForm
  v-model:name="inviteeName"
  v-model:email="inviteeEmail"
/>
    
//编译成
    h(InviteeForm, {
  name: inviteeName,
  'onUpdate:name': name => (inviteeName = name),
    email: inviteeEmail,
  'onUpdate:email': email => (inviteeEmail = email)
})

同样,为了更贴近v-bind,还支持.trim这类修饰符的使用,某种意义上是比v-bind多了一个自定义的update。


0012-custom-directive-api-change


0012提案-重新设计定制指令 API,以便更好地与组件生命周期保持一致。


提案指出现有的自定义指令的生命周期与组件的生命周期差异过大,为了更好的理解和维护,需要将自定义指令钩子名称与组件生命周期更加一致(当然,似乎只是命名上的一致性,对于指令内部的实现并没有提出改动)。

以下是变动了的指令生命周期

  • bind -> beforeMount
  • inserted -> mounted
  • beforeUpdate new,在元素本身更新之前调用
  • update  删除,使用updated 代替
  • componentUpdated -> updated
  • beforeUnmount new
  • unbind -> unmounted



0013-composition-api


登登噔噔,到了最重要的一个RFC登场了,composition-api汇聚了无数焦点目光的一个RFC,也是迄今最长的一个RFC。

一句话,增加了一组附加的、基于函数的 API,允许组件逻辑的灵活组合,详细的我们在后面再着重介绍。



0014-drop-keycode-support


0014号提案-删除对使用数字(keyCodes)的支持以及移除config.keyCodes全局配置。


这意味着@keyup.13这类的修饰符将不被识别,也无法通过Vue.config.keyCodes来全局配置键盘值。

所有都将依赖于标准的keyboarddevent.key。

<input @keyup.13="onEnter"> // 无法使用


0015-remove-filters

0015号提案-移除对filters的支持。


也是一个小点,所有filters都可以通过methods或者computed的方式去实现,这个语法糖并没有特别的用处。


0016-remove-inline-templates

0016号提案-删除对内联模板特性的支持


一个比较冷门的能力。inline-template。官方建议,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 <template> 元素来定义模板。


所以删除的影响不大。


精读


Vue新版本的RFC大概有30多条,涉及到Vue3.0方方面面的新特性,除了composition API外,还有一些值得我们去深入探究为什么会有这个RFC提出。


slot的变化


上面的几个提案中,有两个提案都提到了slot的改版, 0001-new-slot-syntax0002-slot-syntax-shorthand0001号提案提出了统一slot和slot-scope为v-slot,0002号提案提出了v-slot的缩写。当然提案还提出了不允许在没有模板的情况下使用插槽作用域。


在这两个提案中(当然,其实已经在2.6+上实现了)v-slot指令代替slot,用在模板槽容器上表示传递给组件的槽,其中槽名称通过指令参数表示,同时也代替slot-scope,使用指令的属性值声明接收到的 slot 的 props。


关于slot相关的详细介绍在这篇文章中做了详细的介绍:https://www.yuque.com/xixiaobai/inzwn0/dcqpct



global api的变化


0004-global-api-treeshaking,0008-render-function-api-change,0009-global-api-change三个提案都涉及了全局API的改动。


在之前的Vue中,global API都公开在一个 Vue 对象上,我们需要通过Vue.nextTick这样来使用全局API,这就意味着,不管你使不使用这些全局API,只要你引入了Vue,最终都会打包这些API,这对最终打包出来的代码大小是有影响的。于是提出了将部分全局API单独导出,使用方可以通过tree-sharking按需引入。


原来创建Vue实例的方法也被导出为一个名为createApp的方法。


之前我们通过new一个Vue的实例,来创建应用,现在我们需要通过createApp方法来创建Vue实例,当然这个点变化倒是不大,更大的是原来的全局配置,比如注册指令的Vue.use,mixin的Vue.mixin会有全局方法变为直接挂载在实例上的方法,所以我们要通过创建的实例来调用这些,这个给我们带来了好处,一个应用可以有多个不同的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')



remove掉了一些功能


Vue3.0同时还remove掉了以下可以用其他能力代替的,或者没有什么用的能力。


比如更改了指令的一些生命周期,以匹配组件的生命周期。


删除对使用数字(keyCodes)的支持以及移除config.keyCodes全局配置,跟着官方标准走,直接使用原生的常量KeyboardEvent.key


移除对filters的支持,filters这个语法糖本身并没有什么特别,所有filters都可以通过methods或者computed的方式去实现。


删除对内联模板特性的支持,Vue中提供的单文件组件和render渲染方式足够使用了,很少会有人使用inline-template。




总结


Vue3.0预计在今年8月份正式发布,在正式发布之前,提前熟悉一些Vue的新特性,对于我们理解和使用Vue3.0还是有很大帮助的,没有什么比直接看RFC能更接近Vue的开发思路了。