vue3新特性及对比 2.x 的重要变动

今年7月,尤大大宣布 Vue 3 进入 RC 阶段,这意味着 Vue 3 内核 API 与实现已趋稳定。vue

Vue做为一种渐进式框架, 借鉴了 React 的组件化和虚拟 DOM ,借鉴了 Angular 的模块化和双向数据绑定。就框架的 API 而言,对比之下,它更加轻便简洁。对于Vue的好处这里再也不赘述。node

相对vue2.x,Vue3作了不少重要的变动,特别是Composition API的引入。咱们知道,Vue 现有 API 是经过「选项」组织代码的,随着功能的增加,复杂组件的代码会变得愈来愈难维护,不少逻辑都没法复用。开发的时候复杂的组件千行代码template/data/methods来回翻,看到你头晕,而vue3可让你在接手别人的代码时更容易理清逻辑关系。下面直接进入主题,咱们来看看Vue3都有哪些新特性,相对于以前版本又有哪些变动。react

1、3.0 对比 2.x 的重要变动

Vue 3.0 相对与以前的版本,有 6 个方面的重要变动:数组

Performance(性能)

性能上,主要是优化了虚拟 DOM,因此也就有了更加优化的编译,同时实现了更加高效的组件初始化。app

  1. Rewritten virtual dom implementation (重写了虚拟 DOM)
  2. Compiler-informed fast paths (优化编译)
  3. More efficient component initialization (更高效的组件初始化)
  4. 1.3-2x better update performance (1.3~2 倍的更新性能)
  5. 2-3x faster SSR (2~3 倍的 SSR 速度)

Tree-shaking support (支持 Tree-shaking)

在大部分状况下,咱们并不须要 vue 中的全部功能,可是在以前的 vue 版本中,咱们没有一个合适的办法用来除去不须要的功能,而 Vue3 中,为了知足体积更小的需求,支持 Tree-shaking,也就意味着咱们能够按需求引用的内置的指令和方法。框架

  1. Most optional features (e.g. v-model, <transition>) are now tree-shakable (大多数可选功能(如 v-model、<transition>)如今都是支持 Tree-shaking 的。)
  2. Bare-bone HelloWorld size: 13.5kb. 11.75kb with only Composition API support.
  3. All runtime features included: 22.5kb. More features but still lighter than Vue 2.

Composition API

Composition API 主要是提升了代码逻辑的可复用性,而且将 Reactivity 模块独立出来,这也使得 vue 3 变得更加灵活地与其余框架组合使用。dom

  1. Usable alongside existing Options API (可与现有选项 API 一块儿使用)
  2. Flexible logic composition and reuse (灵活的逻辑组成和重用)
  3. Reactivity module can be used as a standalone library (Reactivity 模块能够做为独立的库使用)

Fragment, Teleport, Suspense

  1. Fragment

在书写vue2时,因为组件必须只有一个根节点,不少时候会添加一些没有意义的节点用于包裹。Fragment组件就是用于解决这个问题的(这和React中的Fragment组件是同样的)。异步

  • No longer limited to a single root node in templates (<template> 中再也不局限于单一的根节点)

-Manual render functions can simply return Arrays (render 函数能够返回数组)async

  • “Just works”
  1. Teleport

Teleport其实就是React中的Portal。Portal 提供了一种将子节点渲染到存在于父组件之外的 DOM 节点的优秀的方案。一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你须要子组件可以在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。ide

  • Previously known as <Portal>(原名为 <Portal>)
  • More details to be shared by @Linusborg
  1. Suspense

一样的,这和React中的Supense是同样的。Suspense 让你的组件在渲染以前进行“等待”,并在等待时显示 fallback 的内容。

  • Wait on nested async dependencies in a nested tree
  • Works with async setup() (与 async 函数 setup() 配合使用)
  • Works with Async Components (与 Async 组件配合使用)

Better TypeScript support (更好的 TypeScript 支持度)

vue3.0 对 TS 的支持度更高了,同时也支持 TSX 的使用;API 在 JS 与 TS 中的使用相同;类组件仍然可用,可是须要咱们引入 vue-class-component@next,该模块目前还处于 alpha 测试阶段。

  1. Codebase written in TS w/ auto-generated type definitions
  2. API is the same in JS and TS
  3. In fact, code will also be largely the same
  4. TSX support
  5. Class component is still supported (vue-class-component@next is currently in alpha)

Custom Renderer API (自定义的 Renderer API)

