跳到主要内容

4 篇博文 含有标签「TS」

查看所有标签

TS 无法写入文件

· 阅读需 1 分钟

tsconfig.json 报错: 无法写错写入文件 ,因为他会覆盖输入文件 是怎么回事?

这个错误通常是由于 tsconfig.json 中的编译选项配置导致的。

具体来说,这个错误可能是在输出目录与输入文件目录重叠时发生的。

输出目录配置错误

如果 outDir 未指定,TypeScript 编译器会尝试在同一目录中写入输出文件,从而覆盖输入文件。

参考 tsconfig.json

所以,确保 outDir 指向一个单独的目录(例如 ./dist)。

标签:

什么时候使用 unknown 和 never

· 阅读需 4 分钟

unknown 是所有可能值的集合。任何值都可以分配给 unknown 类型的变量。这意味着 unknown 是所有其他类型的超类型。因此, unknown 被称为顶级类型

never 是空集。没有值可以分配给 never 类型的变量。事实上,将值类型解析为 never 是一个错误,因为这会产生矛盾。空集可以放入任何其他集合中,因此 never 是所有其他类型的子类型。这就是为什么 never 被称为底部类型

unknown+never

对于任何类型的 T

T | neverT
T & unknownT

never 类型

利用值类型解析为 never 是一个错误,可以用来裁剪或者说缩小类型范围

// timeout 永远不会调用 resolve
function timeout(ms: number): Promise<never> {
return new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout elapsed')), ms))
}

async function fetchPriceWithTimeout(tickerSymbol: string): Promise<number> {
const stock = await Promise.race([
fetchStock(tickerSymbol), // returns `Promise<{ price: number }>`
timeout(3000),
])
return stock.price
}

Promise.race 签名的工作方式如下

function race<A, B>(inputs: [Promise<A>, Promise<B>]): Promise<A | B>

就上面这段代码,将 fetchStock 与 timeout 组合在一起,因此输入 Promise 解析类型为 { price: number } 和 never

输出的解析类型 stock 的类型应为 { price: number } | never

因为 never 是联合的标识,所以该类型简化为 { price: number }

如果我们使用 any ,我们就会失去类型检查的好处,因为 { price: number } | any 相当于 any

如果我们使用 unknown ,那么 stock 的类型将是 { price: number } | unknown ,它简化为 unknown

使用 never 来修剪不需要的情况

// 它从联合类型中删除了 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T

unknown 类型

任何值都可以分配给 unknown 类型的变量。因此,当值可能具有任何类型,或者不方便使用更具体的类型时,请使用 unknown

function prettyPrint(x: unknown): string {
if (Array.isArray(x)) {
return '[' + x.map(prettyPrint).join(', ') + ']'
}
if (typeof x === 'string') {
return `"${x}"`
}
if (typeof x === 'number') {
return String(x)
}
return 'etc.'
}

在 TypeScript 3.0 之前,编写 prettyPrint 的最佳方法是使用 any 作为 x 的类型。类型缩小对 any 的作用与对 unknown 的作用基本相同;

但是,如果我们犯了一个错误,有时候认为类型已经缩小,但实际上并没有缩小,那么使用 unknown 可以拯救我们:

function prettyPrint(x: any): string {
if (isArray(x)) {
// whoops - this `isArray` is not a type guard!
return '[' + x.mop(prettyPrint).join(', ') + ']'
}
/* snip */
return 'etc.'
}

因为 isArray 不是类型保护,并且我们使用 any 作为 x 的类型,所以 x 的类型仍然是 any 在 if 正文中。因此,编译器不会捕获此版本 prettyPrint 中的拼写错误。如果 x 的类型是 unknown ,我们会得到这个错误: Object is of type ‘unknown’.

参考

When to use never and unknown in TypeScript

标签:

TS Equals

· 阅读需 4 分钟

在做 ts 类型练习的时候,碰到需要比较两个类型是否相同,看到答案里有这么一种方法

export type Equals<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;

这里的泛型 T 是什么意思?这段代码该怎么理解?

搜索到比较好的答案在这里,也顺便记录下自己看完后的理解

泛型 T 是什么意思?

没有意思!!这就是一个定义的泛型,只是我们平时用的比较多的是像这样

type Identity<T> = (data: T) => T

区别只是上面这段代码在执行时会自动根据传入的 data 类型推断 T 的类型,而 Equal 中的写法需要手动传入,默认是 unknown,没有任何特殊的含义

怎么理解这段代码

Equal 这段代码重点在于比较两个函数 (<T>() => T extends X ? 1 : 2)(<T>() => T extends Y ? 1 : 2) 是否相同。

那么问题就变成 X 和 Y 是否相同。

如果 X 和 Y 相同,没什么好说的, extends 一定成立;如果不同时,那么我们总能找到一种类型,使得用它作为 T 时,extends 不成立。代码如下

declare let x: <T>() => T extends number ? 1 : 2
declare let y: <T>() => T extends string ? 1 : 2

const a = x<string>() // 2
const b = x<number>() // 1

const c = y<string>() // 1
const d = y<number>() // 2
declare let x: <T>() => T extends { foo: string; bar: number } ? 1 : 2
declare let y: <T>() => T extends { foo: string } ? 1 : 2

const a = x<{ foo: string }>() // 2
const b = y<{ foo: string }>() // 1

因此我们就可以用它来判断 X 和 Y 是否相同。

特殊情况

以上只是解释了 Equal 的基本原理,ts 内部实现可能要复杂的多,可能有时候为了性能或者什么原因,会出现一些和常规认识不一样的结果

NativeEqual

type NaiveEquals<X, Y> = X extends Y ? (Y extends X ? true : false) : false

type A = NaiveEquals<{ a?: number }, {}> // true
type B = Equals<{ a?: number }, {}> // false

理论上 A 也应该是 false,因为类型 {a: string} 可分配给 ,但不能分配给 {a?: number}

联合类型

type A = Equals<{ x: 1 } & { y: 2 }, { x: 1; y: 2 }> // false

理论上这里 A 应该是 true,但结果却是 false,了解不了,但是事实。

总结

Equal 当作日常使用应该是没问题的,特殊情况我觉得完全可以当作补充知识去了解下。

再次说明,原文在这里

标签: