您当前的位置:首页 > 电脑百科 > 程序开发 > 程序员

TypeScript 程序员晋级的 11 个必备技巧

时间:2023-04-21 12:39:36  来源:微信公众号  作者: 前端新世界


当你学习TypeScript时,你的第一印象可能会欺骗你:这不就是JAVAScript注解的一种方式吗?不就是编译器用来帮助我找到潜在bug的吗?

虽然这种说法没错,但随着你对TypeScript不断了解,你会发现这门编程语言最不可思议的力量在于编写、推断和操作数据类型。

本文总结的一些技巧,可以帮助大家充分发挥TypeScript的潜力。

#1 用集合的概念思考问题

数据类型是程序员日常要处理的概念,但要简洁地定义它却出奇地困难。然而我发现集合非常适合用作概念模型。

刚开始学习TypeScript时,我们常常会发现用TypeScript编写类型的方式很不自然。举一个非常简单的例子:

type Measure = { radius: number };
type Style = { color: string };

// typed { radius: number; color: string }
type Circle = Measure & Style;

如果你在逻辑AND的意义上解释运算符&,可能会认为Circle是一个虚拟类型,因为它是两种类型的结合,没有任何重叠的字段。这不是TypeScript的工作方式。此时通过集合的概念思考更容易推断出正确的行为:

  • 每个类型都是一系列值的集合。
  • 有些集合是无限的:例如string、object;有些是有限的:例如bool,undefined,...
  • unknown​是通用集(包括所有值),而never是空集(包括无值)。
  • 类型Measure是包含radius数字字段的所有对象的集合。style也是如此。
  • &​运算符创建一个交集:Measure & Style表示包含radius和color的对象集,这实际上是一个较小的集合,字段更常用。
  • 同理,|运算符创建一个并集:一个较大的集合,但常用字段可能较少(如果组合两个对象类型的话)。

集合还有助于了解可分配性:仅当值的类型是目标类型的子集时,才允许赋值:

type ShapeKind = 'rect' | 'circle';
let foo: string = getSomeString();
let shape: ShapeKind = 'rect';

// disallowed because string is not subset of ShapeKind
shape = foo;

// allowed because ShapeKind is subset of string
foo = shape;

#2 了解声明类型和收窄类型

TypeScript中一个非常强大的功能是基于控制流的自动类型收窄。这意味着变量在代码位置的任何特定点都有两种与之关联的类型:声明类型和收窄类型。

function foo(x: string | number) {
  if (typeof x === 'string') {
    // x's type is narrowed to string, so .length is valid
    console.log(x.length);

    // assignment respects declaration type, not narrowed type
    x = 1;
    console.log(x.length); // disallowed because x is now number
  } else {
    ...
  }
}

#3 使用可区分的联合类型而不是可选字段

当定义一组多态类型(如Shape)时,很容易这样开始写代码:

type Shape = {
  kind: 'circle' | 'rect';
  radius?: number;
  width?: number;
  height?: number;
}

function getArea(shape: Shape) {
  return shape.kind === 'circle' ? 
    Math.PI * shape.radius! ** 2
    : shape.width! * shape.height!;
}

需要非空断言(访问radius、width和height​时),因为kind和其他字段之间没有建立关系。相反,可区分的联合类型是一个更好的解决方案:

type Circle = { kind: 'circle'; radius: number };
type Rect = { kind: 'rect'; width: number; height: number };
type Shape = Circle | Rect;

function getArea(shape: Shape) {
    return shape.kind === 'circle' ? 
        Math.PI * shape.radius ** 2
        : shape.width * shape.height;
}

从以上代码可以看出,类型收窄消除了强制类型转换的需要。

#4 使用类型谓词避免类型断言

如果你以正确的方式使用TypeScript的话,你会发现自己很少使用显式类型断言(比如value as SomeType);但是,有时你可能会冲动地写出诸如这样的代码:

type Circle = { kind: 'circle'; radius: number };
type Rect = { kind: 'rect'; width: number; height: number };
type Shape = Circle | Rect;

function isCircle(shape: Shape) {
  return shape.kind === 'circle';
}

function isRect(shape: Shape) {
  return shape.kind === 'rect';
}

const myShapes: Shape[] = getShapes();

// error because typescript doesn't know the filtering
// narrows typing
const circles: Circle[] = myShapes.filter(isCircle);

// you may be inclined to add an assertion:
// const circles = myShapes.filter(isCircle) as Circle[];

