最近,关于前端代码复用的一点思考

前端代码复用一直是一个很重要的话题,也是一个很难的话题。在前端开发中,我们经常会遇到很多重复的代码,比如说,我们经常会在不同的页面中使用相同的组件,或者是相同的功能。这个时候,我们就需要考虑如何将这些重复的代码进行复用。在这篇文章中,我将会和大家分享一些前端代码复用的精髓。

1. 组件复用

我们在 GitHub 上可以找到很多优秀的前端组件库,比如说 Ant Design、Element UI、Vant 等等。这些组件库都提供了很多优秀的组件,不难发现,这些个组件并不是一朝一夕一次性推出来的,一定都是经过了很多版本迭代,很多人的使用和测试的,而且是非常通用的,比如,按钮,选择框,数据库,表单,dialog等等。这些组件,我们可以直接拿来使用。这些组件库的优势在于,它们提供了很多现成的组件,我们可以直接拿来使用,而且这些组件库都是经过了很多人的使用和测试的,所以它们的质量是非常有保障的。这样的方式其实就是一种组件复用的方式。

那么,回归到我们自己的项目中,我们应该如何进行组件复用呢?其实,我们可以将一些通用的组件进行封装,然后在需要的地方进行引用。比如说,我们可以将一些通用的表单组件进行封装,然后在需要的地方进行引用。这样的方式可以大大提高我们的开发效率,而且也可以减少我们的代码量。

举一个例子,比如说我们有一个通用的联系人组件,可能很多个页面都会用到这个组件,这个时候我们就可以将这个组件进行封装,然后在需要的地方进行引用。这样的方式可以大大提高我们的开发效率,而且也可以减少我们的代码量,而且也可以减少我们的维护成本。一个可能的代码示例如下:

jsx 复制代码
import React from 'react';
import { Input, Button } from 'antd';
//通用的联系人函数式组件
//渲染一个联系人面板,包含姓名、电话、身份证号等信息,敏感信息自动打码

function Contact(props) {
  const { name, phone, idCard } = props;
  //敏感信息打码
  const maskIdCard = idCard => {
    return idCard.replace(/(\d{4})\d+(\d{4})$/, '$1****$2');
  };
  return (
    <div className="contact">
      <div className="name">姓名:{name}</div>
      <div className="phone">电话:{phone}</div>
      <div className="idCard">身份证号:{maskIdCard(idCard)}</div>
      {if (props.children) {
        <div className="divider"></div>
        <div className="extra">{props.children}</div>
      }}
    </div>
  );
}

export default Contact;

2. 逻辑复用

实际上,在前端领域中,很多业务因为其复杂的交互,在如PC和移动端的一些操作会存在较大的差异,因此在前端组件上做复用可能会比较不现实,即便强行做了组件复用,也会导致组件的复杂度增加,维护成本增加。

哪怕是目前流行的前端框架,也无法完全解决这个问题。有人会说 比如 taro 或者 uni-app不就解决了一套代码解决了多端问题吗?但是实际上,这些框架也是通过一套代码生成多端代码,而不是真正的逻辑复用。

真正到了要写pc端页面和移动端页面的时候,我们就会发现,很多界面组件是无法复用的。比如说,我们在移动端页面中可能会有一些滑动操作,而在 PC 端页面中可能会有一些点击操作,另外pc端的本身可用空间比较多,一屏显示的内容比较多,而移动端的本身可用空间比较少,一屏显示的内容比较少,在布局上也会有很大的差异。

就比如,uni-app开发App和小程序,实际上也很难做到逻辑复用,因为App和小程序的交互方式是不一样的,App是原生的交互方式,两者在底层能力上都会有一些不同,因此我接触过的多数团队,都是App和小程序分别些一个页面,举一个例子,一个页面的目录结构可能是这样的,他会有严格的区分 app 还是 miniprogram,还是h5的

shell 复制代码
├── src
│   ├── pages
│   │   ├── HomePage
│   │   │   ├── app.vue  // 为App编写的代码
│   │   │   ├── miniprogram.vue  // 为小程序编写的代码
│   │   │   ├── h5.vue  // 为H5编写的代码
│   │   │   ├── index.vue  // 通用的代码
│   ├── models
│   ├── services
│   ├── utils
├── package.json
├── tsconfig.json
├── README.md

其中,index.vue 代码示例如下:

vue 复制代码
<template>
  <div>
    <component :is="currentComponent" />
  </div>
</template>

<script>
import app from './app.vue';
import miniprogram from './miniprogram.vue';
import h5 from './h5.jsx';

