软件编程有一个重要的原则是 D.R.Y(Don't Repeat Yourself),讲的是尽量复用代码和逻辑,减少重复。组件扩展可以避免重复代码,更易于快速开发和维护。那么,扩展 Vue 组件的最佳方法是什么?

Vue 提供了不少 API 和模式来支持组件复用和扩展,你可以根据自己的目的和偏好来选择。

本文介绍几种比较常见的方法和模式,希望对你有所帮助。


扩展组件是否必要

扩展往往通过继承基础组件来达到功能复用的目的。要知道,所有的组件扩展方法都会增加复杂性和额外代码,有时候还会增加性能消耗。经验告诉我们,组合模式优于继承。

因此,在决定扩展组件之前,最好先看看有没有其他更简单的设计模式能完成目标。

下面几种模式通常足够替代扩展组件了:

  • props 配合模板逻辑

  • slot 插槽

  • JavaScript 工具函数

props 配合模板逻辑

最简单的方法是通过props结合模板条件渲染,来实现组件的多功能。

比如通过 type 属性:MyVersatileComponent.vue

<template>  <div class="wrapper">    <div v-if="type === 'a'">...div>    <div v-else-if="type === 'b'">...div>

  div>template><script>export default {props: { type: String },  ...}script>

使用组件的时候传不同的type值就能实现不同的结果。

// *ParentComponent.vue*<MyVersatileComponent type="a" /><MyVersatileComponent type="b" />template>

如果出现下面两种情况,就说明这种模式不适用了,或者用法不对:

  1. 组件组合模式把状态和逻辑分解成原子部分,从而让应用具备可扩展性。如果组件内存在大量条件判断,可读性和可维护性就会变差。

  2. props 和模板逻辑的本意是让组件动态化,但是也存在运行时资源消耗。如果你利用这种机制在运行时解决代码组合问题,那是一种反模式。

slot(插槽)

另一种可避免组件扩展的方式是利用 slots(插槽),就是让父组件在子组件内设置自定义内容。

// *MyVersatileComponent.vue*<template>  
class="wrapper">

Common markup


    
  

// *ParentComponent.vue*<template>  <MyVersatileComponent>    <h4>Inserting into the sloth4>  MyVersatileComponent>template>

渲染结果:

<div class="wrapper">  <h3>Common markupdiv>  <h4>Inserting into the sloth4>div>

这种模式有一个潜在约束, slot 内的元素从属于父组件的上下文,在拆分逻辑和状态时可能不太自然。scoped slot会更灵活,后面会在无渲染组件一节里提到。

JavaScript 工具函数

如果只需要在各组件之间复用独立的函数,那么只需要抽取这些 JavaScript 模块就行了,根本不需要用到组件扩展模式。

JavaScript 的模块系统是一种非常灵活和健壮的代码共享方式,所以你应该尽可能地依靠它。MyUtilityFunction.js

export default function () {  ...}

MyComponent.vue

import MyUtilityFunction from "./MyUtilityFunction";export default {  methods: {    MyUtilityFunction  }}

扩展组件的几种模式

如果你已经考虑过以上几种简单的模式,但这些模式还不够灵活,无法满足需求。那么就可以考虑扩展组件了。

扩展 Vue 组件最流行的方法有以下四种:

  • Composition API

  • mixin

  • 高阶组件(HOC)

  • 无渲染组件

每一种方法都有其优缺点,根据使用场景,或多或少都有适用的部分。

Composition API

组件之间共享状态和逻辑的最新方案是 Composition API。这是 Vue 3 推出的 API,也可以在 Vue 2 里当插件使用。

跟之前在组件定义配置对象里声明datacomputedmethods等属性的方式不同,Composition API 通过一个 setup 函数声明和返回这些配置。

比如,用 Vue 2 配置属性的方式声明 Counter 组件是这样的:Counter.vue

<template>  <button @click="increment">    Count is: {{ count }}, double is: {{ double }}  button><template><script>export default {data: () => ({count: 0  }),methods: {    increment() {this.count++;    }  },computed: {    double () {return this.count * 2;    }  }}script>

用 Composition API 重构这个组件,功能完全一样:Counter.vue

<template><template><script>import { reactive, computed } from "vue";export default {  setup() {const state = reactive({count: 0,double: computed(() => state.count * 2)    });function increment() {      state.count++    }return {      count,      double,      increment    }  }}script>

用 Composition API 声明组件的主要好处之一是,逻辑复用和抽取变得非常轻松。

进一步重构,把计数器的功能移到 JavaScript 模块 useCounter.js中:useCounter.js

import { reactive, computed } from "vue";

export default function {  const state = reactive({    count: 0,    double: computed(() => state.count * 2)  });

  function increment() {    state.count++  }

  return {    count,    double,    increment  }}

现在,计数器功能可以通过setup函数无缝引入到任意 Vue 组件中:MyComponent.vue

<template>template><script>import useCounter from "./useCounter";export default {  setup() {const { count, double, increment } = useCounter();return {      count,      double,      increment    }  }}script>

Composition 函数让功能模块化、可重用,是扩展组件最直接和低成本的方式。

Composition API 的缺点

Composition API 的缺点其实不算什么——可能就是看起来有点啰嗦,并且新的用法对一些 Vue 开发者来说有点陌生。新技术总有个适应的过程,迟早会大面积应用。

mixin

如果你还在用 Vue 2,或者只是喜欢用配置对象的方式定义组件功能,可以用 mixin 模式。mixin 把公共逻辑和状态抽取到单独的对象,跟使用 mixin 的组件内部定义对象合并。

我们继续用之前的Counter组件例子,把公共逻辑和状态放到CounterMixin.js模块中。CounterMixin.js

export default {  data: () => ({    count: 0  }),  methods: {    increment() {      this.count++;    }  },  computed: {    double () {      return this.count * 2;    }  }}

使用 mixin 也很简单,只要导入对应模块并在mixins数组里加上变量就行。组件初始化时会把 mixin 对象与组件内部定义对象合并。MyComponent.vue

import CounterMixin from "./CounterMixin";

export default {  mixins: [CounterMixin],  methods: {    decrement() {      this.count--;    }  }}

选项合并

如果组件内的选项跟 mixin 冲突怎么办?

比如,给组件定义一个自带的increment 方法,哪个优先级更高呢?MyComponent.vue

import CounterMixin from "./CounterMixin";

export default {  mixins: [CounterMixin],  methods: {    // 自带的 `increment`` 方法会覆盖 mixin 的`increment` 吗?    increment() { ... }  }}

这个时候就要说到 Vue 的合并策略了。Vue 有一系列的规则,决定了如何处理同名选项。

通常,组件自带的选项会覆盖来自 mixin 的选项。但也有例外,比如同类型的生命周期钩子,不是直接覆盖,而是都放进数组,按顺序执行。

你也可以通过 自定义合并策略 改变默认行为。

mixin 的缺点

作为扩展组件的一种模式,mixin 对于简单的场景还算好用,一旦规模扩大,问题就来了。不仅需要注意命名冲突问题(尤其是第三方 mixin),使用了多个 mixin 的组件,很难搞清楚某个功能到底来自于哪里,定位问题也比较困难。

高阶组件

高阶组件(HOC)是从 React 借用的概念,Vue 也能使用。

为了理解这个概念,我们先抛开组件,看看两个简单的 JavaScript 函数,increment 和 double

function increment(x) {  return x++;}

function double(x) {  return x * 2;}

假设我们想给这两个函数都加一个功能:在控制台输出结果。

为此,我们可以用高阶函数模式,新建一个 addLogging函数,接受函数作为参数,并返回一个带有新增功能的函数。

function addLogging(fn) {  return function(x) {    const result = fn(x);    console.log("The result is: ", result);    return result;  };}

const incrementWithLogging = addLogging(increment);const doubleWithLogging = addLogging(double);

组件如何利用这种模式呢?类似地,我们创建一个高阶组件来渲染Counter组件,同时添加一个decrement方法作为实例属性。

实际代码比较复杂,这里只给出伪代码作为示意:

import Counter from "./Counter";

// 伪代码const CounterWithDecrement => ({  render(createElement) {    const options = {      decrement() {        this.count--;      }    }    return createElement(Counter, options);  }});

HOC 模式比 mixin 更简洁,扩展性更好,但是代价是增加了一个包裹组件,实现起来也需要技巧。

无渲染组件

如果需要在多个组件上使用相同的逻辑和状态,只是展示方式不同,那么就可以考虑无渲染组件模式。

该模式需要用到两类组件:逻辑组件用于声明逻辑和状态,展示组件用于展示数据。

逻辑组件

还是回到Counter的例子,假设我们需要在多个地方重用这个组件,但是展示方式不同。

创建一个CounterRenderless.js 用于定义逻辑组件,包含逻辑和状态,但是不包含模板,而是通过 render函数声明 scoped slot

scoped slot暴露三个属性给父组件使用:状态count,方法increment 和计算属性 doubleCounterRenderless.js

export default {  data: () => ({    count: 0  }),  methods: {    increment() {      this.count++;    }  },  computed: {    double () {      return this.count * 2;    }  },  render() {    return this.$scopedSlots.default({      count: this.count,      double: this.double,      increment: this.toggleState,    })  }}

这里的scoped slot是这种模式里逻辑组件的关键所在。

展示组件

接下来是展示组件,作为无渲染组件的使用方,提供具体的展示方式。

所有的元素标签都包含在scoped slot里。可以看到,这些属性在使用上跟模板直接放在逻辑组件里没什么两样。CounterWithButton.vue

<template>  <counter-renderless slot-scope="{ count, double, increment }">    <div>Count is: {{ count }}div>     <div>Double is: {{ double }}div>    <button @click="increment">Incrementbutton>  counter-renderless>template><script>import CounterRenderless from "./CountRenderless";export default {components: {    CounterRenderless  }}script>

无渲染组件模式非常灵活,也容易理解。但是,它没有前面那几种方法那么通用,可能只有一种应用场景,那就是用于开发组件库。

模板扩展

上面的 API 也好,设计模式也罢,都有一种局限性,就是无法扩展组件的模板。Vue 在逻辑和状态方面有办法重用,但是对于模板标签就无能为力了。

有一种比较 hack 的方式,就是利用 HTML 预处理器,比如 Pug,来处理模板扩展。

第一步是创建一个基础模板.pug文件,包含公共的页面元素。还要包含一个 block input ,作为模板扩展的占位符。

BaseTemplate.pug

div.wrapper  h3 {{ myCommonProp }}   block input 

为了能扩展这个模板,需要安装 Vue Loader 的 Pug 插件。然后就可以引入基础模板并利用block input语法替换占位部分了:MyComponent.vue

<template lang="pug">  extends BaseTemplate.pug  block input    h4 {{ myLocalProp }} template>

一开始你可能会认为它跟 slot 的概念是一样的,但是有个区别,这里的基础模板不属于任何单独的组件。它在编译时跟当前组件合并,而不是像 slot 那样是在运行时替换。

参考资料:

  • https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html

  • https://adamwathan.me/renderless-components-in-vuejs/

其他推荐

你不知道的 Web Workers (上)

JS执行上下文的两个阶段做了些啥?

Vue3 的响应式和以前有什么区别?(源码级详解)

学会这个套路,彻底掌握排列组合。【会点算法的前端更早下班】

实战vue-ssr服务端渲染的单页应用

Vue3 尝鲜 Hook + TypeScript 取代 Vuex 实现图书管理小型应用

应用动态规划和贪心算法高效实现瀑布流布局

React 和 Vue 都在用的 FLIP 思想是什么?

懂这些JavaScript的骚操作,你可以更牛B

Vue3 的诞生的故事

从零解读Vue3.0源码响应式系统

如何使用Chrome来分析运行时的性能、内存问题 ?[太实用了]

超有料的前端性能优化总结(建议收藏)

CSS高级特效的必备技巧

图文并茂深度解析浏览器渲染原理,包看懂超值得收藏

Vue 的生命周期之间到底做了什么事清?(源码详解)

如何写出被Team Leader喜欢的JS 代码

前端面试128问汇总(含超详细答案)超详细webpack构建方式补给!超详细webpack基础补给!当你升级到前端的TL时,如何快速打造用于中小团队的前端基建线上项目出现问题时如何快速调试定位问题?你确定你真的懂CSS吗?太齐了!前端的学习及工作资料只收藏这份就够了!大厂都在用的高级缓存方案我这个页面居然用了10G的GPU?!!原来微信支付软件架构是这样哒!!!高级前端工程师是怎样高效部署前端应用?【撩妹教程】如何教公司新来的女实习生小姐姐什么是闭包?前端如何在繁忙的业务中提升自己大厂的高性能小程序原来是这样弄的!测试一下你离前端专家这个称号还有多远?

点在看的人特别帅/美

login组件的两种用法_Vue.js 组件该如何正确的复用和扩展相关推荐

  1. 19、angular1之pass-word组件、input-select组件 、only-select组件(两种)、on-off组件、layui中的datetime示例、京东购物车、两种作用域绑定、

    19.angular1之pass-word组件.input-select组件 .only-select组件(两种).on-off组件.layui中的datetime示例.京东购物车.两种作用域绑定.两 ...

  2. login组件的两种用法_vue2组件系列第四十节:NoticeBar 通告栏

    NoticeBar通造栏一般会出现在顶部或者是比较显眼的地方,目的就是引大家的注意. 准备工作: 创建一个页面:NoticeBar.vue 在router.js里配置NoticeBar页面的路由 { ...

  3. login组件的两种用法_Android-模块化、组件化、插件化、热修复-组件化-组件间的通信(本地,下沉,bus,路由)...

    延续上一篇 MonkeyLei:Android-模块化.组件化.插件化.热修复-组件化工程构建+页面路由多种方式实践 ,我们进行搞下组件之间的通信.比如登录成功后怎么通知其他页面刷新: 方式可能有很多 ...

  4. 帆软BI中界面上如果要替换原有的组件,两种替换方式

    通过查看源码js得知,BI中的组件有两种书写方式,第一种就是类似: var He = function (e) {var t, i;function n() {};i = e;t = n;t.prot ...

  5. TypeScript与Haxe:两种截然不同的JS转译工具横向对比

    转自:TypeScript与Haxe:两种截然不同的JS转译工具横向对比 JavaScript无疑是当今最火爆的编程语言之一,它的崛起要归功于AJAX.Node.js的出现以及时下各种MVC框架的流行 ...

  6. Vue渲染组件的两种方式

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

  7. C++ operator两种用法【转】

    C++中的operator,有两种用法,一种是operator overloading(操作符重载),一种是operator casting(操作隐式转换).下面分别进行介绍: 1.operator ...

  8. html如何让a标签提交表单提交,html post请求之a标签的两种用法解析

    这篇文章主要介绍了html post请求之a标签的两种用法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧 html post请求之a标签的两种用法 ...

  9. html post举例,html post请求之a标签的两种用法举例

    html post请求之a标签的两种用法举例 1.使用ajax来发起POST请求 HTML代码如下:发起POST请求a> JQuery代码如下:$(".a_post").on ...

  10. java sort 第二个参数_详解java Collections.sort的两种用法

    Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序的,它有两种参数形式: public static > void sort(List list) { l ...

最新文章

  1. php 做的网页 排版错误,discuz 帖子排版显示出错
  2. python urllib的用法实例
  3. SrsAutoFree模式,避免内存泄漏和错误
  4. 上海区块链会议演讲ppt_进行第一次会议演讲的完整指南
  5. matlab zigzag算法,ZIGZAG扫描的MATLAB实现
  6. 《软件需求分析(第二版)》第 1 章——软件需求基础知识 重点部分总结
  7. python的except之后还运行吗_python except异常处理之后不退出,如何解决异常继续执行...
  8. MYSQL 横向展示数据
  9. 性能测试场景设计--混合业务场景下的脚本比例控制
  10. 欧拉工程第60题:Prime pair sets
  11. numpy.mgrid的用法图解
  12. Eclipse常用插件下载
  13. Photoshop 2019 破解
  14. 智能驾驶场景库设计方法-V2X
  15. 删除Mac右上角可恶的状态栏图标
  16. CSS入门(狂神学习笔记)
  17. 模拟卷Leetcode【普通】198. 打家劫舍
  18. 20200220 MFC之列表控件技术总结 CListCtrl (一)
  19. 【概率论与数理统计】猴博士 笔记 p36-37 协方差、相关系数、不相关、相互独立时的期望和方差
  20. java+selenium自动化抓取51la数据

热门文章

  1. 技术人 | 为什么我们的系统会如此复杂?
  2. Kubernetes迁移指北 | 凌云时刻
  3. 毕设题目:Matlab语音识别
  4. 【TSP】基于matlab粒子群算法求解旅行商问题【含Matlab源码 445期】
  5. ffmpeg drawtext 背景_8款电视背景墙:电视背景墙这样装,不仅省钱还作用多!效果大不一样!...
  6. r语言 生成等差序列_使用序列模型生成自然语言
  7. ibm watson_使用IBM Watson Assistant构建AI私人教练-第1部分
  8. 词嵌入生成词向量_使用词嵌入创建诗生成器
  9. 循环神经网络 递归神经网络_递归神经网络-第3部分
  10. jquery多维对象计算个数_山东省2005年专升本计算机考试真题2??