更优雅的解决方案是将isCircle和isRect​更改为返回类型谓词,这样就可以帮助TypeScript在filter调用后进一步收窄类型:

function isCircle(shape: Shape): shape is Circle {
    return shape.kind === 'circle';
}

function isRect(shape: Shape): shape is Rect {
    return shape.kind === 'rect';
}

...
// now you get Circle[] type inferred correctly
const circles = myShapes.filter(isCircle);

#5 控制联合类型的分布方式

类型推断是TypeScript的特性;大多数时候,它默默地为你工作。但是有时你可能对模棱两可的细微情况进行干预。分布式条件类型就是其中一种情况。

假设我们有一个ToArray辅助类,如果输入类型还不是数组类型,则返回数组类型:

type ToArray<T> = T extends Array<unknown> ? T: T[];

你认为以下类型会推断出什么?

type Foo = ToArray<string|number>;

答案是string[] | number[]​。但这是模棱两可的。为什么不是(string | number)[]呢?

默认情况下,当TypeScript遇到联合类型(此处为string | number​)的泛型参数(此处为T​)时,它会分布到每个组成部分中,这就是为什么会得到string[] | number[]​的原因。你可以通过使用特殊语法并将T​包装在一对[]中来更改此行为,例如:

type ToArray<T> = [T] extends [Array<unknown>] ? T : T[];
type Foo = ToArray<string | number>;

现在Foo​被推断为类型(string | number)[]。

#6 使用详尽检查捕获在编译时未处理的情况

在switch​语句中使用enum枚举时,一个好习惯是在没有匹配到合适值的情况下主动抛错,而不是像在其他编程语言中那样默默地忽略它们:

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rect':
      return shape.width * shape.height;
    default:
      throw new Error('Unknown shape kind');
  }
}

通过使用never类型,静态类型检查就可以更早地查找到错误:

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rect':
      return shape.width * shape.height;
    default:
      // you'll get a type-checking error below          
      // if any shape.kind is not handled above
      const _exhaustiveCheck: never = shape;
      throw new Error('Unknown shape kind');
  }
}

有了这个,在添加新的shape​种类时,就不可能忘记更新getArea函数。

该技术背后的基本原理是,除了never​之外,不能为never​类型分配任何内容。如果shape.kind​的所有备选项都被case​语句用尽,那么达到default​的唯一可能类型是never​;但是,如果未涵盖所有备选项,则将泄漏到default分支并导致无效分配。

#7 宁可使用type而不是interface

在TypeScript中,type和interface​是两种非常相似的数据结构,都可以用来构造复杂的对象的。虽然可能有争议,但我的建议是在大多数情况下始终使用type,仅在满足以下任一条件时才使用interface:

  • 想利用interface的合并功能。
  • 有涉及类/接口层次结构的OO样式代码。

否则,始终使用更通用的type构造会产生更一致的代码。

#8 只要合适宁可使用元组而不是数组

对象类型是构造结构化数据的常用方法,但有时你可能希望使用更简洁的表示形式,而改用简单的数组。例如,Circle可以定义为:

type Circle = (string | number)[];
const circle: Circle = ['circle', 1.0];  // [kind, radius]

但是这种构造是松散的,如果创建类似['circle', '1.0']的内容很容易出错。我们可以通过使用元组来使其更严格:

type Circle = [string, number];

// you'll get an error below
const circle: Circle = ['circle', '1.0'];

使用元组的一个很好的例子是React中的useState。

const [name, setName] = useState('');

既紧凑又类型安全。

#9 控制推断类型的通用性或特殊性

TypeScript在进行类型推断时使用合理的默认行为,旨在使常见情况下的代码编写变得容易(因此类型不需要显式注释)。有几种方法可以调整其行为。

  • 使用const缩小到最具体的类型
let foo = { name: 'foo' }; // typed: { name: string }
let Bar = { name: 'bar' } as const; // typed: { name: 'bar' }

let a = [1, 2]; // typed: number[]
let b = [1, 2] as const; // typed: [1, 2]

// typed { kind: 'circle; radius: number }
let circle = { kind: 'circle' as const, radius: 1.0 };

// the following won't work if circle wasn't initialized
// with the const keyword
let shape: { kind: 'circle' | 'rect' } = circle;
  • 使用satisfies来检查类型,而不影响推断的类型

请看以下示例:

type NamedCircle = {
    radius: number;
    name?: string;
};