export default {
  data() {
    return {
      currentComponent: null
    };
  },
  created() {
    // 根据当前的运行环境选择组件
    if (__PLATFORM__ === 'app') {
      this.currentComponent = app;
    } else if (__PLATFORM__ === 'miniprogram') {
      this.currentComponent = miniprogram;
    } else if (__PLATFORM__ === 'h5') {
      this.currentComponent = h5;
    }
  }
};
</script>

因此,我们看到在一些复杂的业务逻辑场景,尤其是交互差异形势巨大的时候,一套代码解决多端问题是不现实的,往往,实现上应该是多套代码,准确来说是一种编码语言,多端分开实现。因此我们需要把精力放在逻辑上做复用上。

虽然在前端界面上,做到前端交互代码复用可能实施难度比较大,甚至在一些场景上不大现实,但是在逻辑复用上,我们还是可以做到的。比如说,我们可以将一些通用的逻辑进行封装,然后在需要的地方进行引用。比如说,我们可以将一些通用的请求逻辑进行封装,然后在需要的地方进行引用。这样的方式可以大大提高我们的开发效率,而且也可以减少我们的代码量。

下面,我们来说说业务逻辑复用的姿势,其实在前端研发领域,我们或多或少接触过一些设计模式,比如 MVC,MVVM,MVP,MVI 等等。这些设计模式都是为了解决一些通用的问题,比如说,MVC 是为了解决数据和视图的分离问题,MVVM 是为了解决数据和视图的双向绑定问题,MVP 是为了解决视图和业务逻辑的分离问题,MVI 是为了解决视图和状态的分离问题。

下面分别介绍一下这几种设计模式:

  • MVC:MVC 是 Model-View-Controller 的缩写,它是一种软件架构模式,它将软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。MVC 模式的核心是模型、视图、控制器三个部分之间的交互。

其架构图使用mermaid语法描述如下:

graph TD A[Model] -->|数据| B[Controller] B -->|指令| C[View] C -->|用户输入| B
  • MVVM:MVVM 是 Model-View-ViewModel 的缩写,它是一种软件架构模式,它是 MVC 模式的一种变种。MVVM 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。MVVM 模式的核心是模型、视图、视图模型三个部分之间的交互。

其架构图使用mermaid语法描述如下:

graph TD A[Model] -->|数据| B[ViewModel] B -->|数据绑定| C[View] C -->|用户输入| B
  • MVP:MVP 是 Model-View-Presenter 的缩写,它是一种软件架构模式,它是 MVC 模式的一种变种。MVP 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。MVP 模式的核心是模型、视图、控制器三个部分之间的交互。

其架构图使用mermaid语法描述如下:

graph TD A[Model] -->|数据| B[Presenter] B -->|指令| C[View] C -->|用户输入| B
  • MVI:MVI 是 Model-View-Intent 的缩写,它是一种软件架构模式,它是 MVC 模式的一种变种。MVI 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。MVI 模式的核心是模型、视图、意图三个部分之间的交互。

其架构图使用mermaid语法描述如下:

graph TD A[Model] -->|数据| B[Intent] B -->|指令| C[View] C -->|用户输入| B

今天要说的这个前端业务逻辑复用,其实可以参考或者直接使用上述一些模式,比如MVP,我们专注于打造通用的M层和P层,然后在不同的页面中引用这些通用的M层和P层,这样就可以实现逻辑复用。这样的方式可以大大提高我们的开发效率,而且也可以减少我们的代码量。

那么,具体点,我们怎么去实施呢?假设我们现在有三个端:

  • 小程序
  • H5
  • PC

我们如何打造这样的通用的M层和P层呢?这就比较考验我们对业务的抽象能力了,我们需要将业务逻辑进行抽象,然后将这些抽象的业务逻辑进行封装,然后在不同的页面中引用这些抽象的业务逻辑。我们也许需要糅合一些设计模式,比如说,我们可以使用观察者模式,将一些通用的业务逻辑进行封装,然后在不同的页面中引用这些通用的业务逻辑。我们也需要遵循一些设计原则,我觉得可能最重要的是单一职责原则,我们需要将业务逻辑进行抽象,然后将这些抽象的业务逻辑进行封装,然后在不同的页面中引用这些抽象的业务逻辑。另外就是开闭原则,我们需要将业务逻辑进行抽象,然后将这些抽象的业务逻辑进行封装,然后在不同的页面中引用这些抽象的业务逻辑。

ok,扯了这么多,有点口干舌燥了,准备开始动手写代码了,下面我们来看一个具体的例子,比如说,我们现在有一个企业用户管理流。

这个里面可能设计到的业务逻辑有:

  • 企业认证
  • 个人用户实名认证
  • 法人授权
  • 员工管理
  • 权限审批
  • 企业信息管理
  • 联系人管理
  • 印章管理

