在 vue3 版本之前,我们复用组件(或者提取和重用多个组件之间的逻辑),通常有以下几种方式:
上述提到的几种方式,也是我们项目中正在使用的方式。对于提取和重用多个组件之间的逻辑似乎并不简单。我们甚至采用了 extend 来做到最大化利用已有组件逻辑,因此使得代码逻辑依赖严重,难以阅读和理解。
Vue3 中的 Composition API 便是解决这一问题;且完美支持类型推导,不再是依靠一个简单的 this
上下文来暴露 property(比如 methods
选项下的函数的 this
是指向组件实例的,而不是这个 methods
对象)。其是一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑。
使用 Vue2 和 Vue3 开发组件有很大的差异性:
下面,通过一个示例代码,结合 Vue2 和 Vue3 来聊聊 Composition Api 。
<template>
<div>
<p>当前系统的主题色为 -- {{color}}p>
<p>当前viewport的宽高比为16:9 -- {{is16than9}}p>
div>
template>
WEB API | 说明 | 示例 |
---|---|---|
Window.matchMedia() | 回一个新的MediaQueryList 对象,表示指定的媒体查询字符串解析后的结果 | window.matchMedia(mediaQueryString) |
prefers-color-scheme | 检测用户是否有将系统的主题色设置为亮色或者暗色 | window.matchMedia('(perfers-color-scheme: dark)') |
aspect-ratio | 可以用来测试 viewport 的宽高比 |
①:主题色相关 ②:宽高比检测相关
export default {
name: 'SystemColor',
data () {
return {
color: '', // ①
is16than9: false // ②
}
},
methods: {
// ①
perfersColorSchemeUpdate () {
this.perfersColorSchemeMedia = window.matchMedia('(perfers-color-scheme: dark)')
this.color = this.perfersColorSchemeMedia.matches ? 'dark': 'light'
},
// ②
aspectRatioUpdate () {
this.aspectRatioMedia = window.matchMedia('(aspect-ratio: 16/9)')
this.is16than9 = this.aspectRatioMedia.matches
}
},
created () {
// ①
this.perfersColorSchemeUpdate()
this.perfersColorSchemeMedia.addEventListener('change', this.perfersColorSchemeUpdate)
// ②
this.aspectRatioUpdate()
this.aspectRatioMedia.addEventListener('change', this.aspectRatioUpdate)
},
destroyed () {
// ①
this.perfersColorSchemeMedia.removeEventListener('change', this.perfersColorSchemeUpdate)
// ②
this.aspectRatioMedia.removeEventListener('change', this.aspectRatioUpdate)
}
}
现在的诉求,又有一个新组件我们需要复用 「主题色相关」内容,我们需要对其进行抽离。
在 vue2 中,我们优先想到的是采用 mixins 方式
/* perfersColorSchemeMixin.js */
export default {
methods: {
perfersColorSchemeUpdate () {
this.perfersColorSchemeMedia = window.matchMedia('(perfers-color-scheme: dark)')
this.color = this.perfersColorSchemeMedia.matches ? 'dark': 'light'
}
}
}
import perfersColorSchemeMixin from './perfersColorSchemeMixin.js'
import aspectRatioMixin from './aspectRatioMixin.js'
export default {
name: 'SystemColor',
mixins: [perfersColorSchemeMixin, aspectRatioMixin],
data () {
return {
color: '',
is16than9: false
}
},
created () {
this.perfersColorSchemeUpdate()
this.perfersColorSchemeMedia.addEventListener('change', this.perfersColorSchemeUpdate)
this.aspectRatioUpdate()
this.aspectRatioMedia.addEventListener('change', this.aspectRatioUpdate)
},
destroyed () {
this.perfersColorSchemeMedia.removeEventListener('change', this.perfersColorSchemeUpdate)
this.aspectRatioMedia.removeEventListener('change', this.aspectRatioUpdate)
}
}
当然我们也可以把 data
以及 created
中的内容也抽离到 mixins
中。但这样会导致模板中使用的变量不知道来自哪个 mixins,且可能存在命名冲突等问题。此时如果我们想再增加第3个新增功能,需要各个地方增加,阅读性变差、难以维护。
正是这种碎片化使得理解和维护一个复杂的组件变得非常困难。选项的强行分离为展示背后的逻辑关注点设置了障碍。此外,在处理单个逻辑关注点时,我们必须不断地在选项代码块之间“跳转”,以找到与该关注点相关的部分。
当我们在组件间提取并复用逻辑时,组合式(@vue/runtime-core
) API 是十分灵活的。一个组合函数仅依赖它的参数和 Vue 全局导出的 API,而不是依赖其微妙的 this
上下文。你可以将组件内的任何一段逻辑导出为函数以复用它。
组件可以变得如此清爽
import { reactive, toRefs } from 'vue'
import usePrefersColorScheme from './usePrefersColorScheme.js'
import useAspectRatio from './useAspectRatio.js'
export default {
name: 'SystemColor',
setup () {
let color = usePrefersColorScheme() // ① 仅此一处
let is16than9 = useAspectRatio() // ② 仅此一处
return {
color,
is16than9
}
}
}
下述可组合式函数,建议使用 use 作为函数名的开头,以表示它是一个组合函数
/* ① usePrefersColorScheme.js */
import { ref, onUnmounted, onMounted } from 'vue'
export default function usePrefersColorScheme () {
let media
let color = ref(null)
const update = () => {
media = window.matchMedia('(perfers-color-scheme: dark)')
color.value = media.matches ? 'dark': 'light'
}
update()
media.addEventListener('change', update)
onUnmounted(() => {
media.removeEventListener('change', update)
})
return color
}
/* ② useAspectRatio.js */
import { ref, onUnmounted, onMounted } from 'vue'
export default function usePrefersColorScheme () {
let media
let is16than9 = ref(null)
const update = () => {
media = window.matchMedia('(aspect-ratio: 16/9)')
is16than9.value = media.matches
}
update()
media.addEventListener('change', update)
onUnmounted(() => {
media.removeEventListener('change', update)
})
return is16than9
}
这些组合函数里面他都可以使用生命周期的钩子,可以自动更新和自动注销。在使用的时候也就没有了负担,只需要去留意每一个 ref 对应什么样的功能,其更好的提高代码的复用度、可读性、可维护性。
相比而言,组合式 API:
Vue 3 已经可以使用的有两个主要的逻辑的组件库,vueuse
和 vue-composable