const circle: NamedCircle = { radius: 1.0, name: 'yeah' };

// error because circle.name can be undefined
console.log(circle.name.length);

有个错误,这是因为根据circle​的声明类型NamedCircle,name​字段确实可以未定义,即使变量初始值设定项提供了字符串值。当然,我们可以删除:NamedCircle​类型注释,但这将松散对circle对象有效性的类型检查。进退两难。

幸运的是,Typescript 4.9引入了一个新的satisfies关键字,它允许你在不更改推断类型的情况下检查类型:

type NamedCircle = {
    radius: number;
    name?: string;
};

// error because radius violates NamedCircle
const wrongCircle = { radius: '1.0', name: 'ha' }
    satisfies NamedCircle;

const circle = { radius: 1.0, name: 'yeah' }
    satisfies NamedCircle;

// circle.name can't be undefined now
console.log(circle.name.length);

修改后的版本具有两个优点:对象字面量保证符合NamedCircle类型,推断类型具有不可为空的名称字段。

#10 使用infer创建额外的泛型类型参数

设计实用工具函数和类型时,你经常会觉得需要使用从给定类型参数中提取的类型。在这种情况下,infer关键字就可以派上用场。它可以帮助快速推断新的类型参数。下面是两个简单的例子:

// gets the unwrApped type out of a Promise;
// idempotent if T is not Promise
type ResolvedPromise<T> = T extends Promise<infer U> ? U : T;
type t = ResolvedPromise<Promise<string>>; // t: string

// gets the flattened type of array T;
// idempotent if T is not array
type Flatten<T> = T extends Array<infer E> ? Flatten<E> : T;
type e = Flatten<number[][]>; // e: number

infer​关键字在T extends Promise<infer U>​中的工作原理可以理解为:假设T​与一些实例化的泛型Promise类型兼容,临时凑合一个类型参数U​以使其工作。因此,如果T​被实例化为Promise<string>​,则U​的解决方案将是string。

#11 创新类型操作以保持DRY

TypeScript提供了强大的类型操作语法和一组非常有用的实用程序,可帮助你将代码重复减少到最低限度。以下是一些简单示例:

与其重复字段声明:

type User = {
    age: number;
    gender: string;
    country: string;
    city: string
};
type Demographic = { age: number: gender: string; };
type Geo = { country: string; city: string; };

还不如使用pick实用程序提取新类型:

type User = {
    age: number;
    gender: string;
    country: string;
    city: string
};
type Demographic = Pick<User, 'age'|'gender'>;
type Geo = Pick<User, 'country'|'city'>;

与其复制函数的返回类型:

function createCircle() {
    return {
        kind: 'circle' as const,
        radius: 1.0
    }
}

function transformCircle(circle: { kind: 'circle'; radius: number }) {
    ...
}

transformCircle(createCircle());

还不如使用ReturnType<T>提取:

function createCircle() {
    return {
        kind: 'circle' as const,
        radius: 1.0
    }
}

function transformCircle(circle: ReturnType<typeof createCircle>) {
    ...
}

transformCircle(createCircle());

与其并行同步两种类型的shape​(此处为config​类型和Factory):
type ContentTypes = 'news' | 'blog' | 'video';

// config for indicating what content types are enabled
const config = { news: true, blog: true, video: false }
    satisfies Record<ContentTypes, boolean>;

// factory for creating contents
type Factory = {
    createNews: () => Content;
    createBlog: () => Content;
};

还不如使用映射类型和模板字面量类型根据config​的形状自动推断正确的factory类型:

type ContentTypes = 'news' | 'blog' | 'video';

// generic factory type with a inferred list of methods
// based on the shape of the given Config
type ContentFactory<Config extends Record<ContentTypes, boolean>> = {
    [k in string & keyof Config as Config[k] extends true
        ? `create${Capitalize<k>}`
        : never]: () => Content;
};

// config for indicating what content types are enabled
const config = { news: true, blog: true, video: false }
    satisfies Record<ContentTypes, boolean>;

type Factory = ContentFactory<typeof config>;
// Factory: {
//     createNews: () => Content;
//     createBlog: () => Content; 
// }

总结

这篇文章介绍了一系列TypeScript语言的高级应用。在实践中,你可能会发现直接这样用并不常见;但是,这些技术被大量用于那些专门为TypeScript而设计的库:如Prisma和tRPC。了解这些技巧可以帮助你更好地理解这些工具是发挥其威力的。



