NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Vue 3 Composition API 实战前瞻

  • 2019-11-26
  • 本文字数:5147 字

    阅读完需:约 17 分钟

Vue 3 Composition API实战前瞻

虽然 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 中的布局)。它负责处理以下内容:


  • 设定区域;

  • 检查用户是否处于登录状态,如果没有,则将其重定向;

  • 防止用户重新加载应用程序太多次数;

  • 跟踪用户活动,并在用户静默一段时间后做出反应;

  • 使用 EventBus 监听事件(或窗口对象事件)。


这些只是这个组件可以做的事情的一些例子。你可能会设想出一个更复杂的组件,不过这里的这些已经足够本文举例说明了。为了便于阅读,我只用了 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>
复制代码


如你所见,每个选项都包含所有功能的其中一部分。它们之间没有明确的分隔,这使代码很难阅读。如果代码并不是你写的,你还是第一次看到它,那么读起来会更费劲,很难分清具体哪种功能使用的是哪种方法。


我们再来看一下,但这次用注释把逻辑上的关注点标识出来。这些关注点包括:


  • Activity Tracker(活动追踪);

  • Reload Blocker(阻止重新加载);

  • Authentication check(登录状态检查);

  • Locale(区域选项);

  • Event Bus Registration(EventBus 注册)。


<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 logicfunction 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 logicfunction useReloadBlocker(context) {  const reloadCount = ref(null)
function blockReload() {...} function setReloadCount() {...}
onMounted(() => { setReloadCount() blockReload() })}
复制代码


// Locale logicfunction 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 registrationimport 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.jsimport 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.无需与任何特定组件紧密耦合也独立运行的一般性功能


  • 与一个特定功能相关的所有逻辑都放在一个文件中;

  • 将其保存在 @/composables/*.js,并将其导入组件中;

  • 示例:活动跟踪、阻止重新加载和区域设置。


2.可在多个组件中使用的可复用功能


  • 与一个特定功能相关的所有逻辑都放在一个文件中;

  • 将其保存在 @/composables/*.js,并将其导入组件中;

  • 示例:事件总线侦听器注册、窗口事件注册、通用动画逻辑、通用库的使用。


3.组件内的代码组织


  • 与一个特定功能相关的所有逻辑都放在一个函数中;

  • 将代码保留在组件内的 composition 函数中;

  • 与同一逻辑关注点相关的代码位于同一位置。也就是说,无需在数据、计算属性、方法、生命周期 hooks 等内容之间来回跳转)。

记住:这些都尚在开发中!

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/


2019-11-26 15:162898
用户头像
王文婧 InfoQ编辑

发布了 126 篇内容, 共 70.6 次阅读, 收获喜欢 275 次。

关注

评论

发布
暂无评论
发现更多内容

语音聊天app源码中,技术性和功能性并存,技术和功能层面的细节考虑有哪些?

山东布谷科技胡月

语音聊天APP源码 语音直播app开发 国际多语言设计app开发 语音社交平台搭建

模块八 消息队列mysql存储表结构设计

家有两宝

#架构训练营

Spring 容器介绍

EquatorCoco

spring spring ioc

【MySQL技术专题】「问题实战系列」深入探索和分析MySQL数据库的数据备份和恢复实战开发指南(数据恢复补充篇)

洛神灬殇

MySQL 数据库 Binlog 数据库备份和恢复

携手生态共筑数智底座,加速企业数智化转型

用友BIP

数智底座

java面试题-多线程

程序员小张

智能制造之路—从0开始打造一套轻量级MOM平台

EquatorCoco

数字化 智能制造

【网易云信】直播场景播放侧常见问题分析与实践经验

网易云信

直播 实时音视频 音视频开发 直播推流

9个值得推荐的前端低代码项目!

这我可不懂

前端 低代码 低代码平台 JNPF

OpenMLDB 发布线上到线下数据自动同步工具

第四范式开发者社区

人工智能 机器学习 数据库 开源 特征

成都站|阿里云 Serverless 技术实战营邀你来玩!

Serverless Devs

云计算 负载均衡 Serverless 云原生 弹性计算

简化办公,云上助力!

知者如C

软件测试/测试开发丨Linux 三剑客与管道使用

测试人

Linux 程序员 软件测试

使用 Postman 批量发送请求实用教程

Liam

Java 后端 开发 Postman API

五种高级 NodeJS 技术

互联网工科生

node.js nodejs

什么是低代码开发平台?浅谈它的价值

高端章鱼哥

低代码 aPaaS JNPF

覆盖全球4亿+用户的大型企业如何构建财务共享中心?

用友BIP

财务共享

“数智化供应链“赋能有色企业原料供应链管理优化

用友BIP

冶金

Python案例实现|爬取租房网站信息

TiAmo

Python 数据分析

点云标注在自动驾驶中有着广泛的应用案例

来自四九城儿

如何成为网络安全大牛(黑客)?

网络安全学海

黑客 网络安全 信息安全 计算机 渗透测试

汽车软件的模糊测试

DevOps和数字孪生

软件定义汽车

【参考设计】100 W USB PD 3.0电源

元器件秋姐

设计 电路 方案 usb 电源

软件测试/测试开发丨Python 面向对象编程思想

测试人

Python 编程 面向对象 软件测试

得物 Android 包体积资源优化实践

得物技术

前端 用户体验 SEO

打造工业互联网平台,强化“腰部”支撑,助力实现国产替代

用友BIP

国产替代

兴业银行携手用友,为企业打造新一代财资管理服务

用友BIP

银行 司库

【网易云信】直播场景播放侧常见问题分析与实践经验

网易智企

直播 实时音视频 直播推流 音视频技术

MQTT 订阅选项的使用

EMQ映云科技

mqtt 订阅选项

如何使用 NFTScan SDK 工具构建 NFT Explorer Dapp

NFT Research

NFT\ SDK 教程

用友BIP助力企业升级数智化底座,实现数智转型

用友BIP

国产替代

Vue 3 Composition API实战前瞻_大前端_Mateusz Rybczonek_InfoQ精选文章