为什么不要用类型编程,不要打类型体操

252 阅读3分钟

TypeScript的类型编程功能非常强大

甚至有人用类型编程做了个中国象棋:zhuanlan.zhihu.com/p/426966480

注意观察这个象棋程序里有多少个嵌套的extends

但是,正是类型编程的强大,导致它带来的复杂度无法承受

故事

有一个具体的场景,导致我在一瞬间丧失了对类型编程的兴趣。

需求背景

当时项目里需要建立一个链接,接受消息通知。

我决定在网页加载时就建立一个链接,并分发消息。

其他地方只需要监听消息。

代码设计

有几种不同的消息

enum EventsType {
	begin,
	end,
	pause,
}

每种消息的有不同的数据结构

type Begin = {...}
type End = {...}
type Pause = {...}

然后,

  1. 需要给每种消息,都生命一个数组,存放回调函数。
  2. 需要给每种消息,都提供一个对外暴露的,注册回调的函数。

显然,这两部操作除了消息类型不一样,逻辑都是一样的。 于是我写了一个高阶函数,在闭包里保存回调函数数组,并返回注册用的函数。

const listenerFactory =
	<R extends MessageType>(eventType: R['type']) =>
	(cb: (message: R) => void) => {
		const _cb = (message: MessageType) => {
			if (message.type !== eventType) return;
			cb(message as R);
		};
		callbacks.push(_cb);
		const remove = () => {
			pull(callbacks, _cb);
		};
		/**
		 * remove the listener
		 */
		return remove;
	};

用法

export const onBegin = listenerFactory(EventsType.Begin);

export const onEnd = listenerFactory(EventsType.End);

export const onPause = listenerFactory(EventsType.Pause);

类型编程

但是现在有一个问题是高阶函数listenerFactory无法自动推断出R的类型。

这时,我可能需要搞一点类型编程了,弄一个类型函数,让回调函数能自动获得类型约束。

我大概需要写一堆嵌套的extends判断来递归的找到R的正确的类型。

我很兴奋,学到的类型编程技巧,有用武之地了。

结果我在三秒之后就丧失了兴趣!!!

我为什么要花时间弄这个类型编程?仅仅是为了VSCode能弹出一个菜单让我选?

而且整个项目是只有我一个人在开发。

我为什么不直接把类型写死,以避免类型编程呢?

就像这样:

export const onBegin = listenerFactory<Begin>(EventsType.Begin);

探讨

React

想象给把React改成TypeScript。

FiberNode里有大量相同属性名,但值类型不同的数据结构

万一TypeScript做不到自动推断,得写多少类型编程的代码?

Vue

Vue2的选项是api,因为属性,方法都是写在对象上,导致类型编程无法避免。

而且是事后添加对TypeScript的支持,类型编程更麻烦

作者在一个播客上聊过这件事

TypeScript编译器甚至专门提供了一个ThisType解决这个问题

Dart

类型编程这个东西,在大部分语言里都没用,但是这些语言仍然提供了良好的类型系统。

比如Dart也是强类型,没有类型编程带来的复杂度,但是仍然有强类型的好处。

结论

类型编程在大部分情况下都是不必要的。