是时候抛弃Mixin啦

267 阅读6分钟

vue2中我们抽取代码的方式只有mixin一种方式,mixin确实也成功起到了代码抽离的作用,但是这种方式也带来了许多问题。尤其在大型项目中几十个甚至更多个mixin一起混入那将是灾难。

mixin带来的问题

1、如果我们要去查看一段代码逻辑,这段代码刚好又是mixin混入的,运气好能在第一个mixin文件中找到,运气不好可能需要连续找四五个mixin才能找到你要看的代码逻辑。

2、当你找到你想要的代码逻辑,你发现逻辑里面有个变量需要分析一下,当前文件一搜索,空空如也。然后你又开始在一层层mixin中查找这个变量。

3、混入很多mixin的时候,本地开发热更新也会明显变慢,改动一句代码就触发十几个mixin一起加载,导致开发体验很差

4、变量容易被覆盖,如果mixin中有一个很常见的变量,例如list,很多人在自己vue文件可能也会存在list这个变量,但是他并没有去查看mixin,那这个变量就被覆盖了,可能就导致mixin中的逻辑出现了问题

上面罗列了一些使用mixin中常见的一些问题,可能还有一些没有提及的,但是我们还是不能否认mixin的作用,在小型项目中合理使用mixin还是可以很好的抽离代码逻辑

Composition API

Composition APIvue3推出来的新的代码组合方式,中文翻译过来的意思就是组合式 API,那何为组合式 API呢?

组合式 API 就是以函数的方式,将响应式变量和代码逻辑组合在一起,对外暴露响应式变量和方法。官方也称这种函数为hook,跟react中的hook是没有差别的,但是内部实现是完全不同的,vue团队也说了是借鉴了react

有了Composition API这种代码组织方式,完全可以取代之前的mixin的功能,并且解决了mixin所带来的那些问题。使用函数的方式也让前端开发者开发体验更舒服

虽然Composition APIvue3推出来的新功能,但是vue团队把这个功能也更新到了vue2中,这里给vue团队点赞👍

在 Vue2 中使用 Composition API

vue2中使用Composition API需要根据vue2的版本来区分对待。在vue2.7版本之前,Composition API是作为vue2的一个插件来提供的,开发者需要在项目中安装Composition APInpm包,然后作为插件安装即可。相关的api也要从这个npm包中导入

vue2.7以后官方把Composition API的功能集成进了vue里面,开发者不需要再手动安装npm包了,直接使用即可,相关的api也直接可以从vue中导入。

vue2.7版本之前使用 Composition API

// 安装
+ npm i @vue/composition-api

// 在main.js中使用插件
+ import Vue from 'vue'
+ import CompositionApi from '@vue/composition-api'
+ Vue.use(CompositionApi)

// 在hook中使用
+ import { ref, onMounted } from '@vue/composition-api'
+ const useAge = (initialAge) => {
+   const age = ref(initialAge)
+   const getAge = () => {
+     return age.value
+   }
+   onMounted(() => {
+     console.log(`age:${age.value}`)
+   })
+ }

// vue文件中使用
+ export default {
+   setup() {
+     const { age, getAge } = useAge(18)
+   }
+ }

vue2.7版本使用 Composition API

// 2.7版本不需要安装
- npm i @vue/composition-api

// 2.7版本不需要在main.js中使用插件
- import Vue from 'vue'
- import CompositionApi from '@vue/composition-api'
- Vue.use(CompositionApi)

// 在hook中使用
- import { ref, onMounted } from '@vue/composition-api'
+ import { ref, onMounted } from 'vue'
+ const useAge = () => {
+   const age = ref(18)
+   const getAge = () => {
+     return age.value
+   }
+   onMounted(() => {
+     console.log(`age:${age.value}`)
+   })
+ }

// vue文件中使用
+ export default {
+   setup() {
+     const { age, getAge } = useAge(18)
+   }
+ }

