自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

讓你更好使用 Typescript 的11個技巧

開發(fā) 前端
雖然這種說法總體上是正確的,但隨著你的前進,會發(fā)現(xiàn)語言最不可思議的力量在于組成、推斷和操縱類型。本文將總結(jié)幾個技巧,幫助你充分發(fā)揮語言的潛力。

學習Typescript通常是一個重新發(fā)現(xiàn)的過程。最初印象可能很有欺騙性:這不就是一種注釋Javascript 的方式嗎,這樣編譯器就能幫助我找到潛在的bug?

雖然這種說法總體上是正確的,但隨著你的前進,會發(fā)現(xiàn)語言最不可思議的力量在于組成、推斷和操縱類型。

本文將總結(jié)幾個技巧,幫助你充分發(fā)揮語言的潛力。

將類型想象成集合

類型是程序員日常概念,但很難簡明地定義它。我發(fā)現(xiàn)用集合作為概念模型很有幫助。

例如,新的學習者發(fā)現(xiàn)Typescript組成類型的方式是反直覺的。舉一個非常簡單的例子:

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

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

如果你將 & 操作符解釋為邏輯與,你的可能會認為 Circle 是一個啞巴類型,因為它是兩個沒有任何重疊字段的類型的結(jié)合。這不是 TypeScript 的工作方式。相反,將其想象成集合會更容易推導(dǎo)出正確的行為:

  • 每種類型都是值的集合
  • 有些集合是無限的,如 string、object;有些是有限的,如 boolean、undefined,...
  • unknown 是通用集合(包括所有值),而 never 是空集合(不包括任何值)
  • Type Measure 是一個集合,包含所有包含名為 radius 的 number 字段的對象。Style 也是如此。
  • &運算符創(chuàng)建了交集:Measure & Style 表示包含 radius 和 color 字段的對象的集合,這實際上是一個較小的集合,但具有更多常用字段。
  • 同樣,|運算符創(chuàng)建了并集:一個較大的集合,但可能具有較少的常用字段(如果兩個對象類型組合在一起)

集合也有助于理解可分配性:只有當值的類型是目標類型的子集時才允許賦值:

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 {
...
}
}

使用帶有區(qū)分的聯(lián)合類型而不是可選字段

在定義一組多態(tài)類型(如 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 與其他字段之間沒有建立關(guān)系。相反,區(qū)分聯(lián)合是一個更好的解決方案:

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

類型收窄已經(jīng)消除了強制轉(zhuǎn)換的需要。

使用類型謂詞來避免類型斷言

如果你正確使用 TypeScript,你應(yīng)該很少會發(fā)現(xiàn)自己使用顯式類型斷言(例如 ??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[];

一個更優(yōu)雅的解決方案是將isCircle和isRect改為返回類型謂詞,這樣它們可以幫助Typescript在調(diào)用 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);

控制聯(lián)合類型的分布方式

類型推斷是Typescript的本能;大多數(shù)時候,它公默默地工作。但是,在模糊不清的情況下,我們可能需要干預(yù)。分配條件類型就是其中之一。

假設(shè)我們有一個ToArray輔助類型,如果輸入的類型不是數(shù)組,則返回一個數(shù)組類型。

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

你認為對于以下類型,應(yīng)該如何推斷?

type Foo = ToArray<string|number>;

答案是string[] | number[]。但這是有歧義的。為什么不是(string | number)[] 呢?

默認情況下,當typescript遇到一個聯(lián)合類型(這里是string | number)的通用參數(shù)(這里是T)時,它會分配到每個組成元素,這就是為什么這里會得到string[] | number[]。這種行為可以通過使用特殊的語法和用一對[]來包裝T來改變,比如。

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

現(xiàn)在,F(xiàn)oo 被推斷為類型(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類型,讓靜態(tài)類型檢查提前為你找到錯誤:

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函數(shù)。

這種技術(shù)背后的理由是,never? 類型除了 never? 之外不能賦值給任何東西。如果所有的 shape.kind? 候選者都被 case? 語句消耗完,到達default 的唯一可能的類型就是 never?;但是,如果有任何候選者沒有被覆蓋,它就會泄漏到 default 分支,導(dǎo)致無效賦值。

優(yōu)先選擇 type 而不是 interface

在 TypeScript 中,當用于對對象進行類型定義時,type? 和 interface? 構(gòu)造很相似。盡管可能有爭議,但我的建議是在大多數(shù)情況下一貫使用 type?,并且僅在下列情況之一為真時使用 interface:

你想利用interface的 "合并"功能。

你有遵循面向?qū)ο箫L格的代碼,其中包含類/接口層次結(jié)構(gòu)

否則,總是使用更通用的類型結(jié)構(gòu)會使代碼更加一致。

在適當?shù)臅r候優(yōu)先選擇元組而不是數(shù)組

