Vue3.0(官方正式版还未上线,持续更新中!!!)

Vue3.0

 

Vue 3.0 已经在原型设计阶段了,而且我们已经实现了一个与 2.0 的特性近乎相等的运行时了。下文中列出的许多条目,要么已经实现了,要么已经确认可实现。那些还未实现或者仍在探索阶段的条目会用一个“*”标记

性能提升

一句话简介:更小巧,更快速;支持摇树优化;支持 Fragments 和跨组件渲染;支持自定义渲染器。

  • 更小巧:这份新的代码库在设计之初就考虑到了对“摇树优化 (tree-shaking)”的友好。那些如内置组件 (、) 、运行时工具性指令(v-model)等特性将变为按需导入,所以也是“可摇树的”。对于这个新的运行时,它的大小将永远保持在 10kb 之下。另外,使这些特性变为“可摇树的”后,我们就可以提供更多的内置特性,同时还不会增加网络负载——如果没使用到这些特性的话。

摇树优化,是一种在打包时去除没用到的代码的优化手段,谷歌有一篇教程可以了解下: Reduce JavaScript Payloads with Tree Shaking

  • 更快:在前期的基准测试中,我们看到整体性能有了一倍的提升,包括虚拟 DOM 的挂载和打补丁(patching,指更新——译注) 的速度(我们从 Inferno 那里学了好些个技巧过来——Inferno 是目前速度最快的虚拟 DOM 实现),以及组件实例化速度和数据监测的性能。在 3.0 中,你应用的启动时间将缩减一半。
  • 支持 Fragments 和 Portal:虽然体积更小了,但 3.0 还将内置对 Fragments (即允许组件拥有多个根节点) 和 Portal (即允许在 DOM 的其他位置进行渲染,而不是组件内部) 的支持。

关于 Portal ,你可以将其理解为跨组件渲染或者异地渲染,vue-portal 是一个第三方实现,可以了解一下;Fragments 特性也有一个第三方库,但译者认为这个库的内部实现不够完善,叫做 vue-fragments,感兴趣可以了解一下。

  • 增强的 slot 机制:所有由编译器生成的 slot 都将是函数形式,并且在子组件的 render 函数被调用过程中才被调用 (译注:现在只有 scoped slot 才是函数形式,其渲染的时机也是在父组件的渲染进行时)。这使得 slot 中的依赖项 (即数据——译注) 将被作为子组件的依赖项,而不是现在的父组件;从而意味着:1)当 slot 的内容发生变动时,只有子组件会被重新渲染;2)当父组件重新渲染时,如果子组件的内容未发生变动,子组件就没必要重新渲染。这种机制的改变,可以提供更精确的变动探测,也就可以消除没必要的重渲染。
  • 支持自定义渲染器 (Renderer):这个 API 可以用来创建自定义的渲染器,它将作为“一等公民”出现,到时不再需要 fork 一份 Vue 的代码来通过修改实现自定义。这个 API 的到来,将使得那些如 Weex 和 NativeScript 的“渲染为原生应用”的项目保持与 Vue 的同步更新变得更加容易。除此之外,还将使得那些为了各种用途而创建自定义渲染器变得极其容易。

编译器相关的提升

  • 如果采用的是支持“摇树优化”的打包器,模板中使用到的那些可选特性,在生成的代码中将通过 ES 的模块语法导入;而在打包后的文件中,那些没用到的可选特性就会被“摇掉”。
  • 由于新的虚拟 DOM 实现所带来的提升,我们可以执行一些更加高效的编译耗时优化,如静态树提升(static tree hoisting)、静态属性提升(static props hoisting);以及为运行时提供一些来自编译器的提示,以此避开子组件的规范过程 (children normalization);提供 VNode 快速创建路径; 等等。
  • 我们计划对解析器进行重写,以便在对模板进行编译发生错误时,可以提供错误发生的位置信息;除此之外还可以带来对模板的 source map 支持;还可以支持第三方工具如 eslint-plugin-vue 和 IDE 的语言服务 (language services) 特性。

API 变动