等等,就不写下去了。接下来我们就上述的业务逻辑进行抽象,然后将这些抽象的业务逻辑进行封装,然后在不同的页面中引用这些抽象的业务逻辑。我们的目标是做出这个M 层,注意这个层几乎完全是使用typescript实现的,不依赖任何前端框架,这样子即便你小程序使用 uni-app开发使用的是vue,h5使用的是react,pc使用的是angular,也可以使用这个M层。

shell 复制代码
├── src
│   ├── models
│   │   ├── EnterpriseUserManager.ts  // 你的M层类
│   │   ├── index.ts  // 导出所有的models
│   ├── services
│   │   ├── authService.ts  // 包含企业认证、个人用户实名认证等方法的服务
│   │   ├── userService.ts  // 包含员工管理、权限审批等方法的服务
│   │   ├── enterpriseService.ts  // 包含企业信息管理、联系人管理等方法的服务
│   │   ├── sealService.ts  // 包含印章管理的方法的服务
│   │   ├── index.ts  // 导出所有的services
│   ├── utils
│   │   ├── api.ts  // 包含API调用的工具函数
│   │   ├── index.ts  // 导出所有的utils
│   ├── app.ts  // 主应用文件
│   ├── index.ts  // 应用入口文件
├── package.json  // npm包管理文件
├── tsconfig.json  // TypeScript配置文件
├── README.md  // 项目说明文件
typescript 复制代码
class EnterpriseUserManager {
  // 这些属性可能需要根据你的业务需求进行调整
  private enterpriseId: string;
  private userId: string;

  constructor(enterpriseId: string, userId: string) {
    this.enterpriseId = enterpriseId;
    this.userId = userId;
  }

  // 企业认证
  async enterpriseCertification(certificationInfo: any): Promise<any> {
    // 这里应该包含实际的认证逻辑
  }

  // 个人用户实名认证
  async individualCertification(certificationInfo: any): Promise<any> {
    // 这里应该包含实际的认证逻辑
  }

  // 法人授权
  async legalPersonAuthorization(authorizationInfo: any): Promise<any> {
    // 这里应该包含实际的授权逻辑
  }

  // 员工管理
  async manageEmployee(employeeInfo: any): Promise<any> {
    // 这里应该包含实际的员工管理逻辑
  }

  // 权限审批
  async permissionApproval(approvalInfo: any): Promise<any> {
    // 这里应该包含实际的审批逻辑
  }

  // 企业信息管理
  async manageEnterpriseInfo(info: any): Promise<any> {
    // 这里应该包含实际的企业信息管理逻辑
  }

  // 联系人管理
  async manageContact(contactInfo: any): Promise<any> {
    // 这里应该包含实际的联系人管理逻辑
  }

  // 印章管理
  async manageSeal(sealInfo: any): Promise<any> {
    // 这里应该包含实际的印章管理逻辑
  }
}

export default EnterpriseUserManager;

然后,我在我的业务页面中引用这个M层,比如说,我在我的企业认证流中的页面引入这个M层,他的小程序vue,和h5 react端的代码可能是这样的:

小程序端

vue 复制代码
<template>
  <view>
    <!-- 你的页面组件 -->
  </view>
</template>

<script>
import { EnterpriseUserManager } from '@/models';

export default {
  data() {
    return {
      enterpriseUserManager: null
    };
  },
  created() {
    this.enterpriseUserManager = new EnterpriseUserManager('enterpriseId', 'userId');
    // 使用enterpriseUserManager进行企业认证
    this.enterpriseUserManager.enterpriseCertification(certificationInfo)
      .then(response => {
        // 处理认证成功
      })
      .catch(error => {
        // 处理认证失败
      });
  }
};
</script>

H5端

jsx 复制代码
import React, { useEffect } from 'react';
import { EnterpriseUserManager } from '@/models';

function EnterpriseCertificationPage() {
  useEffect(() => {
    const enterpriseUserManager = new EnterpriseUserManager('enterpriseId', 'userId');
    // 使用enterpriseUserManager进行企业认证
    enterpriseUserManager.enterpriseCertification(certificationInfo)
      .then(response => {
        // 处理认证成功
      })
      .catch(error => {
        // 处理认证失败
      });
  }, []);

  return (
    // 你的页面组件
  );
}

export default EnterpriseCertificationPage;

前端页面关注的是视图的渲染,而不是业务逻辑的处理,这样的方式可以大大提高我们的开发效率,维护行提高也是不言而喻的。

代码自动生成

我们在实践代码复用的时候,发现一个问题,那就是代码规范问题,具体按照什么样的模式来写代码,才能方便后续的这个业务逻辑能够被复用到多个端,我们可能需要一个标准的模板,定义出一套复用的框架,然后业务逻辑的开发者只需要按照这个模板来写代码,然后就可以实现业务逻辑的复用。

