TypeScript 迷之操作: 如何判断 any 类型是 any 类型 ?

1,411 阅读4分钟

这个标题, 大多数人看到反应应该是这样的

这时候就有长得帅的小伙伴会问了: any 类型不是已经等于放弃治疗了吗? 为什么我还要管 any 类型是不是 any 类型?

所以首先我们先设想一下场景:

众所周知, ts 除了类型检测, 还有一个非常开发者友好的效果, 就是 vscode 中的代码提示, 像下面这样

不错, 正是在下

在写工具库或者组件库的时候, 我们可以将这样的类型定义随着 npm 包发布, 来极大的提升开发者的使用体验; 这种大家应该都见得多了, 平平无奇的 typescript 技巧;🎈

但是

假如这些类型需要使用者来定义呢?

比如我定义这样一个 <MapingMessage /> 组件, 暂且就以 react 为例子

import React, { FC } from 'react';
const mapping = {
  hello: 'world',
  love: 'juejin',
} as const;

const MappingMessage: FC<{ id: keyof typeof mapping }> = ({ id }) => {
  return <div>{mapping[id]}</div>;
};

const App: FC = () => {
  return <MappingMessage id=""></MappingMessage>;
};

export default MappingMessage;

那么, 我在用这个组件的时候 就会有 id 提示出来, 像这样

其中 keyof typeof mapping 这个id 的类型, 就是用户自定义的, 你作为第三方库是没办法知道的, 那你写库的时候类型就只能泛泛而填, 写个 string , 类型安全保证--, 代码提示效果-- 没错, 说的就是你 react-intl/FormattedMessage

不过其实我们可以

让用户把这个类型暴露到全局, 像这样

// global.d.ts
import mapping from './mapping';

declear global {
  type MappingKeys = keyof typeof mapping;
}

这样就可以在我们开发的库里面直接引用这个类型来用作类型提示了~

芜湖起飞🚀

const MappingMessage: FC<{ id: MappingKeys }> = ({ id }) => {
// ----------------------------^a)这里这里----------------------
  return <div>{mapping[id]}</div>;
};

不过

作为三方库的开发者我们需要多考虑一层 这波, 这波在大气层

使用者不写这个类型怎么办?

我们分两步来看这个问题

a) 没有定义的类型是什么类型? 就是上面标注那里

b) 没有定义的话我能不能加个保底的 string 类型?

到这里, 终于可以引出我们的标题 any 类型 , 第一题的答案可以简单写行代码就能验证出来

尽管 vscode 很无情的报了一个明显的错, 然而还是很诚实的告诉了我们这相当于写了一个 any

那么第二个问题就提出了我们标题中的疑问:

如何判断一个 any 类型any 类型?

T extends X ? 'yes' : 'no'

Step1: 检查一个类型是不是 never, 利用了 extends 配合 ?: 来写一个工具类型,注意观察 type a-f 的输出

根据这个结果, 相信凭借程序员の基本素养 找规律 已经能够发现 any 类型的与众不同了, 只有 any 类型的返回类型是 | 或类型的!!!🎉🎉🎉

type CheckNever<T> = T extends never ? 'yes' : 'no';

type a = CheckNever<any>;           // yes | no
type g = CheckNever<NODEFINED>;     // yes | no
type b = CheckNever<unknown>;       // no
type c = CheckNever<undefined>;     // no
type d = CheckNever<null>;          // no
type f = CheckNever<string>;        // no
type e = CheckNever<never>;         // never

Step2: 最有趣的一部分来了, 再来一次判断, 这里是一个反向操作!!非常绕!! 强烈建议在 vscode 中去查看效果, 慢慢体会~~

type CheckAny<T> = CheckNever<T> extends 'no' ? '不是Any' : '是Any';
// ----------------------------------------------^这里是反向的哦---
//                                // CheckNever<T>  ->  extends 'no' ? ->
type aa = CheckAny<any>;          // yes | no       ->  否             ->  不是Any
type gg = CheckAny<NODEFINED>;    // yes | no       ->  否             ->  不是Any
type bb = CheckAny<unknown>;      // no             ->  是             ->  是Any
type cc = CheckAny<undefined>;    // no             ->  是             ->  是Any
type dd = CheckAny<null>;         // no             ->  是             ->  是Any
type ff = CheckAny<string>;       // no             ->  是             ->  是Any
type ee = CheckAny<never>;        // never          ->  是!奇妙        ->  是Any

大功告成!!!

那么剩下的工作就只剩一丢丢了, 我们只需要利用 CheckAny<T> 这个工具类型

type GetIdType = CheckAny<MappingKeys> extends 'yes' ? T : string;

const MappingMessage: FC<{ id: GetIdType }> = ({ id }) => {
  return <div>{mapping[id]}</div>;
};

来最终完善一下工具库的类型, 为我们的类型库画上一个圆满的句号


当然故事并没有完结

由此可以推出来更多的问题

  1. CheckUndefined ? CheckNull? CheckUnknown?
  2. 或者更加加通用的 CheckIsX ?
  3. 除了这种方式, 有没有其他办法来获取到用户定义的类型?
  1. 这个还是倒是还有思路的比如利用 interface 类型合并的特性, 大概写法, 没验证
// other_lib_in_npm.polyfill.d.ts

import 'other-lib-in-npm';
declear module 'other-lib-in-npm' {
  interface GetIdType extends keyof typeof mappings {}
}

typescript 的类型系统真是其乐无穷啊

完结撒花🎉🎉🎉


我们也在招聘!!

看这里看这里 #掘金沸点# juejin.cn/pin/6921602…