• 前端项目模块化的实践3:使用 TypeScript 的收益


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

    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> 的内部实现

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

  • 相关阅读:
    c++继承与多态
    逻辑运算符
    页面分栏布局
    js中的三大特殊数据:undefined,null,NaN
    逻辑分支中if小括号中的隐式转换
    最小高度的兼容方法
    js数据类型的转换
    ognl.OgnlException: target is null for setProperty(null, "goodsSize", [Ljava.lang.String;@c3bb5。
    解决拦截器的对于参数传递无效问题
    引用外部js乱码问题
  • 原文地址:https://www.cnblogs.com/Jusfr/p/9626518.html
Copyright © 2020-2023  润新知