對象類型是輸入結(jié)構(gòu)化數(shù)據(jù)的常見方式,但有時你可能希望有更多的表示方法,并使用簡單的數(shù)組來代替。例如,我們的Circle可以這樣定義:

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

但是這種類型檢查太寬松了,我們很容易通過創(chuàng)建類似 ['circle', '1.0'] 的東西而犯錯。我們可以通過使用 Tuple 來使它更嚴格:

type Circle = [string, number];

// 這里會得到一個錯誤
const circle: Circle = ['circle', '1.0'];

Tuple使用的一個好例子是React的useState:

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

它既緊湊又有類型安全。

控制推斷的類型的通用性或特殊性

在進行類型推理時,Typescript使用了合理的默認行為,其目的是使普通情況下的代碼編寫變得簡單(所以類型不需要明確注釋)。有幾種方法可以調(diào)整它的行為。

使用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關(guān)鍵字進行初始化,則以下內(nèi)容將無法正常工作
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);

我們遇到了錯誤,因為根據(jù)circle?的聲明類型NamedCircle?,name?字段確實可能是undefined?,即使變量初始值提供了字符串值。當然,我們可以刪除:NamedCircle?類型注釋,但我們將為circle對象的有效性丟失類型檢查。相當?shù)睦Ь场?/p>

幸運的是,Typescript 4.9 引入了一個新的satisfies關(guān)鍵字,允許你在不改變推斷類型的情況下檢查類型。

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創(chuàng)建額外的泛型類型參數(shù)

在設(shè)計實用功能和類型時,我們經(jīng)常會感到需要使用從給定類型參數(shù)中提取出的類型。在這種情況下,infer關(guān)鍵字非常方便。它可以幫助我們實時推斷新的類型參數(shù)。這里有兩個簡單的示例:

//  從一個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?關(guān)鍵字的工作方式可以理解為:假設(shè)T與某些實例化的通用Promise類型兼容,即時創(chuàng)建類型參數(shù)U?使其工作。因此,如果T?被實例化為Promise<string>?,則U的解決方案將是string。

通過在類型操作方面保持創(chuàng)造力來保持DRY(不重復(fù))

Typescript提供了強大的類型操作語法和一套非常有用的工具,幫助你把代碼重復(fù)率降到最低。

不是重復(fù)聲明:

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

不是重復(fù)函數(shù)的返回類型

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

而是使用Mapped Type?和Template Literal Type,根據(jù)配置的形狀自動推斷適當?shù)墓S類型。

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

總結(jié)

本文涵蓋了Typescript語言中的一組相對高級的主題。在實踐中,您可能會發(fā)現(xiàn)直接使用它們并不常見;然而,這些技術(shù)被專門為Typescript設(shè)計的庫大量使用:比如Prisma和tRPC。了解這些技巧可以幫助您更好地了解這些工具如何在引擎蓋下工作。

原文:https://dev.to/zenstack/11-tips-that-help-you-become-a-better-typescript-programmer-4ca1

責任編輯:武曉燕 來源: 大遷世界
相關(guān)推薦

2020-08-06 00:25:38

Python代碼開發(fā)

2023-03-27 23:57:25

JavaScrip開發(fā)技巧

2015-10-30 10:33:02

溝通程序員事業(yè)發(fā)展

2022-10-08 08:51:21

KDE PlasmaLinux 桌面Linux

2023-03-27 14:33:50

ChatGPT

2017-09-08 08:43:39

iOS 11SafariPDF

2020-03-31 09:47:04

Vue開發(fā)代碼

2009-10-27 09:09:06

Eclipse技巧

2023-04-21 07:53:38

2009-12-30 15:21:55

ADO.NET訪問

2023-02-06 16:46:59

JavaScript程序員技巧

2023-05-20 00:13:22

prompt團隊升職

2024-05-10 15:41:22

字符串TypeScrip

2024-06-06 08:06:26

2021-08-17 10:08:44

HTML網(wǎng)站網(wǎng)絡(luò)

2020-11-29 17:32:01

EmacsLinux

2019-04-29 08:31:25

PythonPandas數(shù)據(jù)

2015-05-15 13:25:43

Linux終端命令

2015-05-13 14:26:40

LinuxLinux終端命令

2021-06-07 17:46:31

Python 3.8Python編程語言
點贊
收藏

51CTO技術(shù)棧公眾號