Item 70: Mirror Types to Sever Dependencies 
要点 
- Avoid transitive type dependencies in published npm modules.
 - Use structural typing to sever dependencies that are nonessential.
 - Don't force JavaScript users to depend on 
@types. Don't force web developers to depend on Node.js. - 避免在发布的 npm 模块中出现传递类型依赖。
 - 使用结构化类型来切断非必要的依赖。
 - 不要强迫 JavaScript 用户依赖 
@types,也不要强迫 web 开发者依赖 Node.js。 
正文 
假设你编写了一个用于解析 CSV 文件的库。它的 API 很简单:你传入 CSV 文件的内容,然后得到一个将列名映射到值的对象列表。
为了方便你的 Node.js 用户,你允许内容既可以是字符串,也可以是 Node.js Buffer:
// parse-csv.ts
import { Buffer } from 'node:buffer'
function parseCSV(contents: string | Buffer): { [column: string]: string }[] {
  if (typeof contents === 'object') {
    // It's a buffer
    return parseCSV(contents.toString('utf8'))
  }
  // ...
}Buffer 的类型定义来自 Node.js 类型声明,你必须安装它:
npm install --save-dev @types/node这里我们遵循了第 65 条的建议,将 @types 作为开发依赖而不是生产依赖。
当你发布你的 CSV 解析库时,你使用 --declaration 生成类型声明并将其打包。生成的 .d.ts 文件如下所示:
// parse-csv.d.ts
import { Buffer } from 'node:buffer'
export declare function parseCSV(contents: string | Buffer): {
  [column: string]: string
}[]如果你采用这种方法,你的库的 JavaScript 用户会很高兴,但 TypeScript web 开发者不会。你会收到他们的投诉,说他们从你的库中得到了错误:
Cannot find module 'node:buffer' or its corresponding type declarations.因为我们将 @types/node 作为 devDependency,所以它不会随我们的包一起安装,即使我们的类型(作为我们包的一部分)依赖于它。
那么我们应该将 @types/node 作为生产依赖吗?这会让错误消失,但现在你可能会收到另一组投诉:
- JavaScript 开发者会想知道这些他们依赖的 
@types模块是什么。 - TypeScript web 开发者会想知道为什么他们要依赖 Node.js。
 - 使用不同版本 Node.js 的 TypeScript 开发者会想知道为什么他们有重复的类型定义。
 
这些投诉是合理的。Buffer 行为不是必需的,只对已经在使用 Node.js 的用户相关。而 @types/node 中的声明只对同时使用 TypeScript 的 Node.js 用户相关。@types/node 包不小(近 10 万行代码),我们的库只使用了其中很小的一部分。
TypeScript 的结构化类型(第 4 条)可以帮助你摆脱困境。与其使用来自 @types/node 的 Buffer 声明,你可以只写你需要的方法和属性。在这种情况下,只是一个可以接受编码的 toString 方法:
export interface CsvBuffer {
  toString(encoding?: string): string
}
export function parseCSV(
  contents: string | CsvBuffer
): { [column: string]: string }[] {
  // ...
}这个接口比完整的接口短得多,但它确实捕获了我们对 Buffer 的(简单)需求。在 Node.js 项目中,用真实的 Buffer 调用 parseCSV 仍然可以,因为类型是兼容的:
parseCSV(new Buffer('column1,column2\nval1,val2', 'utf-8')) // OK再次查看 CsvBuffer 接口,它没有任何特定于 CSV 文件的内容。给它一个更"结构化"的名称可以强化这一点:
/** Anything convertible to a string with an encoding, e.g. a Node buffer. */
export interface StringEncodable {
  toString(encoding?: string): string
}import { Buffer } from 'node:buffer'
import { parseCSV } from './parse-csv'
test('parse CSV in a buffer', () => {
  expect(parseCSV(new Buffer('column1,column2\nval1,val2', 'utf-8'))).toEqual([
    { column1: 'val1', column2: 'val2' },
  ])
})这个测试验证了你的代码的运行时行为和 Node Buffer 对 StringEncodable 的可赋值性。测试导入了 node:buffer,但这没关系,因为 @types/node 可以是 devDependency 而不影响你的库的用户。
如果你的代码开始使用 Buffer 接口的更多方法,那么你还需要将它们添加到你的接口版本中。这可能感觉重复,但正如 Go 语言社区所说,"一点复制比一点依赖更好"。如果你依赖另一个库类型的大部分,你可以选择通过供应商化依赖来正式化这种复制。
无论如何,通过切断 @types 依赖,你为 JavaScript 和各种 TypeScript 开发者提供了良好的体验。如果 @types 依赖有自己的依赖,那么你可能会切断整个依赖树,这对编译器性能有巨大的积极影响(第 78 条)。
这种技术也有助于切断单元测试和生产系统之间的依赖。参见第 4 条中的 getAuthors 示例。
要点回顾 
- 避免在发布的 npm 模块中出现传递类型依赖。
 - 使用结构化类型来切断非必要的依赖。
 - 不要强迫 JavaScript 用户依赖 
@types,也不要强迫 web 开发者依赖 Node.js。