如何管理好10万行代码的前端单页面应用

做者简介 导演 蚂蚁金服·数据体验技术团队前端

蚂蚁金服数据平台前端团队主要负责多个数据相关的PC Web单页面应用程序,业务复杂度类比Excel等桌面应用,业务前端代码量在几万行~几十万行,随着产品不断完善,破百万指日可待。管理好10万行级甚至百万行级代码的前端应用,是咱们团队的核心挑战之一。react

接下来的系列文章,我会尝试从如下几个角度介绍咱们团队应对挑战的方法:git

  • 前端架构
  • 质量保障
  • 性能优化
  • 团队前端开发流程
  • 人员素养

前端架构

团队的架构方案是多个产品经历一年的持续迭代,不断摸索出来的一套适合本团队数据产品业务场景的架构方案,架构方案中还存在还没有解决的痛点和有争议的部分须要持续优化,不保证这套架构适合您的产品。github

产品特色

先介绍下咱们团队的产品特色:数据库

  • ToB产品,业务复杂度高、业务理解门槛高;
  • 前端代码量巨大(数据分析产品从零开始经历8个月迭代业务代码8万行,仅实现了产品长期规划需求的20%)

架构方案

架构的目的是管理复杂度,将复杂问题分而治之、有效管理,咱们的具体方法以下:redux

1. 首先经过路由切割“页面级”粒度的功能模块

这里的“页面级”粒度指一个路由映射的组件设计模式

router

2. 同一“页面”内的模块再划分

划分原则:api

  • 纵向:经过业务功能(可根据视图模块判断)划分
  • 横向:经过Model-View-Controller三种不一样职能划分

module

3. 合并同类项

继续细分粒度,而后将可复用模块或组件抽离到公共区域性能优化

3.1 数据模型

数据模型根据职责分红两类:架构

  1. Domain Model 领域模型
  2. App State Modal 应用状态模型
3.1.1 领域模型

领域模型是业务数据,每每要持久化到数据库或localStorage中,属于可跨模块复用的公共数据,如:

  • Users 用户信息
  • Datasets 数据集信息
  • Reports 报表信息

领域模型做为公共数据,建议统一存放在一个叫作Domain Model Layer的架构独立分层中(前端业界通常对这层的命名为ORM层)。

下沉到Domain Model Layer(领域模型层)有诸多利处:

  • 跨模块数据同步问题不复存在,例如:以前Users对象在A和B两个业务模块中单独存储,A模块变动Users对象后,需将Users变动同步到B模块中,如不一样步,A、B模块在界面上呈现的User信息不一致,下沉到领域模型层统一管理后,问题不复存在;
  • 除领域模型复用外,还可复用领域模型相关的CRUD Reducer,例如:以前Users对象对应的Create Read Update Delete方法可能在A和B两个业务模块各维护一套,下沉到领域模型层统一管理后,减小了代码重复问题;
  • 天然承担了部分跨模块通讯职责,以前数据同步相关的跨模块通讯代码没有了存在的必要性;
3.1.2 应用状态模型

应用状态模型是与视图相关的状态数据,如:

  • 当前页面选中了列表的第n行 currentSelectedRow: someId
  • 窗口是否处于打开状态 isModalShow: false
  • 某种视图元素是否在拖拽中 isDragging: true

这些数据与具体的视图模块或业务功能强相关,建议存放在业务模块的Model中。

3.2 视图层组件

组件根据职责划分为两类:

  1. Container Component 容器型组件
  2. Presentational Component 展现型组件
3.2.1 容器型组件

容器型组件是与store直连的组件,为展现型组件或其它容器组件提供数据和行为,尽可能避免在其中作一些界面渲染相关的事情。

3.2.2 展现型组件

展现型组件独立于应用的其它部份内容,不关心数据的加载和变动,保持职责单一,仅作视图呈现和最基本交互行为,经过props接收数据和回调函数输出结果,保证接收的数据为组件数据依赖的最小集。

一个有成百上千展现型组件的复杂系统,若是展现型组件粒度切分能很好的遵循高内聚低耦合和职责单一原则的话,能够沉淀出不少可复用的通用业务组件

