• 原文地址:https://itnext.io/better-composition-in-vue-fd35b9fe9c79

  • 原文作者:????Francesco Vitullo

  • 译文出自:????掘金翻译计划

  • 本文永久链接:https://github.com/xitu/gold-miner/blob/master/article/2020/better-composition-in-vue.md

  • 校对者:????Gesj-yean, ????dupanpan

VueJS 中有一些组合组件并复用逻辑的方法。在本文中,我将展示一种在 Vuejs (2.* 及 3.*) 中改进组合方式的方法。

我的确欣赏最近的 Composition API 提案,但我认为视野还可以更开阔。

下面,你可以看到一个实现了一种常规用例(从远端获取一个简单的数据并将其搭配不同的转场效果显示出来)的组件,尽管大部分逻辑及其相关的模版、数据和其它变量等与出现在其它地方或组件中的相同逻辑并无不同,它们还是出现在了该组件中。

<template>
<div><div v-if="loading"> Loading... </div><div v-if="error"> An Error occured, please try again</div><div v-if="hasData"> {{ data }} </div>
</div>
</template></template><script>export default {data() {return {loading: false,error: false,data: {}}},methods: {fetchData() {this.loading = true;setTimeout(() => {this.data = { text: 'example' };this.loading = false;}, 4000);}},computed: {hasData() {return this.data && !!this.data.text;}},mounted() {this.fetchData();}}
</script>

该如何重构并改善这个组件呢?让我们一步步地让其更易读且更容易复用。

Vue Composition API

感谢新的 Vue Composition API,使得我们可以在不丢失由 Vue 组件提供的响应性或其它特性的前提下,抽出一些逻辑以来复用它。

这种方式有助于组织代码、让组件更易读,并有助于降低总体复杂度。作为一种建议,我相信这些应该是重构巨大、复杂和混乱的组件时的首要之事。

我们将抽取与获取数据有关的部分及相关的变量(loading、error 等……),但我并不想谈论什么是 Composition API 以及其特性、优点和缺点。

让我们来创建一个提供了获取数据必要功能及若干响应式变量的简单函数:

import { reactive, toRefs, computed, Ref, ComputedRef } from '@vue/composition-api';interface ReceivedData {text?: string
}interface FetchState {loading: boolean,error: boolean,data: ReceivedData
}interface FetchDataVars {loading: Ref<boolean>;error: Ref<boolean>;data: Ref<object>;fetchData: Function;hasData: ComputedRef<boolean>
}export default (): FetchDataVars => {const state = reactive<FetchState>({loading: true,error: false,data: {}});const fetchData = async () => {state.loading = true;setTimeout(() => {state.data = { text: 'example' };state.loading = false;}, 4000);}const hasData = computed(() => state.data && !!state.data.text)return {...toRefs(state),fetchData,hasData}
}

新创建的函数现在返回了可被用于组件的一组响应式变量 (loading、error、data,及 hasData) 及一个用来执行数据获取任务的异步函数 (fetchData,将会改变上述响应式变量) 。

而后,来使用 Composition API 重构组件:

<template>
<div><div v-if="loading"> Loading... </div><div v-if="error"> An Error occured, please try again</div><div v-if="hasData"> {{ data }} </div>
</div>
</template></template><script lang="ts">import useFetchData from '../composables/use-fetch-data';import { defineComponent } from '@vue/composition-api';export default defineComponent({setup() {const { loading, error, data, fetchData, hasData } = useFetchData();return {loading,error,data, fetchData,hasData}},mounted() {this.fetchData();}});
</script>

正如你所注意到的,我们的组件还包含了 setup 方法,由其调用 useFetchData 函数,同时解构返回的变量和函数并将它们返回给组件实例。

在这个例子中,我在 mounted 生命周期钩子中使用了 fetchData 函数,但其实你可以在期望的任意位置调用它。无论何时,被该函数求值或改变的结果都会反映在组件中,因为它们都是响应式属性。

JSX 和 TSX

现在假设我们想要将获取的数据传递到一个内部组件中。借助 VueJS 有多种实现的方法,但我却想使用  TSX (你若更喜欢 JSX 也行) 来重构代码:

<script lang="tsx">import useFetchData from '../composables/use-fetch-data';import { defineComponent } from '@vue/composition-api';export default defineComponent({setup() {const { loading, error, data, fetchData, hasData } = useFetchData();return {loading,error,data, fetchData,hasData}},mounted() {this.fetchData();},render() {return (<div>{ this.loading && <div> Loading ... </div> }{ this.error && <div> An Error occured, please try again </div> }{ <div> { this.data } </div> }</div>)}});
</script>