如果在vue2.7使用了 Composition API插件,就会导致插件使用ref等响应式api的时候会被同时收集两次而导致意想不到的问题。所以在vue2中使用的时候首先就是要确认自己的vue版本信息

查看自己的vue版本信息的时候不能单纯的看package.json中的版本,因为很多项目的vue版本都不是2.7,但是项目又没有锁定版本号。当新人或者重新拉取项目安装依赖的时候,实际上安装的vue版本是最新的2.7。我们最好在node_modules中找到对应的包,然后去查看对应包的package.jsonversion

Composition API 使用(踩坑)指南

以下所有的的试例vue版本默认都是在vue2.7版本中

Composition API获取this

在vue里面我们很多方法都依赖当前的组件实例,但是在Composition APIthis就是个普通的变量了,不再是当前的组件实例,那如果要在Composition API获取组件实例该怎么做呢?

vue对外暴露了一个getCurrentInstance方法可以在setup中获取到当前的组件实例。这个方法只能在setup中使用,非setup中使用会报错。

但是getCurrentInstance方法在vue2vue3中是有些区别的

vue2中getCurrentInstance().proxy才是组件实例

vue3中getCurrentInstance()就可以直接获取到组件实例了

import { getCurrentInstance } from 'vue'
export default {
  setup() {
    const { age, getAge } = useAge(18)
    const vm = getCurrentInstance()
  }
}

思考
setup中也可以将当前的this指向组件实例,为啥vue内部没有这样做呢?
vue2中的options中不管在哪this都会指向当前的组件实例,这是vue2内部的特殊处理,这种改变this指向本身就有点破坏了js的编程规范,也会让刚接触的vue的人感到疑惑,加上在setup中基本上使用不到this了,所以让this回归他原本的作用也更贴合我们的编程规范

关于Ref API

Composition API中我们设置一个响应式变量主要是通过refreactive两个API来实现了。在vue3中响应式变量是通过Proxy重新实现,弥补了vue2响应式的一些不足,比如对象未定义的属性直接添加不会加入响应式,数组通过下标访问不会触发副作用等等。但是在vue2中虽然把Composition API更新过来了,不过内部的响应式还是使用的gettersetter,所以vue2中使用Composition API还是有之前的响应式丢失问题。

再回过头来聊这个refapi,在Composition API中优先还是推荐使用ref,一个是使用ref需要通过它的value属性去读区值,我们可以很好辨别当前哪些是响应式变量哪些是非响应式变量。当前社区里面也是更推荐使用ref而非reactive,但是都不是绝对的。

上面我们提到过怎么获取组件实例,如果我们通过组件实例去访问一个ref变量的时候,是不需要读取refvalue属性,直接使用就可以,组件实例会自动帮我们进行一个类似解包的操作

关于组件实例中ref响应式变量的“自动解包”
其实内部的原理是很简单的,vue内部对setup的返回值进行了一层Proxy拦截,在setter中帮我们给value附值,在getter帮我们读取value的值

“解包”原理

const proxyRef = new Proxy(objectWithRefs, {
  get(target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set(target, key, value, receiver) => {
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    } else {
      return Reflect.set(target, key, value, receiver)
    }
  },
})

const { setup } = options
const setupResult = proxyRef(setup())

在 Composition API中获取使用的组件或者dom元素

之前我们获取dom元素都是通过在组件或者dom元素上设置一个ref属性,然后通过this.$refs.设置的ref名称来获取,但是因为setupthis不再指向当前组件实例了,所以我们获取dom也换了方式

<template>
  <div>
    <h1 ref="titleRef">h1</h1>
    <home-page ref="homePageRef"/>
  </div>
</template>

<script>
  import { ref } from 'vue'
  export default {
    setup() {
      // 设置和ref相同的名称,会自动绑定上template中设置的ref
      const titleRef = ref()
      const homePageRef = ref()
      
      return { titleRef, homePageRef }
    }
  }
</script>