一句话介绍:除渲染函数 API 和 scoped-slot 语法之外,其余均保持不变或者将通过另外构建一个兼容包来兼容 2.x。

  • 模板语法的 99% 将保持不变。除了 scoped slot 语法可能会有一些微调之外,我们还没有任何其他针对模板的变动计划。
  • 3.0 版本将原生地支持基于 class 的组件,而且无需借助任何编译及各种 stage 阶段的特性,以此提供良好的编写体验。许多现有的 (组件) 配置项将有对应的合理的 class 版本的 API。各种 stage 阶段的特性,如 class 的静态字段和装饰器 (decorator) 等仍然可以选择性地使用,以此增强编写体验。另外,整体的 API 在设计时也将考虑 TypeScript 的类型推断特性。3.x 的代码库本身将用 TypeScript 来编写,并提供增强的 TypeScript 支持。(就是说,TypeScript 的使用与否仍然是整体可选的)
  • 2.x 系列的基于对象的组件格式仍将受支持,不过会在内部将其转换为一个相应的 class。
  • 仍然支持 Mixins。*
  • 为了避免在安装插件时造成对 Vue 的运行时的修改,顶层 API 可能会做一个大的翻修。到时,插件的作用域将只局限到具体的一个组件树,使得对那些依赖于某些具体插件的组件的测试变得容易,也会使得在同一个页面中挂载多个使用不同插件——但使用同一个 Vue 运行时——的 Vue 应用变为可能。*
  • 函数式组件将支持纯函数的书写形式——不过,这样的话异步组件就需要通过一个辅助性函数来显式地创建了。
  • 变动最大的部分将是渲染函数 (render) 中的虚拟 DOM 的格式。我们现在正在收集主流的第三方库的作者们的反馈,在对这些变动有了更多的信心之后,我们还会将更多的细节曝光;虽然变动较大,但是只要你没在你的应用中重度使用手写的渲染函数 (不是指 JSX),那么变动之后的升级应该会比较容易。

代码重构

一句话介绍:更优良的内部模块解耦;TypeScript;更易于贡献的代码库。

在从零开始编写 3.0 之初,“达到更加清晰和更易维护的架构,特别是为了让代码的贡献变得容易”就是我们的目标。为了对复杂性进行隔离,我们将一些内部功能拆分为了多个单独的包。例如,observer 模块将成为一个单独的包,拥有自己对外的 API 和自己的测试用例;不过请注意,这不会对框架级的 API 造成影响——你不需要另外手动从多个包里导入许多小件小件的模块就可以使用 Vue,相反 Vue 的最终包会事先装配好这些内部包。

另外,代码库现在改为了用 TypeScript 编写;虽然这会使得“熟练 TypeScript”成为对新代码库进行贡献的一个前置要求,不过我们相信有类型信息配合 IDE 的支持,对于一个新的贡献者来说,要做出有意义的贡献,实际上反而会更加容易。

将 observer 和 scheduler 解耦为分开的两个包后,我们还可以拿一些替代的实现对这两个包进行置换试验。例如,我们可以实现一个兼容 IE11、API 也相同的 observer;或者实现另外一种利用 requestIdleCallback 来在长耗时的更新中产出工作成果到浏览器的 scheduler。

重写虚拟 DOM (Virtual DOM Rewrite)

随着虚拟 DOM 重写,我们可以期待更多的 编译时(compile-time)提示来减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。

优化插槽生成(Optimized Slots Generation)