那么,具体的实操,我们该如何做呢?我们可以使用 Yeoman和VS Code Extension Generator 这两个工具来实现代码自动生成。Yeoman 是一个用于生成项目模板的工具,VS Code Extension Generator 是一个用于生成 VS Code 插件的工具。这者配合起来做这个事情,简直太合适不过,想一想,右键对着文件夹,点击生成代码,然后就生成了一套标准的业务逻辑代码框架,然后研发小伙伴只需要按照这个框架来写代码,就可以实现业务逻辑的复用了。

下面是一个 Yeoman 的模板示例:

shell 复制代码
├── generators
│   ├── app
│   │   ├── index.js
│   │   ├── templates
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   ├── miniProgram.js
│   │   │   ├── h5.js
│   │   │   ├── pc.js
│   │   │   ├── README.md
│   │   │   ├── package.json
│   │   │   ├── .gitignore
├── package.json

一个好的可复用的业务逻辑模块,是需要配置自动化测试的,这样可以保证业务逻辑的稳定性,也可以保证业务逻辑的可维护性。index.js里面在设计是,需要遵循设计模式的一些原则,尽量面向接口编程,而不是面向实现编程,这样可以保证业务逻辑的可扩展性,也可以保证业务逻辑的可维护性。

这里,每个每块的外部依赖可能,不太一样,需要处理的业务逻辑也不太一样,如何设计这个模板才比较优雅呢?这里肯定是需要一些业务逻辑的抽象,然后将这些抽象的业务逻辑进行封装,进行模块间的连接,注意,一定是抽象,具体的实现应该交给业务逻辑的开发者来实现,如果存在端的差异,这块不抽象肯定是不行的。

这个是个 templates/index.js 的模板示例,我们这里给出一个MVP层里面 M 层的模板示例:

javascript 复制代码
// 抽象策略类
class AbstractStrategy {
  validateData(data) {
    // 通用的数据验证逻辑
  }

  handleError(error) {
    // 通用的错误处理逻辑
  }

  log(message) {
    // 通用的日志记录逻辑
  }

// 有明显差一点可以写一个抽象,具体在不同平台端 中实现
  businessLogic(data) {
    throw new Error('This method must be overridden');
  }
}

class MiniProgramStrategy extends AbstractStrategy {
  businessLogic(data) {
    // 小程序的业务逻辑
    // 可以使用 this.validateData, this.handleError, this.log
  }
}

class H5Strategy extends AbstractStrategy {
  businessLogic() {
    // H5的业务逻辑
  }
}

class PCStrategy extends AbstractStrategy {
  businessLogic() {
    // PC的业务逻辑
  }
}

// 工厂类
class StrategyFactory {
  static getStrategy(platform) {
    switch (platform) {
      case 'miniProgram':
        return new MiniProgramStrategy();
      case 'h5':
        return new H5Strategy();
      case 'pc':
        return new PCStrategy();
      default:
        throw new Error(`No strategy found for platform ${platform}`);
    }
  }
}

// 使用策略
const platform = 'miniProgram'; // 这个值可能来自用户的输入或者配置文件
const strategy = StrategyFactory.getStrategy(platform);
strategy.businessLogic();

下面是一个 VS Code Extension Generator 的模板示例:

shell 复制代码
├── src
│   ├── extension.ts
│   ├── test
│   │   ├── extension.test.ts
├── package.json

其中 Yeoman里面templates里面的文件就是生成的代码的模板,extension.ts 生成代码实际上就是node fs来基于模板生成代码。

总结

感觉,这是最近关于前端代码复用性的一些思考,前端代码复用是一个很重要的话题,是一个不能回避的问题,也是一个很难的问题。但是虽难,总会找到一些突破口,比如,我们可以把整体进行拆分,发现逻辑层是可以做比较大规模的复用的,然后我们可以使用一些设计模式,比如MVP,来实现逻辑复用,然后我们可以使用 Yeoman 和 VS Code Extension Generator 这两个工具来实现代码自动生成,这样就可以配合我们更好的实现前端代码复用了。

关注我的公众号 老码沉思录,每天推送前端技术干货,带你深度解读前端技术,让你成为技术高手。

相关推荐
逐·風43 分钟前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫1 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦2 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子2 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山3 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享3 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
Dann Hiroaki4 小时前
GPU架构概述
架构
茶馆大橘4 小时前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
清灵xmf5 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
coding侠客5 小时前
揭秘!微服务架构下,Apollo 配置中心凭啥扮演关键角色?
微服务·云原生·架构