typescript 定义map-打字稿中间方法

01键

keyof 与 Object.keys 略有相似,只不过 keyof 取的是接口的 key。

interface Point {
    x: number;
    y: number;
}
// type keys = "x" | "y"
type keys = keyof Point;

假设有一个如下所示的对象,我们需要使用typescript实现一个get函数来获取它的属性值

const data = {
  a: 3,
  hello: 'world'
}
function get(o: object, name: string) {
  return o[name]
}

我们可能会这样开始,但它有很多缺点

无法确认返回类型:这会失去ts最大的类型校准功能 难以约束key:可能会出现拼写错误

这时可以使用keyof来加强get函数的类型功能。 有兴趣的朋友可以看一下_.get的type标签和实现

function get(o: T, name: K): T[K] {
  return o[name]
}

02部分&挑选

现在您了解了 keyof,您可以使用它对属性进行一些扩展,例如实现 Partial 和 Pick。 pick通常用在_.pick中

type Partial = {
  [P in keyof T]?: T[P];
};
type Pick = {
  [P in K]: T[P];
};
interface User {
  id: number;
  age: number;
  name: string;
};
// 相当于: type PartialUser = { id?: number; age?: number; name?: string; }
type PartialUser = Partial
// 相当于: type PickUser = { id: number; age: number; }
type PickUser = Pick

03条件类型

类似于js中的?:操作符,可以用它来扩展一些基本类型

T extends U ? X : Y
type isTrue = T extends true ? true : false
// 相当于 type t = false
type t = isTrue
// 相当于 type t = false
type t1 = isTrue

04从不&排除&省略

官方文档描述never如下

thenevertype 表示从未出现过的值的类型。

never 和条件类型结合可以产生许多有趣且实用的类型,例如 Omit

type Exclude = T extends U ? never : T;
// 相当于: type A = 'a'
type A = Exclude

结合Exclude,可以衍生出Omit的写法

type Omit = Pick<T, Exclude>;
interface User {
  id: number;
  age: number;
  name: string;
};
// 相当于: type PickUser = { age: number; name: string; }
type OmitUser = Omit

05类型

顾名思义typescript 定义map,typeof 表示取某个值的类型。 它们的用法可以通过以下示例进行演示。

const a: number = 3
// 相当于: const b: number = 4
const b: typeof a = 4

在典型的服务器端项目中,我们经常需要往上下文中塞一些工具,比如config、logger、dbmodels、utils等,这时候就用到了typeof。

import logger from './logger'
import utils from './utils'
interface Context extends KoaContect {
  logger: typeof logger,
  utils: typeof utils
}
app.use((ctx: Context) => {
  ctx.logger.info('hello, world')
  // 会报错,因为 logger.ts 中没有暴露此方法,可以最大限度的避免拼写错误
  ctx.loger.info('hello, world')
})

06是

在此之前,我们先看一下koa的错误处理流程。 下面是集中处理错误的过程,但是标记了代码。

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    let code = 'BAD_REQUEST'
    if (err.isAxiosError) {
      code = `Axios-${err.code}`
    } else if (err instanceof Sequelize.BaseError) {
    }
    ctx.body = {
      code
    }
  }
})

在err.code处,会出现编译错误,提示Property'code'doesnotexistontype'Error'.ts(2339)。

这时候可以使用asAxiosError或者asany来防止错误,但是强制类型转换不够友好。

if ((err as AxiosError).isAxiosError) {
  code = `Axios-${(err as AxiosError).code}`
}

这时候可以使用is来判断值的类型

function isAxiosError (error: any): error is AxiosError {
  return error.isAxiosError
}
if (isAxiosError(err)) {
  code = `Axios-${err.code}`
}

在GraphQL的源码中,有很多这样的用法来标记类型

export function isType(type: any): type is GraphQLType;
export function isScalarType(type: any): type is GraphQLScalarType;
export function isObjectType(type: any): type is GraphQLObjectType;
export function isInterfaceType(type: any): type is GraphQLInterfaceType;

07接口&类型

接口和类型有什么区别?可以参考以下stackoverflow问题

一般来说typescript 定义map,接口和类型的区别很小,比如下面两种写法就类似

interface A {
  a: number;
  b: number;
};
type B {
  a: number;
  b: number;
}

其中,接口可以如下组合,类型只能使用&类来连接。

interface A {
    a: number;
}
interface A {
    b: number;
}
const a: A = {
    a: 3,
    b: 4
}

08字典&许多

这些句子糖是从lodash的types源代码中学习到的,在日常工作中使用得相当频繁。

interface Dictionary {
  [index: string]: T;
};
interface NumericDictionary {
  [index: number]: T;
};
const data:Dictionary = {
  a: 3,
  b: 4
}

09使用constenum维护常量表

与使用字面量对象来维护常量相比,constenum 可以提供更安全的类型检测

// 使用 object 维护常量
const enum TODO_STATUS {
    TODO = 'TODO',
    DONE = 'DONE',
    DOING = 'DOING'
}

// 使用 const enum 伟华常量
const enum TODO_STATUS {
    TODO = 'TODO',
    DONE = 'DONE',
    DOING = 'DOING'
}
function todos (status: TODO_STATUS): Todo[];
todos(TODO_STATUS.TODO)

10VSCodeTips&TypescriptCommand

有时在使用VSCode时,用tsc编译时形成的问题与vscode提示的问题不一致

找到项目右下角Typescript字样,两侧显示其版本号。 你可以点击选择UseWorkspaceVersion,这意味着项目所依赖的typescript版本仍然存在。

或者编辑 .vs-code/settings.json

{
  "typescript.tsdk": "node_modules/typescript/lib"
}

11Typescript路线图

最后也是最重要的一点,阅读Roadmap来了解ts的一些新功能和bug修复。

Typescript路线图

参考