iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >让你更好使用Typescript的11个技巧分享
  • 109
分享到

让你更好使用Typescript的11个技巧分享

Typescript使用技巧Typescript技巧 2023-01-05 12:01:34 109人浏览 独家记忆
摘要

目录将类型想象成集合理解类型声明和类型收窄使用带有区分的联合类型而不是可选字段使用类型谓词来避免类型断言控制联合类型的分布方式使用穷举式检查,在编译时捕捉未处理的情况优先选择 typ

学习typescript通常是一个重新发现的过程。最初印象可能很有欺骗性:这不就是一种注释javascript 的方式吗,这样编译器就能帮助我找到潜在的bug?

虽然这种说法总体上是正确的,但随着你的前进,会发现语言最不可思议的力量在于组成、推断和操纵类型。

本文将总结几个技巧,帮助你充分发挥语言的潜力。

将类型想象成集合

类型是程序员日常概念,但很难简明地定义它。我发现用集合作为概念模型很有帮助。

例如,新的学习者发现Typescript组成类型的方式是反直觉的。举一个非常简单的例子:

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

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

如果你将 & 操作符解释为逻辑,你的可能会认为 Circle 是一个哑巴类型,因为它是两个没有任何重叠字段的类型的结合。这不是 TypeScript 的工作方式。相反,将其想象成集合会更容易推导出正确的行为:

  • 每种类型都是值的集合
  • 有些集合是无限的,如 string、object;有些是有限的,如 boolean、undefined,...
  • unknown 是通用集合(包括所有值),而 never 是空集合(不包括任何值)
  • Type Measure 是一个集合,包含所有包含名为 radius 的 number 字段的对象。Style 也是如此。
  • &运算符创建了交集:Measure & Style 表示包含 radiuscolor 字段的对象的集合,这实际上是一个较小的集合,但具有更多常用字段。
  • 同样,|运算符创建了并集:一个较大的集合,但可能具有较少的常用字段(如果两个对象类型组合在一起)

集合也有助于理解可分配性:只有当值的类型是目标类型的子集时才允许赋值:

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

// 不允许,因为字符串不是 ShapeKind 的子集。
shape = foo;

// 允许,因为 ShapeKind 是字符串的子集。
foo = shape;

理解类型声明和类型收窄

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

function foo(x: string | number) {
  if (typeof x === 'string') {
    // x 的类型被缩小为字符串,所以.length是有效的
    console.log(x.length);

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

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

在定义一组多态类型(如 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!;
}

需要使用非空断言(在访问 radiuswidthheight 字段时),因为 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;
}

类型收窄已经消除了强制转换的需要。

使用类型谓词来避免类型断言

如果你正确使用 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();
// 错误,因为typescript不知道过滤的方式
const circles: Circle[] = myShapes.filter(isCircle);

// 你可能倾向于添加一个断言
// const circles = myShapes.filter(isCircle) as Circle[];

一个更优雅的解决方案是将isCircleisRect改为返回类型谓词,这样它们可以帮助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);

控制联合类型的分布方式

类型推断是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)[]

使用穷举式检查,在编译时捕捉未处理的情况

在对枚举进行 switch-case 操作时,最好是积极地对不期望的情况进行错误处理,而不是像在其他编程语言中那样默默地忽略它们:

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');
  }
}

使用Typescript,你可以通过利用never类型,让静态类型检查提前为你找到错误:

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rect':
      return shape.width * shape.height;
    default:
      // 如果任何shape.kind没有在上面处理
      // 你会得到一个类型检查错误。
      const _exhaustiveCheck: never = shape;
      throw new Error('Unknown shape kind');
  }
}

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

这种技术背后的理由是,never 类型除了 never 之外不能赋值给任何东西。如果所有的 shape.kind 候选者都被 case 语句消耗完,到达 default 的唯一可能的类型就是 never;但是,如果有任何候选者没有被覆盖,它就会泄漏到 default 分支,导致无效赋值。

优先选择 type 而不是 interface

在 TypeScript 中,当用于对对象进行类型定义时,typeinterface 构造很相似。尽管可能有争议,但我的建议是在大多数情况下一贯使用 type,并且仅在下列情况之一为真时使用 interface

  • 你想利用interface的 "合并"功能。
  • 你有遵循面向对象风格的代码,其中包含类/接口层次结构

