看到这个标题你的脑海中一定会浮现出两本书,一本就是,《重构--改善既有代码设计》 和 《代码整洁之道》 。这确实是两本非常伟大的图书,但是很遗憾里面提供的 code 都是 Java 的版本。《重构--改善既有代码设计》 的第2版提供了 JavaScript 的版本,已经非常方便我们前端同学阅读了,但是在 TypeScrip 如此火热的今天,缺了 TS 的版本,始终觉得是些遗憾,所以老袁打算每天拿出一些时间将一些非常经典的案例,结合老袁十年的经验总结到一块使用 TS 重写,希望能陪伴各位的技术成长之路,我会从大致如下方向跟各位共同分享:
希望你能和我一起完成这段旅程,写出整洁的TypeScript:
简单理解就是不改变软件可观察行为的前提下,改善其内部结构,以提高理解性和降低修改成本。
描述?:如果一个函数能够不需要任何参数能够解决你的问题(包括使用其他的函数,),这当然是绝佳的。但是在我们日常开发中需要经常为函数添加参数。
动机?:所以使用这个重构的动机很简单,你必须添加一个函数,而修改后的函数需要一些过去没有的信息
。
// ① 函数参数 (理论上少于等于2个)
function createMenu(title, body, buttonText, cancellable) {
...
}
// ② 超过2个参数 使用对象统一传递
interface IMenuConfig {
title?: string;
body?: string;
buttonText?: string;
cancellable?: boolean;
}
const menuConfig: IMenuConfig = {
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true,
};
function createMenu(menuConfig: IMenuConfig) {}
我们上面解决了将多个参数合并为一个,减少了过长的参数(它是代码的坏味道Data Clnmps,坏味道不是翻译的尴尬是坏味道包含如下),后面也会给大家逐步介绍。
Duplicated Code(重复代码) Long Method(过长函数) Large Class(过大的类) Long Parameter List(过长的参数列) Divergent Change(发散式变化) 等...
function add(...rest: number[]): number;
function add(...rest: string[]): string;
function add(...rest: any[]) {
let first = rest[0];
if (typeof first === 'number') {
return rest.reduce((pre, cur) => pre + cur);
}
if (typeof first === 'string') {
return rest.join('');
}
}
console.log(add(1, 2))
console.log(add('a', 'b', 'c'));
//不过如上代码我们可以更加简洁的完成它
interface ConfigFn {
<T>(value: T): void;
}
var getData2: ConfigFn = function <T>(value: T): T {
return value;
};
getData2<string>('老袁');
当然有关于函数这里我们还有很多其他的重构规则 我们后续再慢慢给大家渗透。
描述?:两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。
动机?:双向关联很有用,但是也必须为它付出代价,那就是维护双向链接。大量的双向链接很用以造成某个对象已经死亡,但是依然存在堆区造成内存泄漏。此外双向连接之间有了依赖,如果这是两个独立的文件没有经过webpack打包,比如运行在node中,这样就是跨文件的依赖。跨文件的依赖就会造成耦合系统,使得任何一点点像小改动都造成许多无法预知的后果。所以只有真正需要双向关联的时候,才去使用它。
// 打折商品订单类
class Order {
private _customer: Customer;
public getCustomer(): Customer {
return this._customer;
}
public setCustomer(customerName: string, customer: Customer): void {
const { _customer } = this;
if (_customer) {
// 假设我们规定一个打折商品只能购买一单 价格实时计算
customer.friendOrders.delete(customerName);
} else {
this._customer = customer;
customer.friendOrders.set(customerName, this);
}
}
public disCountedPrice(): number {
return Math.random() * this._customer.getDiscount();
}
}
class Customer {
private _orders = new Map<string, Order>();
public addOrder(orderName: string, order: Order): void {
order.setCustomer(orderName, this);
}
public getDiscount(): number {
//根据用户等级获取除折扣以外的价格
return 0.8;
}
public getPriceFor(order: Order): number {
return order.disCountedPrice();
}
get friendOrders() {
return this._orders;
}
}
const john = new Customer();
const basket = new Order();
john.addOrder('shoes', basket);
const priceShoes = john.getPriceFor(basket);
console.log('①', priceShoes);
john.addOrder('clothes', basket);
const priceClothes = john.getPriceFor(basket);
console.log('②', priceClothes);
console.log('一共消费', priceShoes + priceClothes);
/* ①以上这段的核心是为了计算最后商品的价格
* ②获取商品的价格我们分为了2步骤 一个是获取用户的VIP级别
* ③然后获取了商品本身的随机打折数 最终相乘
* ④其核心代码是addOrder好给Order的_customer赋值
其实完全没必要
*/
仔细想想,先有Customer才会有Order,我们完全可以把链接移除。Order获取折扣信息依赖_customer,所以:
//修改Order 然后外部传入customer 将两个类彻底分开
public disCountedPrice(customer: Customer): number {
return Math.random() * customer.getDiscount();
}
//或者直接修改Customer
public getPriceFor(order: Order): number {
return order.disCountedPrice(this);
}
最后还有一种暴力的手段,我们可以修改getCustomer主动去寻找Customer的实例。这样就保持了单向,最后我们把setCustomer其实就可以取消了。核心就是去除反向指针。
这个就跟上面?刚好相反了,也就是说如果两个类之间相互需要制约,必须由某个类控制某个类,就刚好符合将单向关联改为双向关联,那么vue的双向数据绑定的watcher 和 data之间的关系就是刚好最适合不过了。