自定义 render 会提供一个 API 用来建立自定义的 render,所以再也不须要为了自定义一些功能而 fork Vue 的代码。这个特性给 Weex 和 NativeScript Vue 这样的项目提供了不少便利。

  1. NativeScript Vue integration underway by @rigor789
  2. Users already experimenting w/ WebGL custom renderer that can be used alongside a normal Vue application (Vugel)

2、Composition API 等核心特性

在vue3中引入了Composition API(组合API),使用纯函数分隔复用代码,让逻辑变得清晰。

关于VCA,有人说跟react的hooks很像,咱们来看看做者尤大的介绍:
image

Lifecycle Hooks

新版的生命周期函数,能够按需导入到组件中,且只能在 setup() 函数中使用.

  1. beforeCreate -> use setup()
  2. created -> use setup()
  3. beforeMount -> onBeforeMount
  4. mounted -> onMounted
  5. beforeUpdate -> onBeforeUpdate
  6. updated -> onUpdated
  7. beforeDestroy -> onBeforeUnmount
  8. destroyed -> onUnmounted
  9. errorCaptured -> onErrorCaptured
import {onBeforeMount, onMounted} from 'vue'

 export default {

     setup() {

         onBeforeMount(() => {

             console.log('onBeforeMount')

         })

        onMounted(() => {

            console.log('onMounted')

        })

     },

 }

setup

setup() 函数是 vue3 中,专门为组件提供的新属性。它为咱们使用 vue3 的 Composition API 新特性提供了统一的入口。

  1. setup 函数至关于 vue2.x 中 beforeCreate 和 created, 在 beforeCreate 以后、created 以前执行
  2. setup第一个形参,接收 props 数据
  3. setup第二个形参是一个上下文对象, 在 setup() 函数中没法访问到 this, 能够用这个context来访问
export default {

     props: {

        str: String

     },

     setup(props, context) {

         console.log(props) // str

         console.log(context)  // attrs, slots, parent, root, emit, refs

     },

 }

reactive

reactive() 函数接收一个普通对象,返回一个响应式的数据对象。

import { reactive } from 'vue'

 export default {

     setup() {

         const state = reactive({count: 0}) // 建立响应式数据对象

         return state //将响应式数据对象 return 出去,供 template 使用

     }
 }

ref

ref() 函数根据给定的值建立一个响应式的数据对象,返回值是一个对象,这个对象上只包含一个 .value 属性

import { ref } from 'vue'

 export default {

     setup() {

         const count = ref(0) // 建立响应式数据对象 count,初始值为 0

         console.log(count.value) // 在setup内访问count值须要.value 属性才能够,但在template中能够直接访问

         return {

            count

         }

     },

 }

toRefs

toRefs() 函数能够将 reactive() 建立出来的响应式对象,转换为普通的对象,只不过,这个对象上的每一个属性节点,都是 ref() 类型的响应式数据

import { reactive, toRefs } from 'vue'

 export default {

     setup() {

         const state = reactive({count: 0, name:'weedsFly'}) // 用reactive集中建立多个响应式对象

         const add = () => { // methods写在setup内

             state.count++

         }

         return {

             // ...state,  // 使用展开运算符后 用reactive建立的响应式数据 变成了 固定的值

             ...toRefs(state), // 能够用toRefs函数 将传进来的非响应式对象 转成 ref() 类型的响应式数据

             add

        }

    },

 }

ref引用

经过 ref() 还能够引用页面上的元素或组件,这点和vue2的ref概念相似。

1.元素引用

import { ref, onMounted } from 'vue'

 export default {

     setup() {

         const h1Ref = ref(null) // 建立一个 DOM 引用

         onMounted(() => { // 在 DOM 首次加载完毕以后,才能获取到元素的引用

             h1Ref.value.style.color = 'pink' // h1Ref.value 是原生DOM对象

         })

         return {

            h1Ref

         }

     }

 }

2.组件引用

//父组件:

 import { ref } from 'vue'

 export default {

     setup() {

         const compRef = ref(null) // 建立一个组件的 ref 引用

         showCompData = () => { // 展现子组件中 count 的值

            console.log(compRef.value.count) 

         }

         return {

             compRef,

             showCompData

         }

     }

 }
//子组件:

 import { ref } from 'vue'

 export default {

     setup() {

         const count = ref(0) // 定义响应式的数据

         return {

            count

         }

     }

 }

computed

omputed() 用来建立计算属性,computed() 函数的返回值是一个 ref 的实例。

1.computed建立只读的计算属性(传入一个 function 函数,能够获得一个只读的计算属性)

import { ref, computed } from 'vue'

 export default {

     setup() {

         const count = ref(0)

         const computedCount = computed(() => count.value + 1)

         computedCount.value = 9 //  computed value is readonly.

         return {

             count,

             computedCount

         }

     },

 }