否则,总是使用更通用的类型结构会使代码更加一致。

在适当的时候优先选择元组而不是数组

对象类型是输入结构化数据的常见方式,但有时你可能希望有更多的表示方法,并使用简单的数组来代替。例如,我们的Circle可以这样定义:

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

但是这种类型检查太宽松了,我们很容易通过创建类似 ['circle', '1.0'] 的东西而犯错。我们可以通过使用 Tuple 来使它更严格:

type Circle = [string, number];

// 这里会得到一个错误
const circle: Circle = ['circle', '1.0'];

Tuple使用的一个好例子是ReactuseState

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

它既紧凑又有类型安全

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

在进行类型推理时,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 };

// 如果circle没有使用const关键字进行初始化,则以下内容将无法正常工作
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的声明类型NamedCirclename字段确实可能是undefined,即使变量初始值提供了字符串值。当然,我们可以删除: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类型,并且推断出的类型有一个不可为空的名字字段。

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

在设计实用功能和类型时,我们经常会感到需要使用从给定类型参数中提取出的类型。在这种情况下,infer关键字非常方便。它可以帮助我们实时推断新的类型参数。这里有两个简单的示例:

//  从一个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

T extends Promise<infer U>中的infer关键字的工作方式可以理解为:假设T与某些实例化的通用Promise类型兼容,即时创建类型参数U使其工作。因此,如果T被实例化为Promise<string>,则U的解决方案将是string

通过在类型操作方面保持创造力来保持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());

不是并行地同步两种类型的形状(这里是typeof configFactory)。

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;
};

而是使用Mapped TypeTemplate Literal Type,根据配置的形状自动推断适当的工厂类型。

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。了解这些技巧可以帮助您更好地了解这些工具如何在引擎盖下工作。

以上就是让你更好使用Typescript的11个技巧分享的详细内容,更多关于Typescript技巧的资料请关注编程网其它相关文章!

--结束END--

本文标题: 让你更好使用Typescript的11个技巧分享

