vue3.x的新特性研究

本文仅作为vue3.x的研究,因为vue3.x现阶段还处于"release: v3.0.0-alpha.9"阶段,后续如有之处,欢迎指正

vue2.x面临的问题

vue2.x版本发布于数年前,基于es5的技术架构,受限于当时通用浏览器的版本问题,在某些功能方面做了一些拖鞋:

  1. 监听数据的方法Object.definePerproty(), 不能对Object类型做深度监听。而为了深度监听,以及为了达到目的所要付出的代价,也就是递归遍历绑定。
  2. Mixins, 模版中的数据来源不清晰,不知道来自哪里的mixin,并且容易发生明明冲突
  3. 性能问题,在创建 HOC以及 基于 scoped slots / 作用域插槽封装逻辑的组件 时,需要额外的组件
  4. 功能分块混乱,我们需要把逻辑分别散落在data,methods对象里,有时候,需要来回滚动。

vue3.x的主要优势

  1. 更快(引入Proxy对象)
    vue3重新审视了 vdom,更改了自身对于 vdom的对比算法。vdom从之前的每次更新,都进行一次完整遍历对比,改为了切分区块树,来进行动态内容更新。也就是只更新 vdom的绑定了动态数据的部分,把速度提高了6倍;

    并且vue3.x把 definePerproty改为了 proxy,对于 JavaScript引擎更加友好,响应更加高效。

  2. 更小 (按需引入)
    之前 vue的代码,只有一个 vue对象进来,所有的东西都在 vue上,这样的话其实所有你没用到的东西也没有办法扔掉,因为它们全都已经被添加到 vue这个全局对象上。

    vue3.x的话,一些不是每个应用都需要的功能,我们就做成了按需引入。用 ES module imports按需引入,举例来说,内置组件像 keep-alive、transition,指令的配合的运行时比如 v-model、v-for、帮助函数,各种工具函数。比如 async component、使用 mixins、或者是 memoize都可以做成按需引入。

vue3.x写法变动

  1. 3.0 版本将原生地支持基于 class 的组件。并提供增强的ts支持
  2. 改为函数式写法,函数式组件将支持纯函数的书写形式
    为了体验这种语法,vue3.x已经发布了Vue Function-based API RFC,vue3.0语法预览版 Function-based API 受 React Hooks 的启发,提供了一个全新的逻辑复用方案。
  3. 代码区块变更,从按类别划分,到按功能划分
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

Vue Function-based API RFC

作为 vue-next 项目。也就是 vue3.x 功能的预先体验版而存在

1. setup()

setup(prop,context) 函数,编写组件业务逻辑的主战场,各种 hook 类型的方法均需要在 setup 这个作用域下调用
接收一个props(不可修改)作为参数,在组件实例创建时调用,
可以返回一个值供模版渲染

	const MyComponent = {
	  props: {
	    name: String
	  },
	  setup(props) {
	    return {
	      msg: `hello ${props.name}!`
	    }
	  },
	  template: `<div>{{ msg }}</div>`
	}
  1. setup 无法使用 this
    本身的调用时机应该是介于 beforeCreatecreated 这两个生命周期之间的,也就是说,在这里无法使用 this 调用当前组件实例
  2. 一定要访问可以用 setup() 第二个参数 context
    setup(props, context) {
      do anything...
    }
    
  3. 内部产生的 proxy 对象:
    对于 state 的声明,我们使用 reactive 这个 api 来构造,对于 computed state 的声明,使用 computed,两者本质上均是 Proxy 对象
  4. 返回值可以是 render context,也可以是模板渲染函数( h(…) ):
    render context就是模板中可以访问到的各种数据和方法的上下文对象(受控制的可变数据对象)

2. state 的设定

主要用来代替 2.xdata 的功能

包装对象ref()
  • 针对 js 的基础类型使用,引用类型亦可
  • ref() 对象的本质是一个包有value参数的对象
  • 构建 ref() 对象的意义在于,js基础类型传值不是引用传值,所以需要构建一个带有value的对象,以达到追踪依赖。
  • Ref Unwrapping 包装对象的自动展开,渲染或嵌套在其他模板中时,ref() 会自动展开, 指向 value 这个原始值,所以不需要用 .value 取值

基础例子

