12

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

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

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

一、3.0 对比 2.x 的重要变更

Vue 3.0 相对与之前的版本,有 6 个方面的重要变更:

Performance(性能)

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

  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 变得更加灵活地与其他框架组合使用。

  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 函数可以返回数组)

  • “Just works”
  1. Teleport

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

  • 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)

二、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还有很多还需要多多学习 : )


weedsfly
291 声望5 粉丝

前端路上的追赶者,Fighting : )