3.3 公共服务

  • 全部的HTTP请求放在一块儿统一管理;
  • 日志服务、本地存储服务、错误监控、Mock服务等统一存放在公共服务层;

按照上面三点合并同类项后,业务架构图变动为

api

4. 跨模块通讯

模块粒度逐渐细化,会带来更多的跨模块通讯诉求,为避免模块间相互耦合、确保架构长期干净可维护,咱们规定:

  • 不容许在一个模块内部直接调用其余模块的Dispatch方法(写操做、变动其余模块的state)
  • 不容许在一个模块内部直接读取其余模块的state方法(读操做)

咱们建议将跨模块通讯的逻辑代码放在父模块中,或者在一个叫作Mediator层中单独维护。

最终获得咱们团队完整的业务逻辑架构图:

Architecture

数据流管理

刚刚从空间维度讲了架构管理的方案,如今从时间维度说说应用的数据流转 --- Redux单向数据流。

Redux架构的设计核心是单向数据流,应用中全部的数据都应该遵循相同的生命周期,确保应用状态的可预测性。

redux

1. Action

  • 用户操做行为:click drag input ...
  • 服务端返回数据后续的行为

2. Reducer

每一个Action都会对应一个数据处理函数,即Reducer。特别强调,Reducer必须是纯函数(pure function),这个规定带来一个很是大的好处,数据处理层代码变的很是容易写单元测试。

纯函数的特征是入参相同的状况下,返回值恒等,举个栗子🌰:

纯函数:

function add(a, b) {
	return a + b;
}
复制代码

非纯函数:

function now() {
	let now = new Date();
	return now;
}
复制代码

函数中若是包含 Math.randomnew Date(), 异步请求等内容,且影响到最终结果的返回,即为非纯函数。

3. Store

Store 数据存放的地方,store保存从进入页面开始全部Action操做生成的数据状态(state),每次Action引起的数据变动都必须生成一个新的state对象,且确保旧的state对象不被修改。这样作能够保证 应用的状态的可预测、可追溯,也方便设计Redo/Undo功能。

咱们团队使用轻量级的immutable方案immutability-helper,相比彻底拷贝一份(deep clone)性能更优、存储空间利用率更高。

immutability-helper

immutability-helper的API不够友好,咱们写了一个库immutability-helper-x加强它的易用性。

immutability-helper API风格:

import update from 'immutability-helper';

const newData = update(myData, {
  x: {
  	y: {
			z: { $set: 7 }
		}
	},
});
复制代码

immutability-helper-x API风格:

import update from 'immutability-helper-x';

const newData = update.$set(myData, 'x.y.z', 7);
复制代码

4. 统一渲染视图

React/Redux是一种典型的数据驱动的开发框架(Data-Driven-Development),在开发中,咱们能够将更多的精力集中在数据(领域模型+状态模型)的操做和流转上,不再用被各类繁琐的DOM操做代码困扰,当Store变动时,React/Redux框架会帮助咱们自动的统一渲染视图。

监听Store变动刷新视图的功能是由react-redux完成的:

  • <Provider> 组件经过context属性向后代<connect>组件提供(provide)store对象;
  • <connect> 是一个高阶组件,做用是将store与view层组件链接起来(这里重复提一句,redux官方将<connect>直接链接的组件定义为container component),<connect>向开发者开放了几个回调函数钩子(mapStateToProps, mapDispatchToProps...)用于自定义注入container component的props的姿式;
  • react-redux监听redux store的变动,store改变后通知每个connect组件刷新本身和后代组件,为了减小没必要要的刷新提高性能,connect实现了shouldComponentUpdate方法,若是props不变的话,不刷新connect包裹的container component;

总结

严格遵循架构规范和单向数据流规范,能够保证咱们的前端应用在比较粗的粒度上的可维护性和扩展性,对于更细的粒度的代码,咱们组织童鞋学习和分享《设计模式》《重构 - 改善既有代码的设计》,持续打磨和优化本身的代码,将来团队会持续输出这方面的系列文章。

本篇先聊前端通用架构,具体模块的业务架构、架构遵循的原则、团队架构组的架构评审流程等内容会在接下来的系列文章中阐述。感兴趣的同窗关注专栏或者发送简历至 'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

相关文章
相关标签/搜索