虽然 Vue3.0 尚未发布,但前段时间,Vue 发布了关于 Composition API 的官方插件,使广大用户可以在 Vue2.x 中享受 Function Base 带来的新体验。本文作者通过实际试用,对 Composition API 与当前的 Options API 作出了一番对比,并对 Composition API 的特征与用途进行了通俗易懂的总结。一起来看看!
我最近得到了机会,在一个真实的项目中试用了 Vue 中全新的Composition API,进而对它可能的用途以及未来的用法探索了一番。
现在,当我们创建一个新组件时使用的是Options API。使用这个 API 时,我们必须通过选项将组件的代码分离开来,这意味着我们需要将所有响应性数据放在一个地方,所有计算的属性放在一个位置,所有方法也都放在一个位置,依此类推。
这个 API 在处理较小的组件时比较易读和顺手,而当组件变得更加复杂,需要处理多种功能时,它用起来就很痛苦了。一般来说,与一个特定功能相关的逻辑会包含一些响应性数据、一些计算属性,还有一种或一些方法。有时还会用到组件生命周期 hooks。于是在处理单个逻辑问题时,需要不断在代码中的不同选项之间来回切换。
使用 Vue 时,可能遇到的另一个问题是设法提取可被多个组件复用的通用逻辑。Vue 已经提供了一些方案可供选择,但它们都有各自的缺点(例如 mixins 和作用域插槽)。而新的 Composition API 带来了一种创建组件、分离代码和提取可复用代码段的全新方式。
首先来看组件内的代码构成。
代码构成
假设你有一个核心组件,为整个 Vue 应用设置了一些内容(就像 Nuxt 中的布局)。它负责处理以下内容:
这些只是这个组件可以做的事情的一些例子。你可能会设想出一个更复杂的组件,不过这里的这些已经足够本文举例说明了。为了便于阅读,我只用了 props 的名称,而没有实际实现。
下面是使用 Options API 时组件的样子:
<template>
<div id="app">
...
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userActivityTimeout: null,
lastUserActivityAt: null,
reloadCount: 0
}
},
computed: {
isAuthenticated() {...}
locale() {...}
},
watch: {
locale(value) {...},
isAuthenticated(value) {...}
},
async created() {
const initialLocale = localStorage.getItem('locale')
await this.loadLocaleAsync(initialLocale)
},
mounted() {
EventBus.$on(MY_EVENT, this.handleMyEvent)
this.setReloadCount()
this.blockReload()
this.activateActivityTracker()
this.resetActivityTimeout()
},
beforeDestroy() {
this.deactivateActivityTracker()
clearTimeout(this.userActivityTimeout)
EventBus.$off(MY_EVENT, this.handleMyEvent)
},
methods: {
activateActivityTracker() {...},
blockReload() {...},
deactivateActivityTracker() {...},
handleMyEvent() {...},
async loadLocaleAsync(selectedLocale) {...}
redirectUser() {...}
resetActivityTimeout() {...},
setI18nLocale(locale) {...},
setReloadCount() {...},
userActivityThrottler() {...},
}
}
</script>
复制代码
如你所见,每个选项都包含所有功能的其中一部分。它们之间没有明确的分隔,这使代码很难阅读。如果代码并不是你写的,你还是第一次看到它,那么读起来会更费劲,很难分清具体哪种功能使用的是哪种方法。
我们再来看一下,但这次用注释把逻辑上的关注点标识出来。这些关注点包括:
<template>
<div id="app">
...
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userActivityTimeout: null, // Activity tracker
lastUserActivityAt: null, // Activity tracker
reloadCount: 0 // Reload blocker
}
},
computed: {
isAuthenticated() {...} // Authentication check
locale() {...} // Locale
},
watch: {
locale(value) {...},
isAuthenticated(value) {...} // Authentication check
},
async created() {
const initialLocale = localStorage.getItem('locale') // Locale
await this.loadLocaleAsync(initialLocale) // Locale
},
mounted() {
EventBus.$on(MY_EVENT, this.handleMyEvent) // Event Bus registration
this.setReloadCount() // Reload blocker
this.blockReload() // Reload blocker
this.activateActivityTracker() // Activity tracker
this.resetActivityTimeout() // Activity tracker
},
beforeDestroy() {
this.deactivateActivityTracker() // Activity tracker
clearTimeout(this.userActivityTimeout) // Activity tracker
EventBus.$off(MY_EVENT, this.handleMyEvent) // Event Bus registration
},
methods: {
activateActivityTracker() {...}, // Activity tracker
blockReload() {...}, // Reload blocker
deactivateActivityTracker() {...}, // Activity tracker
handleMyEvent() {...}, // Event Bus registration
async loadLocaleAsync(selectedLocale) {...} // Locale
redirectUser() {...} // Authentication check
resetActivityTimeout() {...}, // Activity tracker
setI18nLocale(locale) {...}, // Locale
setReloadCount() {...}, // Reload blocker
userActivityThrottler() {...}, // Activity tracker
}
}
</script>
复制代码
由此可见,解开这团乱麻有多复杂。🙂
现在假设你需要更改一种功能(例如活动追踪逻辑)。你不仅需要知道有哪些元素与该逻辑相关,而且就算你知道了,也需要在不同的组件选项之间跳来跳去。
下面我们使用 Composition API,通过逻辑关注点来分离代码。为此,我们为每个与特定功能相关的逻辑创建一个函数。这就是我们所说的 composition 函数。
// Activity tracking logic
function useActivityTracker() {
const userActivityTimeout = ref(null)
const lastUserActivityAt = ref(null)
function activateActivityTracker() {...}
function deactivateActivityTracker() {...}
function resetActivityTimeout() {...}
function userActivityThrottler() {...}
onBeforeMount(() => {
activateActivityTracker()
resetActivityTimeout()
})
onUnmounted(() => {
deactivateActivityTracker()
clearTimeout(userActivityTimeout.value)
})
}
复制代码
// Reload blocking logic
function useReloadBlocker(context) {
const reloadCount = ref(null)
function blockReload() {...}
function setReloadCount() {...}
onMounted(() => {
setReloadCount()
blockReload()
})
}
复制代码
// Locale logic
function useLocale(context) {
async function loadLocaleAsync(selectedLocale) {...}
function setI18nLocale(locale) {...}
watch(() => {
const locale = ...
loadLocaleAsync(locale)
})
// No need for a 'created' hook, all logic that runs in setup function is placed between beforeCreate and created hooks
const initialLocale = localStorage.getItem('locale')
loadLocaleAsync(initialLocale)
}
复制代码
// Event bus listener registration
import EventBus from '@/event-bus'
function useEventBusListener(eventName, handler) {
onMounted(() => EventBus.$on(eventName, handler))
onUnmounted(() => EventBus.$off(eventName, handler))
}
复制代码
如你所见,我们可以声明响应性数据(ref/reactive)、计算的 props、方法(纯函数)、观察者(watch)和生命周期 hooks(onMounted/onUnmount)。基本上你平时在组件中使用的所有内容都能声明。
关于保存代码的位置,我们有两个选择。我们可以将其保留在组件中,或提取到单独的文件中。由于 Composition API 尚未正式发布,因此还没有关于如何使用它的最佳实践或规则。我的看法是,如果逻辑与特定组件紧密耦合(即不会在其他任何地方复用),并且逻辑离开了组件就无法生存,我建议将其保留在组件中。另一方面,如果是可能会被复用的一般性功能,则建议将其提取到单独的文件中。但如果我们要将其保存在单独的文件中,则需要从文件中导出函数并将其导入到组件中。
这是使用新创建的 composition 函数后,我们组件的样子:
<template>
<div id="app">
</div>
</template>
<script>
export default {
name: 'App',
setup(props, context) {
useEventBusListener(MY_EVENT, handleMyEvent)
useActivityTracker()
useReloadBlocker(context)
useLocale(context)
const isAuthenticated = computed(() => ...)
watch(() => {
if (!isAuthenticated) {...}
})
function handleMyEvent() {...},
function useLocale() {...}
function useActivityTracker() {...}
function useEventBusListener() {...}
function useReloadBlocker() {...}
}
}
</script>
复制代码
这里每个逻辑关注点都有了一个函数。如果要使用某一个关注点,则需要在新的 setup 函数中调用相关的 composition 函数。
再设想一下,你需要对活动跟踪逻辑作一些更改。与该功能相关的所有内容都放在 useActivityTracker 函数中。现在你就能立刻找出并跳转到正确的位置,查看所有相关的代码段了。非常漂亮!
提取可复用的代码段
在我们的例子中,事件总线侦听器注册(Event Bus listener registrations)看来是一段代码,如果有组件需要侦听事件总线上的事件,我们就可以用这段代码来实现。
如前所述,我们可以将与特定功能相关的逻辑保存在单独的文件中。下面我们将事件总线侦听器设置转移到一个单独的文件中。
// composables/useEventBusListener.js
import EventBus from '@/event-bus'
export function useEventBusListener(eventName, handler) {
onMounted(() => EventBus.$on(eventName, handler))
onUnmounted(() => EventBus.$off(eventName, handler))
}
复制代码
要在组件中使用它,我们需要导出函数(命名或默认),并将其导入组件中。
<template>
<div id="app">
...
</div>
</template>
<script>
import { useEventBusListener } from '@/composables/useEventBusListener'
export default {
name: 'MyComponent',
setup(props, context) {
useEventBusListener(MY_EVENT, myEventHandled)
useEventBusListener(ANOTHER_EVENT, myAnotherHandled)
}
}
</script>
复制代码
完事了!现在我们能在任何组件中使用它了。
小结
关于 Composition API 的讨论还在进行中。这篇文章无意在讨论中站队,更关心的是新 API 可能的用途,以及它能在哪些情况下带来附加价值。
我认为在现实案例中理解概念总是比较容易的,就像上面这个例子一样。用例越多,使用新 API 的次数越多,我们也就能发现更多的模式。这篇文章只涉及了一些基本的模式,供抛砖引玉。
我们再来看一遍上面这些用例,看看 Composition API 的用途有哪些:
1.无需与任何特定组件紧密耦合也能独立运行的一般性功能
2.可在多个组件中使用的可复用功能
3.组件内的代码组织
记住:这些都尚在开发中!
Vue Composition API 目前尚处于开发阶段,未来还可能出现更改。上面示例中提到的任何内容、语法和用例都可能出现变化。这个 API 计划将随 Vue 3.0 一起推出。另外,你可以在view-use-web上查看一组 composition 函数的信息,这些函数预计会包含在 Vue 3 中,但也能用在 Vue 2 中的 Composition API 上。
如果你想尝试新的 API,可以使用@vue/composition库。
原文链接:
https://css-tricks.com/an-early-look-at-the-vue-3-composition-api-in-the-wild/
评论