• Vue 关键概念介绍


    Vue现在已经迭代到 3+ 版本,阅读官方文档的过程中发现作者的一些理念和思路很合我口味,很多概念与方案都是基于解决实际问题提出并实现的,且在权衡利弊后勇于打破常规,比如如何看待关注点分离?。可见,Vue 之所以流行,不单单因为作者是国人,更应该是由于 Vue 作为新一代的解决方案提升了前端编程的体验与效率。

    本文介绍几个核心概念。

    选项式 vs 组合式#

    Vue 提供两种代码的书写风格——选项式组合式。可简单理解为:前者面向对象编程;后者函数式编程。

    选项式:如果你有微信小程序的开发经验,就知道选项式是什么样子,其实就是将组件的逻辑封装到一个对象中,这个对象预定义多个字段和方法(如 data、methods 和 mounted),开发人员需要在适当的地方组织代码。对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致,同时,响应性相关的细节由框架本身处理,对初学者而言更为友好。

    组合式:传统的自由无约束的编码风格,顶层就是各个成员变量和 functions,及一些钩子函数。似乎回到了 js 最初的模样,在对象、类、prototype 这些概念普及以前,大多数代码就是一坨变量加一坨 function,然后 onclick 调用。但是 Vue 的组合式风格依托其底层的依赖注入系统,及完善的响应式 API,使得情况不像看上去那么简单,而是呈现出一种螺旋向上的味道,耐人寻味。

    官方文档对这两种风格有一些比较,个人比较倾向于组合式,所以本文 Vue 代码都是组合式的。

    响应式基础#

    所谓响应式,就是视图会随着 JS 对象状态的改变而自动改变(也就是MVVM模式),有这种效果的对象就叫作响应式对象(其实就是 JavaScript Proxy)。在组合式 API 中,我们需要显式声明响应式对象,有两种方式——reactive()ref()

    reactive()#

    该 API 返回的对象,是传入对象的代理对象,其所有属性及深层的子属性,都是响应式的。响应式对象的内嵌对象也是响应式对象,就算给它赋值普通对象,如:

    Copy
    const proxy = reactive({}) const raw = {} proxy.nested = raw // proxy.nested 自动就是响应式对象 console.log(proxy.nested === raw) // false,代理对象和原始对象不是全等的

    reactive() 的注意事项和原理#

    reactive() 有一定的局限:它仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的基础类型无效;需要尽量避免对一个响应式变量重新赋值,除非我们有办法将新对象和视图重新建立连接;且当我们将响应式对象的属性(基础类型)赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性,如:

    Copy
    let state = reactive({ count: 0 }) // 官方文档这里的表述不是很准确,下面是我的表述: // 表面上看是重新赋值的 state 状态变化没有引起视图的变化,似乎响应连接丢失了, // 其实原对象上的响应式连接还在,但是原对象在此处已无法继续访问,所以响应式连接在不在不重要了, // 重建响应就需要建立视图和新对象的连接。 state = reactive({ count: 1 }) let n = state.count // 基础类型赋值,失去响应性连接 n++ // 不影响 state let { count } = state count++ // 同上 // state.count 值传递给基础类型形参,也失去响应性连接了 callSomeFunction(state.count)

    对于这些情况,有后端经验的同学如果将 reactive() 得到的响应式对象类比成引用类型对象就很好理解,这就是引用类型和值类型在使用过程中需要注意的一些点——变量赋值,如果是引用类型的话,那么指向的是对象的内存地址(新旧对象的内存地址自然是不一样的);如果是值类型,虽然代码看上去都是指向 state.count,其实是拷贝源值到自己的内存块,拷贝完了之后就和源没有关系了。

    对于引用类型的“问题”,只要注意点就好了,但是在响应式的场景下,值类型的“拷贝”特性确实让人有点闹心。有没有类似于后端的装箱操作呢?

    ref()#

    该 API 返回的也是响应式对象,它用于将值类型(基础类型)封装成引用类型(对象类型)。也就是说,我们将上一小节代码改造一下,就能保持基础类型数据在各个变量间传递后的响应性,如下:

    Copy
    const state = reactive({ count: ref(0) }) let n = state.count // 现在 state.count 是引用类型,所以它和 n 指向的是同一个对象 n.value++ // 需要用 value 操作值 // 注意 value 也是响应式的,也就是传递给它的普通对象会自动转为响应式对象,和 reactive() 那边的情况一样 // 同时要注意直接替换掉整个对象会导致出现响应连接丢失的问题(上面提到过) n.value = { name: 'Tony' }

    简言之,我们可以将 ref 对象就看作引用类型对象,就能很快理解它的特性了。唯一要注意的是访问和操作它的值需要 .value,但是在某些时候框架也会帮我们自动解包(不需要使用 .value),可以参看官方文档。

    组合式函数#

    是利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。说白了,就是业务逻辑封装,它表现形式不是对象,但是有状态,状态作为响应式对象对外暴露使用(如果有的话)。推测是为了和对象形式区分,才称之为组合式函数(就像选项式风格和组合式风格的区别)。

    Vue 2 的用户可能会对mixins选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里,然而 mixins 没有自我范围的约束,就像页面里使用