Tags:程序员   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
AI程序员上岗 垂类大模型应用迎来井喷期
能自动写代码的“AI员工”、逐渐告别不够好用的智能客服,无需费时费力开发的工业AI控制器&hellip;&hellip;随着人工智能大模型能力开始深入多个行业,IT、工业生产、金融、服务...【详细内容】
2024-04-07  Search: 程序员  点击:(4)  评论:(0)  加入收藏
首个AI程序员上岗,码农们暂且不必过度焦虑
“AI程序员上岗”或许是噱头,但淘汰焦虑仍然近在咫尺,需要积极面对。全文2418字,阅读约需7分钟 撰稿 / 马尔文(媒体人)编辑 / 何睿 校对 / 张彦君▲随着相关技术的突飞猛进,AI也在...【详细内容】
2024-04-07  Search: 程序员  点击:(5)  评论:(0)  加入收藏
被“摧毁”的数藏玩家:父母50万积蓄变电子垃圾,31岁程序员送外卖还网贷
最窘迫的时候,张晖把借款软件和信用卡都借了个遍,支付宝借呗16万的额度,他也全部用光了。真正压倒张晖的 ,还有一个他难以启齿的“秘密”:一年之内,父母50万的积蓄全部变成了数字...【详细内容】
2024-03-27  Search: 程序员  点击:(18)  评论:(0)  加入收藏
AI程序员Devin:通过了面试,但不一定适合职场
昨天,AI圈上演了第一场“大男主爽文”:一个少年成名的编程天才,组建起仅有10人的编程界奥林匹克“梦之队”,在全球瞩目的科技风暴正中心创办了一家公司。成立不到两个月,其推出的...【详细内容】
2024-03-18  Search: 程序员  点击:(14)  评论:(0)  加入收藏
微软AI程序员登场,10倍AI工程师真来了?996自主生成代码,性能超GPT-4 30%
新智元报道编辑:桃子 润【新智元导读】全球首个AI程序员Devin诞生之后,让码农纷纷恐慌。没想到,微软同时也整出了一个AI程序员&mdash;&mdash;AutoDev,能够自主生成、执行代码等...【详细内容】
2024-03-18  Search: 程序员  点击:(17)  评论:(0)  加入收藏
全球首位AI程序员诞生 人类程序员会失业吗?
近日,世界上第一位AI程序员Devin诞生,号称能自主学习新技术,自己改Bug,甚至它已经成功通过一家AI公司面试。消息一出,震撼整个科技圈。不少从业者在社交媒体留言说,担心Devin这类A...【详细内容】
2024-03-17  Search: 程序员  点击:(24)  评论:(0)  加入收藏
李彦宏称程序员职业将不复存在,周鸿祎:程序员热十年内不会减弱,AI时代更需要程序员
李彦宏称程序员职业将不复存在,周鸿祎:程序员热十年内不会减弱,AI时代更需要程序员3月10日,360创始人周鸿祎在社交平台上发文称,“大模型将替代程序员?未来不用学编程了么?我认为,程...【详细内容】
2024-03-11  Search: 程序员  点击:(22)  评论:(0)  加入收藏
李彦宏最新发声,“程序员”职业将不复存在!
在3月9日央视的《对话》&middot;开年说节目上,百度创始人、董事长兼CEO李彦宏表示,以后不会存在“程序员”这种职业了,因为只要会说话,人人都会具备程序员的能力。“未来的编程...【详细内容】
2024-03-11  Search: 程序员  点击:(18)  评论:(0)  加入收藏
编程二十年,38岁谷歌程序员的16条建议,涉创业、技术淘汰、拿大厂offer……
以能够让项目自负盈亏,并在这一约束下优化其增长的方式来管理和调整项目,这是这个世上最厉害的本事。距离我正式开始编程的工作已经过去二十年了。在这些年里,我有以下收获: 获...【详细内容】
2024-03-10  Search: 程序员  点击:(4)  评论:(0)  加入收藏
有了LLM,所有程序员都将转变为架构师?
编译 | 言征 出品 | 51CTO技术栈(微信号:blog51cto)生成式人工智能是否会取代人类程序员?可能不会。但使用生成式人工智能的人类可能会,可惜的是,现在还不是时候。目前,我们正在见...【详细内容】
2024-03-07  Search: 程序员  点击:(19)  评论:(0)  加入收藏
▌简易百科推荐
AI程序员上岗 垂类大模型应用迎来井喷期
能自动写代码的“AI员工”、逐渐告别不够好用的智能客服,无需费时费力开发的工业AI控制器&hellip;&hellip;随着人工智能大模型能力开始深入多个行业,IT、工业生产、金融、服务...【详细内容】
2024-04-07    千龙网  Tags:AI程序员   点击:(4)  评论:(0)  加入收藏
首个AI程序员上岗,码农们暂且不必过度焦虑
“AI程序员上岗”或许是噱头,但淘汰焦虑仍然近在咫尺,需要积极面对。全文2418字,阅读约需7分钟 撰稿 / 马尔文(媒体人)编辑 / 何睿 校对 / 张彦君▲随着相关技术的突飞猛进,AI也在...【详细内容】
2024-04-07    新京报  Tags:AI程序员   点击:(5)  评论:(0)  加入收藏
为何大语言模型不会取代码农?
译者 | 布加迪审校 | 重楼生成式人工智能(GenAI)会取代人类程序员吗?恐怕不会。不过,使用GenAI的人类可能会取代程序员。但是如今有这么多的大语言模型(LLM),实际效果不一而足。如...【详细内容】
2024-03-21    51CTO  Tags:大语言模型   点击:(23)  评论:(0)  加入收藏
AI程序员Devin:通过了面试,但不一定适合职场
昨天,AI圈上演了第一场“大男主爽文”:一个少年成名的编程天才,组建起仅有10人的编程界奥林匹克“梦之队”,在全球瞩目的科技风暴正中心创办了一家公司。成立不到两个月,其推出的...【详细内容】
2024-03-18    甲子光年  Tags:AI程序员   点击:(14)  评论:(0)  加入收藏
微软AI程序员登场,10倍AI工程师真来了?996自主生成代码,性能超GPT-4 30%
新智元报道编辑:桃子 润【新智元导读】全球首个AI程序员Devin诞生之后,让码农纷纷恐慌。没想到,微软同时也整出了一个AI程序员&mdash;&mdash;AutoDev,能够自主生成、执行代码等...【详细内容】
2024-03-18    新智元  Tags:AI程序员   点击:(17)  评论:(0)  加入收藏
李彦宏称程序员职业将不复存在,周鸿祎:程序员热十年内不会减弱,AI时代更需要程序员
李彦宏称程序员职业将不复存在,周鸿祎:程序员热十年内不会减弱,AI时代更需要程序员3月10日,360创始人周鸿祎在社交平台上发文称,“大模型将替代程序员?未来不用学编程了么?我认为,程...【详细内容】
2024-03-11    九派新闻  Tags:程序员   点击:(22)  评论:(0)  加入收藏
李彦宏最新发声,“程序员”职业将不复存在!
在3月9日央视的《对话》&middot;开年说节目上,百度创始人、董事长兼CEO李彦宏表示,以后不会存在“程序员”这种职业了,因为只要会说话,人人都会具备程序员的能力。“未来的编程...【详细内容】
2024-03-11    红星新闻  Tags:程序员   点击:(18)  评论:(0)  加入收藏
编程二十年,38岁谷歌程序员的16条建议,涉创业、技术淘汰、拿大厂offer……
以能够让项目自负盈亏,并在这一约束下优化其增长的方式来管理和调整项目,这是这个世上最厉害的本事。距离我正式开始编程的工作已经过去二十年了。在这些年里,我有以下收获: 获...【详细内容】
2024-03-10    CSDN  Tags:程序员   点击:(4)  评论:(0)  加入收藏
鸿蒙开发岗平均月薪超1.8万,成为计算机人才和程序员的求职新选择!
智联招聘发布的《2024年春招市场行情周报(第一期)》显示,2023年9-12月,鸿蒙相关职位数同比增速从33.8%攀升至216.1%,鸿蒙相关职位的投递人数同比增速从132.1%攀升至380.1%。春节...【详细内容】
2024-02-26  晓枫侃科技    Tags:鸿蒙开发   点击:(25)  评论:(0)  加入收藏
AI最先砸了程序员饭碗?90%码农认为找工作变难,大学生也要调整职业规划
图片来源:由无界 AI生成想不到AI最先影响到的打工人,居然是程序员。最近外媒一个有超过1万程序员参与的职业调查显示,90%的程序员都认为现在找工作变得更难了。仿佛一夜之间,程...【详细内容】
2024-01-12    新智元  Tags:程序员   点击:(80)  评论:(0)  加入收藏
站内最新
站内热门
站内头条