- 开始时间:2019-04-08
- 目标主要版本:3.x
- 引用 issue:N/A
- 实现的 PR:N/A
摘要
重新设计应用引导和全局 API。
- 改变 Vue 行为的全局 API 现在转移到由新的
createApp方法来创建 app 的实例,并且它们的效果现在只对作用域内的 app 实例有用。 - 不改变 Vue 行为的全局 API(例如,nextTick 和 Advance Reactivity API 中提议的 API)现在 the Global API Treeshaking RFC 规范中的具名 exports。
基本范例
之前
import Vue from 'vue'import App from './App.vue'Vue.config.ignoredElements = [/^app-/]Vue.use(/* ... */)Vue.mixin(/* ... */)Vue.component(/* ... */)Vue.directive(/* ... */)Vue.prototype.customProperty = () => {}new Vue({render: h => h(App)}).$mount('#app')
之后
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')
动机
Vue 当前的一些全局 API 和配置会永久的突变全局状态。这将导致一些问题:
- 全局配置使得在测试过程中很容易意外地污染其他测试案例。用户需要小心的存储原始的全局配置,并在每次测试之后恢复它(例如,重置
Vue.config.errorHandler)。一些 API(如Vue.use和Vue.mixin)甚至无法恢复其效果。这使得涉及插件的测试特别棘手。vue-test-utils必须实现一个特殊的 APIcreateLocalVueto来处理这个问题。
- 这也使得在同一个页面上的多个 app 之间共享同一个 Vue 副本,但是拥有不同的全局配置变得非常困难。 ```javascript // this affects both root instances Vue.mixin({ / … / })
const app1 = new Vue({ el: ‘#app-1’ }) const app2 = new Vue({ el: ‘#app-2’ })
<a name="rLWjp"></a># 具体设计从技术上讲,Vue2 并没有 “app” 的概念。我们所定义的的 app 只是一个通过 new Vue() 创建的根 Vue 实例。每一个由相同 Vue 创建的根实例都共享相同的全局配置。在这个提议中,我们引入了一个全新的全局 API,createApp:```javascriptimport { createApp } from 'vue'const app = createApp({/* root component definition */})
调用 createApp 会返回一个 app 实例。一个 app 实例提供了一个 app 上下文。由 app 实例挂载的整个组件树共享用一个 app 上下文,它提供了以前在 Vue 2.x 中的 “全局” 配置。
全局 API 映射
一个 app 实例暴露了当前全局 API 的一个子集。经验法则是,任何全局范围内改变 Vue 行为的 API 现在都移动到 app 实例上了。这些包括:
- 全局配置
- 资产注册 APIs
Vue.component->app.componentVue.directive->app.directive
- 行为扩展 APIs
Vue.mixin->app.mixinVue.use->app.use
所有其他没有全局突变行为的全局 API 现在都被具名的 exports,正如在 Global API Treeshaking 中提议的那样。
唯一例外的是 Vue.extend。由于全局 Vue 不再是一个可新建的构造函数,Vue.extend 在构造函数扩展方面不再有意义。
- 对于基于 extending 的组件,应该使用
extends选项来代替。 - 对于 TypeScript 的类型推断,使用新的
defineComponent全局 API。 ```javascript import { defineComponent} from ‘vue’
const App = defineComponent({ / Type inference provided / })
请注意,是从实现上来讲 defineComponent 什么都不做 —— 它只是返回传递给它的对象。然而,就类型而言,返回的值有一个手动的渲染函数、TSX 和 IDE 工具支持的构造器的合成类型。这种不匹配是一种有意义的权衡。<a name="FqxPQ"></a>## 挂载 App 实例app 实例可以用 mount 方法挂载一个根组件。它的工作原理类似 2.x 版本 vm.$mount() 方法,并返回挂载的根组件实例:```javascriptconst rootInstance = app.mount(App, '#app')
mount 方法也可以接受 props,通过第三个参数传递给根组件。
app.mount(App, '#app', {// props to be passed to root component})
挂载行为与 2.x 的区别
当使用包含编译器的构建方式,并且挂载一个没有自己模版的根组件是,Vue 会尝试使用挂载目标元素的内容作为模版。注意 3.x 与 2.x 行为的差别:
- 在 2.x 中,根实例使用目标元素的
outerHTML作为模版,并替换为目标元素本身。 - 在 3.x 中,根实例使用目标元素的
innerHTML作为模版,只替换目标元素的子元素。
在大多数情况下,这对你的 app 的行为方式应该没有影响,唯一的副作用是,如果目标节点包含多个子元素,这个根实例将会被挂载为一个片段(fragment),其 this.$el 将指向 fragment 的起始锚点(一个 DOM 注释节点)。
在 Vue3 中,由于 Fragment 的存在,我们建议使用模版 refs 来直接访问 DOM 节点,而不是依赖 this.$el。
Provide / Inject
一个 app 实例也可以提供被组 app 内任何组件注入的依赖关系:
// in the entryapp.provide({[ThemeSymbol]: theme})// in a child componentexport default {inject: {theme: {from: ThemeSymbol}},template: `<div :style="{ color: theme.textColor }" />`}
这类似于在 2.x 根实例中使用 provide 选项。
移除 config.productionTip
在 3.0 中,”use production build” 提示只有使用 “dev + full build”(包括运行时编译器有警告的构建)时才会显示出来。
对于 ES 模块构建,由于它与 bundlers 一起使用,在大多数情况下,CLI 或者 boilerplate 会正确的配置生产环境,这个提示将不再出现。
config.ignoredElements -> config.isCustomElement
引入这个配置选项的目的是为了支持本地的自定义元素,所以重命名可以更好的传达它的作用。新的选项还期望有一个函数,它比旧的字符串 / 正则表达式的版本提供了更多的灵活性:
// beforeVue.config.ignoredElements = ['my-el', /^ion-/]// afterconst app = Vue.createApp({ /* ... */ })app.config.isCustomElement = tag => tag.startsWith('ion-')
重要提示:在 3.0 中,检查元素是否为组件的工作已经转移到了模版编译阶段,因此这个配置选项只有在使用运行时编译器时才有用。如果你仅使用运行时编译, isCustomElement 必须在编译设置中传递给 @vue/compailer-dom,例如,通过 vue-loader 的 [compilerOptions](https://vue-loader.vuejs.org/options.html#compileroptions) 选项。
- 如果 config.isCustomElement 在使用仅运行时构建被分配到,将发出一个警告,指示用户在构建设置中传递该选项来代替。
- 这将是 Vue CLI 配置中的一个新的顶级选项。
config.optionMergeStrategies 行为改变
这个 API 虽然任然支持,但由于 Vue3 的内部实现变化,内置选项不再需要合并策略,所以它们不再被暴露。默认的 app.config.optionMergeStrategies 现在是一个空对象。这意味着:
- 用户现在必须始终提供自己的合并策略函数,而不是重复使用内置策略(例如,你不能再做
config.optionMergeStrategies.custom = config.opetionMergeStrategies.props)。 - 不再可能覆盖内置选项的合并策略。
附加在全局共享实例的属性
在 2.x 中,可以通过简单的将全局共享的实例属性附加到 Vue.prototype 来注入。
在 Vue3 中,由于全局 Vue 不再是一个构造函数,那么 Vue2 中的方式不再被支持。相反,共享实例的属性应该附加到 app 实例的 config.globalProperties 中:
// BeforeVue.prototype.$http = () => {}// Afterconst app = createApp()app.config.globalProperties.$http = () => {}
缺点
插件自动安装
很多 Vue2.x 的库和插件在其 UMD 构建中提供自动安装,例如,vue-router:
<script src="https://unpkg.com/vue"></script><script src="https://unpkg.com/vue-router"></script>
自动安装依赖于调用 Vue.use,而 Vue.use 不再可用。这应该是一个相对容易分迁移,我们可以为 Vue.use 提供一个存根,代替它发出一个警告。
备选方案
N/A
采纳策略
- 迁移是直接了当了(如在基本范例中看到的)。
- 被移动的 methods 可以被替换为发出广告的存根,以指导迁移。
- 也可以提供一个 codemod。
- config.ingoreElements 可以在 compat 构建中被支持。
- config.optionMergeStrategies 的内置策略可以在 compat 构建中得到支持。
没有解决的问题
N/A
