本文旨在分享前端业务模块化开发的探索,鉴于本人知识水平所限,谬误在所难免,望各位不吝赐教…

为什么需要模块化开发

根据业务功能进行模块化一直以来都是后端的普遍做法,而Web前端则通常都是按照UI界面的视图区块View来进行模块化,这样的模块实际上只是Component组件,不具备独立自治的能力。然而在web生态高速发展的今天,浏览器越来越强大,赋能越来越多,前端从一个只负责数据渲染的瘦子,渐渐变成了一个大胖小子。这个时候我们需要回归一致的以业务为驱动的模块化视角。
从开发角度来说:我们需要高内聚、低耦合的松散结构体,而不是牵一发而动全身的巨石应用,这不管是对于开发、维护、还是后期渐进式重构,都至关重要。
从产品角度来说:软件架构永远是服务于业务需求的。我们希望我们的产品能像搭积木一样按需组合,可以快速包装出各种灵活多样的套餐,以满足客户越来越精细化的定制需求。
从工程的角度来说:模块化是跨工程、跨项目共享

前端模块化开发探索

前端模块化开发是一种将代码分成小块、互相独立的模块来开发前端应用程序的方法。通过使用模块化开发,可以使得前端代码更易于维护、测试、扩展和重用。下面将探讨几种常见的前端模块化开发的方法。

CommonJS

CommonJS是一种用于服务器端JavaScript的模块化规范。该规范定义了如何在JavaScript中编写可重用、可组合的代码,并且能够在Node.js中使用。CommonJS的模块化方案是通过export和require两个全局对象来实现的。其中,export对象用于向外部暴露模块的接口,而require对象用于加载其他模块的接口。
以下是一个使用CommonJS模块化规范的示例:

1
2
3
4
5
6
7
8
// math.js
exports.add = function(a, b) {
return a + b;
};

// main.js
const math = require('./math');
console.log(math.add(1, 2)); // 3

AMD

AMD(Asynchronous Module Definition,异步模块定义)是一种浏览器端JavaScript的模块化规范。它的主要特点是允许异步加载模块,从而提高页面的加载速度。AMD规范采用define函数来定义模块,并且使用require函数来加载模块。
以下是一个使用AMD模块化规范的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// math.js
define(function() {
return {
add: function(a, b) {
return a + b;
}
};
});

// main.js
require(['math'], function(math) {
console.log(math.add(1, 2)); // 3
});

ES6 模块

ES6 Modules是一种官方标准的JavaScript模块化方案。它允许将JavaScript代码分成独立的模块,并且支持静态分析、编译时优化和Tree Shaking等高级功能。ES6 Modules使用import和export语句来定义和加载模块。
以下是一个使用ES6 Modules的示例:

1
2
3
4
5
6
7
8
// math.js
export function add(a, b) {
return a + b;
}

// main.js
import { add } from './math';
console.log(add(1, 2)); // 3

总结来说,CommonJS适用于服务器端JavaScript,AMD适用于浏览器端JavaScript,而ES6 Modules是未来的趋势,它在现代浏览器中得到广泛支持,也被Node.js支持。选择哪种模块化方案,应该根据项目的需求和特点来选择。

前端划分模块的原则与边界

  • 拥有高内聚、低耦合的工程结构。
  • 拥有独立自治的子域逻辑。
  • 高内聚和低耦合是软件设计中常用的两个概念。

    高内聚(High Cohesion):指一个模块或组件内部的各个元素(如函数、属性等)之间紧密相关、协同工作,以实现模块或组件的某一特定功能。高内聚的模块或组件通常具有独立性、可重用性和易维护性,因为每个模块或组件只关注自己的任务,不与其他模块或组件产生过多的交互和依赖。
    低耦合(Low Coupling):指不同模块或组件之间的相互关联程度,也就是模块或组件之间的依赖程度。低耦合的模块或组件之间只有最小的相互依赖关系,彼此之间不会过度依赖或产生不必要的交互,从而提高了代码的可维护性和灵活性。

在软件设计中,高内聚和低耦合是非常重要的概念。一个好的设计应该追求高内聚和低耦合,以提高代码的可维护性、可重用性、可扩展性和灵活性。
首先我们看一下正常的项目目录

├─ src
│ ├─ api # API接口管理
│ ├─ assets # 静态资源文件
│ ├─ components # 全局组件
│ ├─ config # 全局配置项
│ ├─ enums # 项目枚举
│ ├─ hooks # 常用 Hooks
│ ├─ language # 语言国际化
│ ├─ layout # 框架布局
│ ├─ routers # 路由管理
│ ├─ store # store
│ ├─ styles # 全局样式
│ ├─ typings # 全局 ts 声明
│ ├─ utils # 工具库
│ ├─ views # 项目所有页面
│ ├─ App.vue # 入口页面
│ └─ main.js # 入口文件

再看一下改进的文件目录

src
├── modules
│ ├── ModuleA
│ │ ├── entities
│ │ ├── assets
│ │ ├── api
│ │ ├── utils
│ │ ├── language
│ │ ├── components
│ │ ├── views
│ │ ├── model.js
│ │ └── index.js
│ │
│ ├── ModuleB
│ ├── ModuleC
模块化是一种将代码分解成独立的、可重用的、可维护的小块的方法。在实践中,有几个原则和边界需要遵守,以确保模块化的有效性和可维护性。每个微模块负责定义和维护自己领域内的事务,拥有独立状态管理、数据模型、控制器、视图、组件、资源、业务实体、API管理等等…总之,所有与自己模块相关的资源都被内聚到了一起。

原则

单一职责原则

单一职责原则(Single Responsibility Principle,SRP)是指一个模块应该只有一个职责。这意味着模块应该只做一件事情,并且将其做好。如果模块做了太多的事情,那么它将变得难以维护和理解。

开放封闭原则

开放封闭原则(open-closed Principle,OCP)是指一个模块应该对扩展开放,对修改关闭。这意味着模块的行为应该能够扩展,但不应该修改现有的行为。如果模块需要进行修改,那么可能需要重构代码。

接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP)是指一个模块应该只暴露必要的接口。这意味着模块应该只提供必要的方法和属性,而不是暴露整个实现细节。这样做可以减少模块之间的耦合度,从而提高代码的可维护性。

依赖反转原则

依赖反转原则(Dependency Inversion Principle,DIP)是指模块之间的依赖关系应该是抽象的。这意味着一个模块应该依赖于抽象而不是具体的实现细节。这样做可以减少模块之间的耦合度,从而提高代码的可维护性。

边界

模块接口

模块接口是指一个模块向外部暴露的方法和属性。模块接口应该是清晰、简单、易于理解的。如果一个模块接口过于复杂,那么使用该模块的开发人员可能会感到困惑。

模块依赖

模块依赖是指一个模块依赖于其他模块的接口。如果一个模块依赖的模块过多,那么该模块可能会变得难以维护。

模块划分

模块划分是指如何将代码分解成模块。模块划分应该是基于单一职责原则,将代码分解成独立、可重用、可维护的小块。如果模块划分

模块化开发的具体实现

根据其不同的业务领域按照模块化结构进行拆分
将各资源按照模块的主题领域内聚在一起
拆解不同模块的view文件,防止耦合
模块内部资源不可随意调用,需建立导出

__END__