在当前的 Vue 版本中,当父组件重新渲染时,其子组件也必须重新渲染(11 月 20 日更新:这句话是不严谨的,非常容易产生误导,我觉得有必要说明一下: 2.0 组件的重新渲染就是组件粒度的,除非修改的数据是子组件的 props,才会触发子组件的重新渲染。引用自:(https://juejin.im/pin/5bf28ddd6fb9a056783705fc)。 使用 Vue 3 ,可以单独重新渲染父组件和子组件。

静态树提升(Static Tree Hoisting)

使用静态树提升,这意味着 Vue 3 的编译器将能够检测到什么是静态组件,然后将其提升,从而降低了渲染成本。它将能够跳过未整个树结构打补丁的过程。

静态属性提升(Static Props Hoisting)

此外,我们可以期待静态属性提升,其中 Vue 3 将跳过不会改变节点的打补丁过程。

基于 Proxy 的观察者机制

目前,Vue 的反应系统是使用 Object.defineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。

为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建。

兼容 IE 11

一句话介绍:IE 11 将受到支持,但将会是另外构建一个版本 (build) 的形式支持,不过这个版本会存在与 Vue 2.x 响应式机制所存在的同样的局限。

新的代码库目前只针对主流浏览器,而且我们假定他们都支持 ES2015。但是,哎,我们也知道在可预见的未来还有很多用户仍然需要支持 IE11。除了 Proxy 外,大多数 ES2015 的特性都可以用转译或者垫片的方式在 IE11 中使用。我们的计划是另外单独实现一个具有同样 API 的替代性 observer,不过是基于老式的 Object.defineProperty API;然后再通过单独构建一个使用这个实现的 Vue 3.x 版本 (build) 进行发布,不过这个单独的版本还是会有 Vue 2.x 在变动探测方面所存在的问题,所以它其实并不是一个完全兼容 3.x 的一个版本。我们也意识到这会给第三方库的作者们带来某些不便,因为他们需要考虑两个不同版本之间的兼容性问题,不过当我们真的推进到那个阶段时,那时我们肯定会确保提供一份清晰的指导。

监测机制

一句话介绍:更加全面、精准、高效;更具可调试性的响应跟踪;以及可用来创建响应式对象的 API。

3.0 将带来一个基于 Proxy 的 observer 实现,它可以提供覆盖语言 (JavaScript——译注) 全范围的响应式能力,消除了当前 Vue 2 系列中基于 Object.defineProperty 所存在的一些局限,如:

  • 对属性的添加、删除动作的监测
  • 对数组基于下标的修改、对于 .length 修改的监测
  • 对 Map、Set、WeakMap 和 WeakSet 的支持

另外这个新的 observer 还有以下特性:

  • 公开的用于创建 observable (即响应式对象——译注) 的 API。这为小型到中型的应用提供了一种轻量级的、极其简单的跨组件状态管理解决方案。(译注:在这之前我们可以通过另外 new Vue({data : {…}}) 来创建这里所谓的 observable;另外,其实 vuex 内部也是用这种方式来实现的)
  • 默认为惰性监测(Lazy Observation)。在 2.x 版本中,任何响应式数据,不管它的大小如何,都会在启动的时候被监测。如果你的数据量很大的话,在应用启动的时候,这就可能造成可观的性能消耗。而在 3.x 版本中,只有应用的初始可见部分所用到的数据会被监测,更不用说这种监测方案本身其实也是更加快的。
  • 更精准的变动通知。举个例子:在 2.x 系列中,通过 Vue.set 强制添加一个新的属性,将导致所有依赖于这个对象的 watch 函数都会被执行一次;而在 3.x 中,只有依赖于这个具体属性的 watch 函数会被通知到。
  • 不可变监测对象(Immutable observable):我们可以创建一个对象的“不可变”版本,以此来阻止对他的修改——包括他的嵌套属性,除非系统内部临时解除了这个限制。这种机制可以用来冻结传递到组件属性上的对象和处在 mutation 范围外的 Vuex 状态树。
  • 更良好的可调试能力:通过使用新增的 renderTracked 和 renderTriggered 钩子,我们可以精确地追踪到一个组件发生重渲染的触发时机和完成时机,及其原因。

 

 

如果你想在v2.x中使用3.0的内容,可通过以下方式

npm install '@vue/composition-api'

在main.js中引入

import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);

数据的双向绑定

V2.x中的数据双向绑定是通过object的defineProperty方法实现的,通过属性中的get和set方法中进行拦截操作。但是它无法监听数组的改变,除了使用以下几种方法可以:push()、pop()、shift()、unshift()、splice()、sort()、reverse(),如果直接使用index设置数组的某一项时是无法改变的。

V3.x中改用了proxy和Reflect实现双向绑定,它可以从更多方面对对象的变化进行监听,除了set 和 get 外,还提供了apply、setPrototypeOf、getPrototypeOf、deletePrototype、isExtensible、preventExtensions、getOwnPropertyDescriptor 、defineProperty、has、ownKeys、construct方法的监听。

对于proxy的实际应用我将在后期专门来讲解,本期以VU3.0的实践为主

直接上一斤的例子

<template>
  <div>
    <h3>数据绑定</h3>
    <div>
      <el-button @click="clicksingleVal">
        单向绑定 {{singleVal}}
      </el-button>
      <el-button @click="clickdoubleVal">
        双向绑定 {{doubleVal}}
      </el-button>
      <el-button @click="clickstateVal">
        状态绑定 {{stateVal}}
      </el-button>
    </div>
  </div>
</template>
​
​
<script>
import {ref, reactive, toRefs} from '@vue/composition-api'
export default {
  setup(){
    let singleVal = 1
    const doubleVal = ref(1)
    const state = reactive({
      stateVal: 1
    })
    const methods = {
      clicksingleVal(){
        singleVal++
        console.log(singleVal);
      },
      clickdoubleVal(){
        doubleVal.value++
      },
      clickstateVal(){
        state.stateVal++
      },
    }
    return{
      singleVal,
      doubleVal,
      ...toRefs(state),
      ...methods
    }
  }
}
</script>
<style>
</style>

如果你是第一次看到上面的写法可能会有点陌生,这就是在vue3.x中的写法。在v2.x中的data、methods、computed、watch等内容,在v3.x中全部都写在一个叫 setup 的函数中。在里面我们可以任意的定义变量、函数等,最后通过return返回,返回的内容可以在模板中进行使用。
对于绑定的数据在v3.x中进行了更加详细的分类,可以分为以下三种:

  • 单向绑定:var singleVal = 1,数据会变化,但是视图不会更新
  • 单个双向绑定:const doubleVal = ref(1),使用 doubleVal.value 改变
  • 双向绑定:需要使用 reactive ,使用 state.stateVal 改变值

我们来逐个的进行讲解:

  • 单向绑定,听名字就可以知道它并不具有双向绑定的特性或功能,它只会在视图初始化时绑定一次,这个变量即使后面发生了改变,视图也不会更新。可以从控制台看到singleVal 的值的确发生了变化,但界面中始终显示的为1。变量声明的方法和我们平时声明一个变量一样,如:let singleVal = 1,最后在return中返回。
  • 单个双向绑定,每次只能声明一个双向绑定的变量,通过ref函数创建一个包装对象,使它包含一个响应式的属性value。例如上面的const doubleVal = ref(1)。如果要改变它的值,需要改变的是它的属性value上的值,像这样一样doubleVal.value++。
  • 双向绑定,通过reactive创建一个响应式的对象,这样创建的对象并不是一个包装对象,因此不需要使用.value来取值,它等价于 Vue 2.x 的Vue.observable。

const state = reactive({
  stateVal: 1
})
return {
 ...state
}

对reactive的内容直接进行解构后返回,会导致响应式丢失,需要使用toRefs将reactive对象转为普通对象,这样结果对象上的每个属性都指向原始对象中对应属性的ref引用对象,保证了在使用对象解构或拓展运算符时响应式不会丢失。
对于事件方法,就和声明一个变量一样,在setup中声明,在return返回即可。

计算属性

引入computed方法,返回计算后的值,这里接着使用上面的例子,用total计算上面3个数的总和。

import {computed, ref, reactive, toRefs} from '@vue/composition-api' 
export default {
  setup(){
    ...
    const total = computed(() =>{
      return singleVal + doubleVal.value + state.stateVal
    })
    ...
    return{
      ...,
      total
    }
  }
}

从演示效果中我们还可以看出一点,单向绑定的数据改变不会触发计算属性方法。

数据监听

同样还是写在setup中,也比较简单,没有什么可讲解的

import {computed, ref, reactive, toRefs, watch} from '@vue/composition-api'
...
watch(() => state.stateVal, (newVal, oldVal ) =>{
  console.log(newVal, oldVal);
})
...

生命周期

vue3.0中取消了 beforeCreate 和 created 两个周期,因为setup会在这个这个周期前执行,因此你可以在setup中进行你需要的处理。其他的生命周期全部以on开头。

import {
  onBeforeMount,
  onMounted,
  onBeforeUnmount,
  onBeforeUpdate,
  onDeactivated,
  onUnmounted,
  onUpdated
} from '@vue/composition-api'
export default {
  setup(){
    onBeforeMount(() =>{
      console.log('onBeforeMount');
    })
    onMounted(() =>{
      console.log('onMounted');
    })
    onBeforeUnmount(() =>{
      console.log('onBeforeUnmount');
    })
    onBeforeUpdate(() =>{
      console.log('onBeforeUpdate');
    })
    onDeactivated(() =>{
      console.log('onDeactivated');
    })
    onUnmounted(() =>{
      console.log('onUnmounted');
    })
    onUpdated(() =>{
      console.log('onUpdated');
    })
  }
}

Mixin

vue3.0中使用函数式的API代替原有的mixin,mixin很容易引起命名重合和覆盖引入mixin的页面属性。

在vue3.0中以一个API的形式存在,当你需要时,将其引入。直接看例子

mixin.vue

<template>
  <div>
    <h3>mixins</h3>
    <div>
      <el-input v-model="searchValue" type="text" @change="onSearch"/>
      <div>
        <ul>
          <li v-for="name in names" :key="name.id" v-show="name.show">
            {{name.value}}
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>
​
<script>
import searchName from './searchName.js'
export default {
  setup(){
    let list = [
      {id: 1, value: 'vue', show: true},
      {id: 2, value: 'react', show: true},
      {id: 3, value: 'angular', show: true},
      {id: 4, value: 'elementui', show: true},
      {id: 5, value: 'ant', show: true},
      {id: 6, value: 'javascript', show: true},
      {id: 7, value: 'css', show: true},
    ]
    var {onSearch, names, searchValue} = searchName(list)
    return {
      onSearch,
      names,
      searchValue
    }
  }
}
</script>
<style>
</style>

searchName.js

import {reactive, toRefs} from '@vue/composition-api'
​
export default function searchName(names){
  const state = reactive({
    names: names,
    searchValue: ''
  })
  const onSearch = () => {
    state.names.forEach(name => {
      name.show = name.value.includes(state.searchValue)
    })
  }
  return {
    ...toRefs(state),
    onSearch
  }
}

上面我们将搜索功能独立到一个js文件中。在 searchName.js 中定义了一些属性和方法,这里的属性也是具有响应式的,最后返回这些内容。在组件中,先引入这个js文件,调用searchName方法,传入需要的参数。在该组件中的searchValue和names两个响应式数据并非自身的所有,而是来自searchName.js中,通过下面演示可以看到,他们的确也具有响应式的特性。

EventBus

  • setup接收两个参数
  • props:等同于V2.x中的 props:{},用于接收父组件传递的参数

ctx:上下文环境。在2.x中的this指向的是全局VUE实例,可以使用this.router、this.router、this.router、this.commit、this.emit等方法进行路由的跳转等操作。而在3.0中不能直接访问到this,如果你尝试输出this,你回看到它的值是undefined。但可以通过ctx.root.emit等方法进行路由的跳转等操作。而在3.0中不能直接访问到this,如果你尝试输出this,你回看到它的值是undefined。但可以通过ctx.root.emit等方法进行路由的跳转等操作。而在3.0中不能直接访问到this,如果你尝试输出this,你回看到它的值是undefined。但可以通过ctx.root.root.$emit将内容挂在到 $root 上,以使得任何地方都可以访问到。

setup(props, ctx){
 ...
 const total = computed(() =>{
   let total = singleVal + doubleVal.value + state.stateVal
   ctx.root.$root.$emit('handleClick', {number: total })
   return total 
 })
 ...
}
...
ctx.root.$root.$on('handleClick', (val) =>{
  console.log('我是通过ctx.root.$root.$on触发的,接收的值为:' + val.number);
  state.otherTotal = val.number
})
...
posted @ 2020-06-10 22:39  凌醉枫  阅读(5549)  评论(2编辑  收藏  举报