以下是关于前端项目模块化的实践,包含以下内容:

  1. 搭建 NPM 私有仓库管理源码及依赖
  2. 使用 Webpack 打包基础设施代码
  3. 使用 TypeScript 编写可靠类库

使用 Webpack 打包基础设施代码已经很大程度上解决了生产力,但日益复杂业务和逻辑仍然让前端陷入“动态一时爽、重构火葬场”的笑谈,TypeScript 为解决这个问题而来。

在本章节我们使用 TypeScript 完成一个类似 LINQ 中 Enumerable<T> 的实现,涉及代码编写与测试用例,仍然不深入关于 TypeScript 的讨论。

场景

假想需要实现这样一个功能:比如一组学生,我们希望按照根据班级分组,接着按年龄排序,最后按照名称排序。

这并不复杂,这些步骤是有先后的,每步操作得到的都是一组对象集合,分组和排序是常规数组操作,写几个循环就可以达到目标。

C# 的实现

void Main() {
var students = new List<Student> {
new Student { Classes = "class-1", Name = "Rattz", Age = 11 },
new Student { Classes = "class-2", Name = "Rose", Age = 10 },
new Student { Classes = "class-1", Name = "Mike", Age = 11 }
}; var seq = students.GroupBy(x => x.Classes)
.Select(g => new {
Classes = g.Key,
Members = g.OrderBy(x => x.Age)
.ThenBy(x => x.Name)
.Select(x => x)
});
} class Student {
public String Classes { get; set; }
public String Name { get; set; }
public Int32 Age { get; set; }
}

得力于静态语言的类型保证及 LINQ 语法对内存对象的操作能力,一行代码就简单而有表现力地解决了问题,在 LINQPad 里组织如下:

ES6 很明了但代码略多

现在来看 JavaScript 的写法,使用 ES6 的 Map 对象极大地减化了代码。

let students = [
{'classes': 'class-1', 'name': 'Rattz', 'age': 11},
{'classes': 'class-2', 'name': 'Rose', 'age': 10},
{'classes': 'class-1', 'name': 'Mike', 'age': 11},
]; let map = new Map();
for (let item of students) {
let classes = map.get(item.classes);
if (Object.is(classes, undefined)) {
classes = [item];
map.set(item.classes, classes);
}
else {
classes.push(item);
}
} for (let [, value] of map.entries()) {
value.sort((p1, p2) => {
if (p1.age !== p2.age) {
return p1.age - p2.age;
}
return p1.name.localeCompare(p2.name);
});
} let groups = [];
for (let [key, value] of map.entries()) {
groups.push({
classes: key,
members: value,
});
}

ES5 reduce 很强有力但很难一次编写正确

代码略多,包含了3个循环,如果不使用 Map 代码就要进行数组查找;如果想压缩循环,就使用 Array.prototype.reduce 函数,代码就很难懂了。

let students = [
{'classes': 'class-1', 'name': 'Rattz', 'age': 11},
{'classes': 'class-2', 'name': 'Rose', 'age': 10},
{'classes': 'class-1', 'name': 'Mike', 'age': 11},
]; let groups = students.reduce((previous, current) => {
let arr = previous.filter(x => x.key === current.classes);
if (arr.length > 0) {
arr[0].members.push(current);
}
else {
previous.push({
classes : current.classes,
members: [current],
});
}
return previous;
}, []); for(let g of groups) {
g.members.sort((a, b) => a.name.localeCompare(b.name));
}

TypeScript 的使用

下文通过编写类库完成类似功能,我们充分使用生产力而先不考虑语言、版本问题,看看具体的调用部分

interface Student {
classes: string,
name: string
age: number
} let students: Student[] = [
{'classes': 'class-1', 'name': 'Rattz', 'age': 11},
{'classes': 'class-2', 'name': 'Rose', 'age': 10},
{'classes': 'class-1', 'name': 'Mike', 'age': 11},
]; let groups = Enumerable.from(students)
.groupBy(x => x.classes)
.select(x => ({
classes: x.key,
members: Enumerable.from(<Student[]>x.items)
.sortBy((x, y) => x.age - y.age)
.thenSortBy((x, y) => x.name.localeCompare(y.name))
}));

完整代码见于 Enumerable 行 100 左右。

如果 Enumerable 的实现是 JavaScript 版本,这部分代码可能充满了疑问,即便阅读源码也很难一下子理解实现者的用意

  • groupby 需要什么样的参数?
  • select 使用了 groupBy 返回值,它包含怎样的数据结构?
  • sortBy 但返回了什么?
  • thenSortBy 如何进行二次排序,又返回了什么?

TypeScript 能够解答上述问题问题,以下是函数签名。

  • groupBy:完整签名是 groupBy<K, U>(keySelector: (value: T, index: number) => K, valueSelector?: (value: T, index: number) => U): Enumerable<Group<K, T | U>>,虽然稍长但是阅读起来也就那回事

    • keySelector: 接受2个参数(分别是数组元素和索引)返回1个值,通常是对象的某个属性,
    • valueSelector: 和keySelector相似,表示得到新元素的方法,术语是“投影”
    • Enumerable<Group<K, T | U>>groupBy 的返回类型,表示仍然是 Enumerable<> 实例,只是内部元素由传入的 valueSelectorkeySelector 决定,该签名使得链式调用成为可能;
  • select: 完整签名是 select<U>(callback: (value: T, index: number) => U): Enumerable<U>,和 groupBy 类似,但更加简单
  • sortBy: 和 Array.prototype.sort 功能相似,但签名是 sortBy(compareFn: (a: T, b: T) => number): OrderedEnumerable<T>,返回 OrderedEnumerable<> 实例
  • thenSortBy:同 sortBy,依赖 OrderedEnumerable<T> 的内部实现