本文链接: https://www.lsjlt.com/news/176792.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 让你更好使用Typescript的11个技巧分享
    目录将类型想象成集合理解类型声明和类型收窄使用带有区分的联合类型而不是可选字段使用类型谓词来避免类型断言控制联合类型的分布方式使用穷举式检查,在编译时捕捉未处理的情况优先选择 typ...
    99+
    2023-01-05
    Typescript使用技巧 Typescript技巧
  • 五个实用技巧,让你更好地使用display
    探索display的五个实用技巧 在现代社会中,display(展示)已经成为了一个非常重要的概念。无论是在商业领域还是在个人生活中,我们都需要有一定的展示能力来吸引人们的注意力,让自己或者自己的产品得到更多的关注和认可。本文将...
    99+
    2024-02-02
    动画 字体
  • 分享11个常用JavaScript小技巧
    目录1.通过条件判断向对象添加属性2.检查对象中是否存在某个属性3.解构赋值4.循环遍历一个对象的key和value5.使用可选链(Optionalchaining)避免访问对象属性...
    99+
    2024-04-02
  • 分享9个好用的Python技巧
    目录1.引言2.Trick 13.Trick 24.Trick35.Trick46.Trick57.Trick68.Trick79.Trick810.Trick911.总结1.引言 ...
    99+
    2024-04-02
  • VSCode实用技巧分享:让你的编程生活更加便捷!
    VSCode实用技巧分享:让你的编程生活更加便捷! 近年来,随着编程行业的飞速发展,越来越多的程序员选择使用Visual Studio Code(简称VSCode)作为自己的主要集成开...
    99+
    2024-04-02
  • 分享7个成为更好的Vue开发者的技巧
    目录1、脚本设置2、如何覆盖反应对象3、反应式 CSS4、全局组件5、组合 API 优于选项 API6、使用 v-once 或 v-memo 提高性能7、组件的异步加载1、脚本设置 ...
    99+
    2024-04-02
  • PHP Laravel框架开发技巧分享:让你的代码更优雅
    1. 使用命名空间 命名空间可以帮助你组织和管理代码,避免命名冲突。在Laravel中,你可以使用namespace关键字来声明一个命名空间。例如: namespace AppHttpControllers; use Illumina...
    99+
    2024-02-06
    文章 PHP Laravel 框架 开发 技巧 命名空间 依赖注入 Eloquent ORM Blade模板引擎
  • 关于TypeScript开发的6六个实用小技巧分享
    目录1. 开发之前确定实体类型 2. 请求接口时只需要定义自己需要用到的字段 3. 使用枚举类型 4. DOM元素的类型要正常给 5.对象的类型要怎么给 6.结构赋值时类型怎么给 总...
    99+
    2024-04-02
  • 分享13个好用到起飞的Python技巧
    目录前言列表1. 将两个列表合并到一个字典中2.将两个或多个列表合并为一个列表3. 对字典列表进行排序4. 对字符串列表进行排序5. 根据另一个列表对列表进行排序6. 将列表映射到字...
    99+
    2024-04-02
  • 分享Python 的24个编程超好用技巧
    目录1、ALL OR ANY2、BASHPLOTIB3、COLLECTIONS4、DIR5、EMOJI6、FROM_FUTURE_IMPORT7、GEOPY8、HOWDOI9、INS...
    99+
    2024-04-02
  • 10个Python中Pip的使用技巧分享
    目录Python pip1.安装 pip2.升级 pip3.安装库4. 库的批量安装5.卸载和升级包6. 冻结 Python pip 依赖7.查看库信息8.查看需要升级的库9. 检查...
    99+
    2024-04-02
  • 你知道吗?这些技巧可以让你在Python中更好地使用numpy!
    Python作为一门高效的编程语言,其强大的科学计算库numpy在数据处理、分析、建模等领域得到了广泛应用。在Python中使用numpy可以提高数据处理的速度和效率,但是要想更好地使用numpy,需要熟悉一些技巧。本文将介绍一些在Pyth...
    99+
    2023-10-20
    numpy shell laravel
  • 分享ES6 20个经常使用技巧
    目录前言:1.打乱数组顺序2.删除数字之外的所有字符3.反转字符串或者单词4.将十进制转换为二进制文件或十六进制数5.合并多个对象6.=== 和 == ...
    99+
    2024-04-02
  • 分享几个JavaScript运算符的使用技巧
    目录一、可选链接运算符【?.】二、逻辑空分配(?? =)三、逻辑或分配(|| =)四、逻辑与分配(&& =)最后ECMAScript发展进程中,会有很...
    99+
    2024-04-02
  • 5个实用的Pandas技巧分享
    本篇内容主要讲解“5个实用的Pandas技巧分享”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“5个实用的Pandas技巧分享”吧!目录Pandas技巧1&...
    99+
    2024-04-02
  • 分享9个实用的Python技巧
    本篇内容介绍了“分享9个实用的Python技巧”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!清理字符串输入对用户输入进行清理的问题几乎适用于...
    99+
    2023-06-16
  • PHP分页技巧大揭秘:让你的网页更加易用
    随着互联网的快速发展,网页中的数据呈现越来越丰富,用户对于信息的获取需求也在不断增长。而随之而来的问题就是网页中数据量过大时如何更好地展示给用户,从而提高用户体验。而分页技术就成为了解...
    99+
    2024-03-01
    php分页 技巧揭秘 网页易用 sql语句
  • 18个帮你简化代码的Python技巧分享
    目录什么是单行代码为什么我需要它们开始1.if-else2. elif3. if4.函数5.循环(列表推导式)6. if 循环7. if else 循环8. While 循环与 if...
    99+
    2024-04-02
  • MyEclipse常用的使用技巧分享
    这篇文章主要介绍“MyEclipse常用的使用技巧分享”,在日常操作中,相信很多人在MyEclipse常用的使用技巧分享问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MyEclipse常用的使用技巧分享”的疑...
    99+
    2023-06-17
  • 常用的29个CSS小技巧分享
    本篇内容主要讲解“常用的29个CSS小技巧分享”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“常用的29个CSS小技巧分享”吧!1.清除图片下方出现几像素的空白间...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作