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

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

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 这两个工具来实现代码自动生成,这样就可以配合我们更好的实现前端代码复用了。

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

相关推荐
天天向上102412 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y28 分钟前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁34 分钟前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry35 分钟前
Fetch 笔记
前端·javascript
拾光拾趣录36 分钟前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟37 分钟前
vue3,你看setup设计详解,也是个人才
前端
Lefan41 分钟前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构
写不出来就跑路1 小时前
基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析
前端·vue.js·ui
OpenTiny社区1 小时前
盘点字体性能优化方案
前端·javascript