我知道这看起来很像 React,但我相信这开启了以更好的方法优化组合方式的许多可能之门。

这其实很易懂,它完成了和模板同样的事情,但我们将 HTML 部分移入了 render 函数中。

我们尚未完成将数据传递进内部组件的任务,实际上我们像下面这样改进一点代码就行,也就是将所有东西导出成一个我们可复用的函数:

import useFetchData from '../composables/use-fetch-data';
import { defineComponent } from '@vue/composition-api';export default () => defineComponent({setup() {const { loading, error, data, fetchData, hasData } = useFetchData();return {loading,error,data, fetchData,hasData}},mounted() {this.fetchData();},render() {return (<div>{ this.loading && <div> Loading ... </div> }{ this.error && <div> An Error occured, please try again </div> }{ <div> { this.data } </div> }</div>)}
});

现在我们已经更上一层楼了,摆脱 SFC (单文件组件 -- Single File Component 文件) 后我们就可以真正的改进组织方式了。

在此阶段,我们使用 defineComponent 创建了一个使用 Composition API 的组件并依托 JSX/TSX 消除了模板部分。这种方式的妙处在于可以将一个组件视为一个函数并自如运用函数式编程范式(如一级函数、纯函数等等……)了。

举例来说,render 函数也包含了一个显示数据的 div,但想象下若将一个组件作为刚才所导出函数的一个参数,并在返回的 JSX/TSX 中使用它(将响应/数据作为属性传递给组件)是如何的呢。

看起来可能会是这样的:

import useFetchData from '../composables/use-fetch-data';
import { defineComponent } from '@vue/composition-api';
import { Component } from 'vue';export default (component: Component) => defineComponent({setup() {const { loading, error, data, fetchData, hasData } = useFetchData();return {loading,error,data, fetchData,hasData}},mounted() {this.fetchData();},render() {const injectedComponentProps = {data: this.data}return (<div>{ this.loading && <div> Loading ... </div> }{ this.error && <div> An Error occured, please try again </div> }<component props={ injectedComponentProps } /></div>)}
});

现在我们正期待着将一个组件作为参数并在 render 函数中使用它。

还可以做得更多。

实际上,我们也可以期待将 useFetchData 函数作为所导出函数的一个参数。

import useFetchData from '../composables/use-fetch-data';
import { defineComponent, ComputedRef, Ref } from '@vue/composition-api';
import { Component } from 'vue';interface FetchDataVars {loading: Ref<boolean>;error: Ref<boolean>;data: Ref<object>;fetchData: Function;hasData: ComputedRef<boolean>
}type FetchData = () => FetchDataVars ;export default (component: Component, factoryFetchData: FetchData) => defineComponent({setup() {const { loading, error, data, fetchData, hasData } = factoryFetchData();return {loading,error,data, fetchData,hasData}},mounted() {this.fetchData();},render() {const injectedComponentProps = {data: this.data}return (<div>{ this.loading && <div> Loading ... </div> }{ this.error && <div> An Error occured, please try again </div> }<component data={ injectedComponentProps } /></div>)}
});

借助这些改变,在组件之上,接受一个类型为 FetchData 并返回一组符合预期的变量/函数/计算值的 函数 作为参数,就可以使用包装过的新组件。

这是一种依托函数式途径达成的相当有用的替代继承/扩展的方法。所以,不同于扩展已有的组件并覆写组件的函数的是,我们可以真正传入期望的组件和函数了。Typescript 在此仅有助于强类型化和类型推断,所以只用 Javascript 也是足够的。

例如,如果我们想要使用它,看起来会是这样的:

import withLoaderAndFetcher from './components/withLoaderAndFetcher';import useFetchDataForEndpointOne from './composables/useFetchDataForEndpointOne'
import useFetchDataForEndpointTwo from './composables/useFetchDataForEndpointTwo'
import useFetchDataForEndpointThree from './composables/useFetchDataForEndpointThree'import ComponentA from './components/ComponentA.vue';
import ComponentB from './components/ComponentB.vue';
import ComponentC from './components/ComponentC.vue';const composedA = withLoaderAndFetcher(ComponentA, useFetchDataForEndpointOne);
const composedB = withLoaderAndFetcher(ComponentB, useFetchDataForEndpointTwo);
const composedC = withLoaderAndFetcher(ComponentC, useFetchDataForEndpointThree);

我们将上例导出的函数称为 withLoaderAndFetcher 并使用其组合了 3 个不同的组件和 3 个不同的函数(装饰者模式)。