import { ref } from "vue";

export default {
	setup() {
	  const count = ref(0);
	  
	  function inc() {
	    count.value++;
	  }
	  
	  return { count, inc };
	}
};

包装对象的自动展开例子

import { ref } from 'vue'
const MyComponent = {
  setup(props) {
    const msg = ref('hello')
    const appendName = () => {
      msg.value = `hello ${props.name}`
    }
    return {
      msg,
      appendName
    }
  },
  template: `<div @click="appendName">{{ msg }}</div>`}

响应式对象例子

onst count = ref(0)
const obj = reactive({
  count
})
console.log(obj.count) // 0

obj.count++
console.log(obj.count) // 1
console.log(count.value) // 1

count.value++
console.log(obj.count) // 2
console.log(count.value) // 2

配合手写render函数
响应式对象例子

import { ref, createElement as h } from 'vue'
const MyComponent = {
  setup(initialProps) {
    const count = ref(0)
    const increment = () => { count.value++ }

    return (props, slots, attrs, vnode) => (
      h('button', {
        onClick: increment
      }, count.value)
    )
  }} 
附加的 reactive()
  • 针对 js 的引用类型使用
import { reactive } from "vue";

export default {
  setup() {
    const state = reactive({
        count: 0
    });
    
    function inc() {
      state.count++;
    }
    
    return { state, inc };
    // 或者通过 toRefs 方法
    // return { ...toRefs(state), inc };
  }
};

两者实现的效果没有差异,都可以实现对数据的绑定,只存在风格上的差异

3. computed

  • computed(getFn, setFn), 功能上与 vue2.x 相同
import { ref, computed } from 'vue'
const count = ref(0)
const countPlusOne = computed(() => count.value + 1)

console.log(countPlusOne.value) // 1

count.value++
console.log(countPlusOne.value) // 2
  • 双向计算值可以通过传给 computed 第二个参数作为 setter 来创建
const count = value(0)
const writableComputed = computed(
  // read
	() => count.value + 1,
  // write  
	val => { count.value = val - 1}
)

4. watch

watch(getter, callback) (监听)
getter 可以是:

  • 一个返回任意值的函数
  • 一个包装对象(当然也包括props对象)
  • 一个包含上述两种数据源的数组,任一数据变化都会触发回调

监听数组例子

watch(
  // getter
  () => count.value + 1,
  // callback
  (value, oldValue) => {
    console.log('count + 1 is: ', value)
  })	// -> count + 1 is: 1
  count.value++// -> count + 1 is: 2
  • watch( ) 在创建的时候会执行一次,类似于 2.ximmediate: true
  • 如何把immediate关闭?
  • 停止监听:
    被动 —— 使用在setup( )以及组件生命周期时,在组件销毁的时候也一起被终止
    主动 —— watch( )返回一个停止观察的函数,调用即终止
  • callback 第三个参数 onCleanup
watch(idValue, (id, oldId, onCleanup) => {
  const token = performAsyncOperation(id)
  onCleanup(() => {
    // id 发生了变化,或是 watcher 即将被停止.    // 取消还未完成的异步操作。    
    token.cancel()
  })
})

5. prop

  • 功能就是传参,与 2.x 中类似,存在于setup()参数
  • prop 自动被 reactive() 包裹,所以可以使用watch监听

结合typescript 使用,相当好用

interface PropTypesI {
   count: number
}

export default {
  setup(props: PropTypesI) {
    watch(() => {
      console.log(\`count is: \` + props.count)
    })
  }
}

6. 生命周期

import { onMounted, onUpdated, onUnmounted } from "vue";

setup() {
  onMounted(() => { ... });
  onUpdated(() => { ... });
  onUnmounted(() => { ... });
}

这里做一个与2.X简单的类比

beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

与 react hooks 对比

vue-next 在这方面借鉴了 react hooks 的设计思想,但是从实现层来讲,它们是不一样的,主要有以下几点:

  • vue-next 不依赖于其调用顺序,而 react 依赖
  • vue-next 提供了生命周期方法,而 react 刻意模糊生命周期的概念
  • vue-next 基于响应式系统实现,意味它的依赖不需要显示声明(而且是自动的),而 react 需要手动声明依赖数组
Logo

前往低代码交流专区

更多推荐