我们可以在安全地传入参数和引用返回值,在代码编写阶段就能得到编译器的语法、值入参数合法性检查。

前端项目模块化的实践3:使用 TypeScript 的收益的更多相关文章

  1. 前端项目模块化的实践2:使用 Webpack 打包基础设施代码

    以下是关于前端项目模块化的实践,包含以下内容: 搭建 NPM 私有仓库管理源码及依赖: 使用 Webpack 打包基础设施代码: 使用 TypeScript 编写可靠类库 使用 TypeScript ...

  2. 前端项目模块化的实践1:搭建 NPM 私有仓库管理源码及依赖

    以下是关于前端项目模块化的实践,包含以下内容: 搭建 NPM 私有仓库管理源码及依赖: 使用 Webpack 打包基础设施代码: 使用 TypeScript 编写可靠类库 使用 TypeScript ...

  3. [Vue 牛刀小试]:第十七章 - 优化 Vue CLI 3 构建的前端项目模板(1)- 基础项目模板介绍

    一.前言 在上一章中,我们开始通过 Vue CLI 去搭建属于自己的前端 Vue 项目模板,就像我们 .NET 程序员在使用 asp.net core 时一样,我们更多的会在框架基础上按照自己的开发习 ...

  4. TypeScript在react项目中的实践

    前段时间有写过一个TypeScript在node项目中的实践. 在里边有解释了为什么要使用TS,以及在Node中的一个项目结构是怎样的. 但是那仅仅是一个纯接口项目,碰巧赶上近期的另一个项目重构也由我 ...

  5. 前端工程模块化——以一个php项目为例

    实现一个页面功能总是需要 JavaScript.CSS 和 Template 三种语言相互组织,所以我们真正需要的是一种可以将 JavaScript.CSS 和 Template 同时都考虑进去的模块 ...

  6. TypeScript在node项目中的实践

    TypeScript在node项目中的实践 TypeScript可以理解为是JavaScript的一个超集,也就是说涵盖了所有JavaScript的功能,并在之上有着自己独特的语法.最近的一个新项目开 ...

  7. 从一个前端项目实践 Git flow 的流程与参考

    Git flow 出自 A successful Git branching model,这里使用了一个前端项目配合本文稿实施了 git flow 并记录流程作出示例和参考,对 hotfix 与持续部 ...

  8. 基于CocoaPods的iOS项目模块化实践

    什么是CocoaPods? CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects. It has ove ...

  9. Webpack:前端资源模块化管理和打包工具

    一.介绍: Webpack 是当下最热门的前端资源模块化管理和打包工具.它可以将许多松散的模块按照依赖和规则打包成符合生 产环境部署的前端资源.还可以将按需加载的模块进行代码分隔,等到实际需要的时候再 ...

随机推荐

  1. Sql Server中的游标最好只用于有主键或唯一键的表

    游标cursor,我想大多数人都在sql server里面用过.当一个表数据量不太大的时候,游标还是可以用的,毕竟游标是循环一个表中每一行数据的最简便办法.但是如果你用一个游标去循环一个没有主键或唯一 ...

  2. CSS| background_属性总结

    Property List 属性 描述 CSS background 在一个声明中设置所有的背景属性. 1 background-attachment 设置背景图像是否固定或者随着页面的其余部分滚动. ...

  3. 使用动态跟踪技术SystemTap监控MySQL、Oracle性能

    [IT168 技术]本文根据吕海波2018年5月11日在[第九届中国数据库技术大会]上的演讲内容整理而成. 讲师介绍: 吕海波,美创科技研究员,ITPUB管理版版主.出版技术书籍<Oracle内 ...

  4. 修改centos等linux的hostname-永久生效

    步骤uname -a (或者hostname)可以看到hostname,修改步骤如下: uname -a 查看hostnamehostname newname 修改下,让hostname立刻生效.vi ...

  5. Rarfile解压不了的问题

    最近用python调用rarfile进行解压rar压缩包时,报了如下错误: rarfile.RarCannotExec: Unrar not installed? (rarfile.UNRAR_TOO ...

  6. TruncateATable 清除一张表

    当我们想删除一张表的全部数据时,我们可以使用 truncate 关键字,但如果要删除的表的主键被引用了,那么就无法执行语句. 1.制作清除数据的工具 ,在 nuget 控制台中输入 Install-P ...

  7. mysql数据导入导出与数据表优化

    一.数据导入 mysqlimport -uroot oa d:/aa.txt --fields-terminated-by=, --fields-optionally-enclosed-by= --l ...

  8. vc MFC 通过IDispatch调用默认成员函数

    CComPtr<IDispatch> spDisp(IDispatch *); if(!spDisp) return; DISPPARAMS dispParam={0}; //没有参数 V ...

  9. 关于Java集合类库中的几种常用队列

    Java中几种常用的队列 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞 ...

  10. python提示警告InsecureRequestWarning

    在Python3中使用以下代码报错: import requests response = requests.get(url='', verify=False) 错误代码如下: InsecureReq ...