这项工作还能推进得更远,但我想展示的是达到这种状态的可能性并增加趋向函数式组合方式的方法数量。这只是示例代码,也可能不会工作得很好,但这种想法和概念才是要义。

干杯 :)

如果发现译文存在错误或其他需要改进的地方,欢迎到 ????掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。

--End--

最后

看完点个赞,分享一下吧,让更多的朋友能够看到。如果你喜欢前端开发博客的分享,就给公号标个星吧,这样就不会错过我的文章了。

好文和朋友一起看~

VueJS 中更好的组件组合方式相关推荐

  1. react 中更好的 svg 使用方式

    前言 之前一直用 image 的方式使用 svg,后来,改把 svg 上传到阿里巴巴的 iconfont-阿里巴巴矢量图标库 使用. 前段时间,iconfont 出问题了,不支持上传 svg,也不支持 ...

  2. [Android学习笔记四] 自定义Android组件之组合方式创建密码框组件

    Android中所有控件(也称组件)都继承自adnroid.view.View类,android.view.ViewGroup是View类的重要子类,绝大多书的布局类就继承自ViewGroup类. 参 ...

  3. 038——VUE中组件之WEB开发中组件使用场景与定义组件的方式

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  4. 组件命名方式||局部组件注册:局部组件只能在注册他的父组件中使用

    组件命名方式 组件注册注意事项                 如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是                 在普通的标签 ...

  5. 组件skype服务器,Skype for Business Server 中的中介服务器组件

    Skype for Business Server 中的中介服务器组件Mediation Server component in Skype for Business Server 2021/3/24 ...

  6. React中的高阶组件

    React中的高阶组件 高阶组件HOC即Higher Order Component是React中用于复用组件逻辑的一种高级技巧,HOC自身不是React API的一部分,它是一种基于React的组合 ...

  7. vuejs中如何实现三级路由并刷新页面时保持当前路由激活状态

    虽互不曾谋面,但希望能和您成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 标星公众号(ID:itclanCoder) 如果不知道如 ...

  8. 如何在cocos2d-x中使用ECS(实体-组件-系统)架构方法开发一个游戏?

    引言 在我的博客中,我曾经翻译了几篇关于ECS的文章.这些文章都是来自于Game Development网站.如果你对这个架构方式还不是很了解的话,欢迎阅读理解 组件-实体-系统和实现 组件-实体-系 ...

  9. 预览ExtJS 4.0的新功能/新特性(一):渲染组件的方式

    转载请注明出处Ext中文网(http://www.ajaxjs.com). ExtJS 3.3的下一个版本就是4.0.--什么!?您不知道?那就让我们为你展开新一段的 Ext 之旅吧! 一.渲染组件的 ...

最新文章

  1. Java多线程高并发学习笔记(一)——ThreadRunnable
  2. 数学符号正三角形△和倒三角形▽的意思
  3. 第一章 TensorFlow基础——python语法(一)
  4. python time strptime_Python中操作时间之strptime()方法的使用
  5. 数据库-解决MySQL的一些常见问题
  6. 写毕业论文,要我狗命!
  7. 如何手动修改oracle表空间,ORACLE数据库创建和修改表空间
  8. flowable 数据库表结构 梳理
  9. OWIN之中间件用法示例
  10. PHP extension mcrypt must be loaded.
  11. rust腐蚀 木制窗户怎么修_潜艇围壳上的窗户为什么不会裂开?
  12. ADO.Net 数据库访问技术
  13. 【生信进阶练习1000days】day11day12-GEO data mining
  14. Struts2+Spring2+Hibernate3配置(根据尚学堂马士兵老师的授课视频整理)
  15. python的数组下标_python数组下标
  16. LSF集群基本概念介绍
  17. メリッサ / 梅莉莎
  18. vue改变class内的属性_vue 绑定 添加class 属性 4种方法 添加style 3中方法 v-bind /:...
  19. 根据AD账号直接单点登录到第三方系统
  20. lumia 525 android 7.1,给大神跪了!诺基亚Lumia 520成功刷上安卓7.1

热门文章

  1. ts文件转js(亲测有效)
  2. html5 翻页第三方,谣言终止,NS使用第三方底座变砖真相揭晓
  3. 运用无限级分类管理数据库原理详解
  4. ROM、RAM、SRAM、DRAM、Flash、SDRAM区别
  5. java计算机毕业设计物流信息管理系统-源码+lw文档+系统+数据库
  6. iOS-苹果开发者账号申请之邓白氏编码查询
  7. C++中std的使用和作用
  8. HEC-RAS批处理的实现
  9. 软件构造复习总结(4)
  10. JAVA之IOC控制原理