2.computed建立可读可写的计算属性

import { ref, computed } from 'vue'

 export default {

     setup() {

         const count = ref(0)

         const computedCount = computed({

             get: () => count.value + 1,

             set: val => {

                count.value = val - 1

             }

         })

         computedCount.value = 100 // 为计算属性赋值的操做,会触发 set 函数 

         console.log(count.value) // 触发 set 函数后,count 的值会被更新

         return {

             count,

             computedCount,

             computedCount

         }

     },

 }

watch

1.监视单个数据源变更

  • 监视单个reactive建立的数据源
import { reactive, watch } from 'vue'

 export default {

     setup() {

         const state = reactive({count: 100}) 

         watch(

             () => state.count,

             (newVal, oldVal) => { console.log(newVal, oldVal)},

             {lazy: true} // 在 watch 被建立的时候,不执行回调函数中的代码

         )

         setTimeout(() => {

            state.count++

         }, 1500)

     }

 }
  • 监视单个ref建立的数据源
import { ref, watch } from 'vue'

 export default {

     setup() {

         const count = ref(100) 

         watch(

             count,

             (newVal, oldVal) => { console.log(newVal, oldVal)},

             {lazy: true} // 在 watch 被建立的时候,不执行回调函数中的代码

         )

         setTimeout(() => {

            count++

         }, 1500)

     }

 }

2.监视多个数据源

  • 监视多个reactive建立的数据源
import { reactive, watch } from 'vue'

 export default {

     setup() {

     const state = reactive({count: 100, name: 'Laiyj'})

     watch(

         [() => state.count, () => state.name],

         ([newCount, newName], [oldCount, oldName]) => { 

             console.log(newCount, oldCount)

             console.log(newName, oldName)

         },

         {lazy: true} // 在 watch 被建立的时候,不执行回调函数中的代码

     )

     setTimeout(() => {

         state.count++

         state.name = 'Lucy'

     }, 1000)

     }

 }
  • 监视多个ref建立的数据源
import { ref, watch } from 'vue'

 export default {

     setup() {

         const count = ref(100)

         const name = ref('Laiyj')

         watch(

             [count, name],

             ([newCount, newName], [oldCount, oldName]) => {

                 console.log(newCount, oldCount)

                 console.log(newName, oldName)

             },

             {lazy: true} // 在 watch 被建立的时候,不执行回调函数中的代码

         )

         setTimeout(() => {

             count++

             name = 'Lucy'

         }, 1000)

     }

 }

3.清除watch监视

import { ref, watch } from 'vue'

 export default {

     setup() {

         const count = ref(100)

         const stop = watch( // 建立监视,并获得 中止函数

             count,

             (newVal, oldVal) => {

                 console.log('I am watching.')

                 console.log(newVal, oldVal)

             },

             {lazy: true} // 在 watch 被建立的时候,不执行回调函数中的代码

         )

         setTimeout(() => {

            count++

         }, 1000)

         const clearWatch = () => {

            stop()

         }

         return {

             count,

             clearWatch

         }

     }

 }

4.在 watch 中清除无效的异步任务(与节流防抖同效)

import { ref, watch } from 'vue'

 export default {

     setup() {

         const keyword = ref('')

         const asyncPrint = (val) => { // 执行异步任务,并获得关闭异步任务的 timerId

             return setTimeout(() => {

                console.log(val)

             }, 1000)

         }

         watch(

             keyword,

             (newVal, oldVal, onClean) => {

                 const timeId = asyncPrint()

                 onClean(() => {clearTimeout(timeId)}) // 若是 watch 监听被重复执行了,则会先清除上次未完成的异步任务

             }

         )

         return {

             keyword

         }

     }

 }

provide & inject

  1. provide() 和 inject() 能够实现嵌套组件之间的数据传递。
  2. 这两个函数只能在 setup() 函数中使用。
  3. 父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。
//父组件:

 import { provide, watch } from 'vue'

 export default {

     setup() {

         const color = ref('red')

         provide('themeColor', color) // 父组件经过 provide 函数向子级组件共享数据(不限层级) provide('要共享的数据名称', 被共享的数据)

         const changeColor = (val) => {

            color.value = val

         }

         return {

             color,

             changeColor

         }

     }

 }
// 子级组件:

 import { inject, watch } from 'vue'

 export default {

     setup() {

         const color = inject('color') // 调用 inject 函数时,经过指定的数据名称,获取到父级共享的数据

         return {

            color

         }

     }

 }

以上只罗列了几个比较基础重要的API,vue3还有不少还须要多多学习 : )