【QCon】精华内容上线92%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

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:162881
用户头像
王文婧 InfoQ编辑

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

关注

评论

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

FusionInsight MRS Flink DataStream API读写Hudi实践

华为云开发者联盟

大数据 华为云 数据读写 企业号十月 PK 榜

为提高 SDLC 安全,GitHub 发布新功能|GitHub Universe 2022

SEAL安全

GitHub 安全

APP“小动作”不断?HarmonyOS 3隐私中心可视化面板让它无处藏身

Geek_2d6073

Spring框架中都用到了哪些设计模式 ?

千锋IT教育

深度理解Redux原理并实现一个redux

夏天的味道123

React

彻底搞懂React-hook链表构建原理

夏天的味道123

React

ShardingSphere + OpenSergo,共同提升微服务体系下数据库的性能与稳定

SphereEx

数据库 微服务 Apache ShardingSphere

河南数字经济产业创新研究院加入星策社区,携手推进企业智能化转型发展

星策开源社区

人工智能 开源 AI 企业转型 智能化

前端手写面试题,看这一篇就够了

helloworld1024fd

JavaScript

深度讲解React Props

夏天的味道123

React

人工智能自然语言处理之数据增强去噪类别不平衡模型轻量化

XiaoChao_AI

人工智能 nlp 数据预处理 11月月更

首个中文Stable Diffusion模型开源;TPU演进十年;18个PyTorch性能优化技巧 | AI系统前沿动态

OneFlow

人工智能 开源 深度学历 TPU

python的成员方法的简单介绍

乔乔

11月月更

一个 SaaS 软件同本地部署 On-Premises 系统集成的实际项目案例分享

Jerry Wang

微服务 SaaS 系统集成 On-Premises 11月月更

GaiaX开源解读 | 跨端动态化模板引擎详解,看完你也能写一个

阿里巴巴文娱技术

开源 移动开发 移动端开发

DTSE Tech Talk | 第10期:云会议带你入门音视频世界

华为云开发者联盟

云计算 后端 华为云 企业号十月 PK 榜

源码学习之Spring容器创建原理

京东科技开发者

xml spring 源码 容器 测试

手写现代前端框架diff算法-前端面试进阶

helloworld1024fd

JavaScript

人工智能计算机视觉之OCR(光学字符识别)

XiaoChao_AI

人工智能 CV 计算机视觉 11月月更

5年匠心之作,云原生安全真经大公开!

青藤云安全

网络安全 青藤云安全 云原生安全

前端vue多人写作开发技巧-路由配置

千锋IT教育

全国信安标委杨建军秘书长一行莅临青藤,调研指导网络安全标准和产业工作

青藤云安全

网络安全 青藤云安全

什么是Maven

莪是男神

Java maven 11月月更

学习MySQL必须掌握的13个关键字,你get了吗?

小小怪下士

Java MySQL 程序员

clickhouse在风控-风险洞察领域的探索与实践

京东科技开发者

flink 数据 Clickhouse 风控 风险控制

手写JavaScript常见5种设计模式

helloworld1024fd

JavaScript

火山引擎DataTester :让字节“跳动”起来的A/B实验平台

字节跳动数据平台

大数据 火山引擎 A/B 测试

蜂巢再获权威认可 | 一个被甲方、乙方都认可的测评

青藤云安全

网络安全

Flask框架:如何运用Ajax轮询动态绘图

华为云开发者联盟

JavaScript 前端 华为云 企业号十月 PK 榜

90 条简单实用的 Python 编程技巧,建议收藏

千锋IT教育

高级前端一面常考手写面试题指南

helloworld1024fd

JavaScript

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