предисловие
Эта статья писалась за несколько месяцев до и после, а раньше я всегда учился кусками, а потом писал статьи с перебоями (лень), ссылался на множество статей и смотрел несколько тс видео, а потом вкладывал все базовые знания. Подводя итог, чувствую себя лучше, чем что-либо на Наггетс.typescript入门
Самые популярные статьи должны быть подробными, ха-ха, потому что я сделал ссылки на все эти популярные статьи, и содержание в основном включено.На этот раз это должно быть урегулировано. Молодец, что написал эту статью.
Раньше я не любил ts из-за определенных затрат на обучение, увеличения размера кода и повышения сложности кода.Позже я постепенно почувствовал, что статическая проверка ts позволяет разработчикам заранее находить ошибки, а сегодняшняя фронтенд-инженерная разработка действительно необходимо, потому что технический уровень членов команды различается,TypeScript
может помочь избежать многих ошибок, конечно, если выany大法
верующие, я призываю вас быть добрыми. не используйTypeScript
и использоватьTypeScript
, предпосылкой его использования должно быть то, что он может помочь вам решить конкретную проблему.
совет:
Не изучайте TypeScript, потому что это дешевое обучение
Не изучайте TypeScript, потому что это снижает командную коммуникацию.
Не изучайте TypeScript, потому что он делает ваш код более надежным
Не изучайте TypeScript, потому что это поможет вам быстро освоить другие внутренние языки.
Не изучайте TypeScript, потому что вы будете одержимы им
~ 唉,大势所趋,这玩意现在是一定要学的了!
Введение в машинописный текст
что такое машинопись?
TypeScript для коротких TS
Отношения между TS и JS на самом деле являются отношениями между Less/Sass и CSS.
Подобно тому, как Less/Sass является расширением CSS, TS также является расширением JS.
Точно так же, как Less/Sass в конечном итоге будет преобразован в CSS, код TS, который мы пишем, в конечном итоге будет преобразован в JS.
TypeScript — это надмножество JavaScript, потому что он расширяет JavaScript и имеет то, чего нет в JavaScript.
С точки зрения отношения родитель-потомок, TypeScript является подклассом JavaScript, который расширяется на основе наследования.
Зачем вам нужен TypeScript?
Проще говоря, поскольку JavaScript слабо типизирован, многие ошибки обнаруживаются только во время выполнения.
TypeScript предоставляет механизм статического обнаружения, который может помочь нам найти ошибки во время компиляции.
Особенности TypeScript
Поддержка новейших функций JavaScript
Статическая проверка кода поддержки
Поддержка функций на бэкэнд-языках, таких как C, C++, Java, Go и т. д. (перечисления, дженерики, преобразования типов, пространства имен, файлы объявлений, классы, интерфейсы и т. д.)
Создайте среду обучения машинописи
Установите последнюю версию машинописного текста
npm i -g typescript
Установить ts-узел
npm i -g ts-node
Создайте файл tsconfig.json
tsc --init
Затем создайте новый index.ts, введите соответствующий код упражнения, а затем выполните ts-node index.ts.
официальная площадка
Официальный также предоставляет облачную среду для онлайн-разработки TypeScript —Playground.
Исходя из этого, нам не нужно устанавливать среду локально, нам нужен только браузер, чтобы в любой момент выучить и написать TypeScript, при этом мы можем легко выбрать версию TypeScript, настроить tsconfig и выполнять в реальном времени определение статического типа для TypeScript, перевод и вывод JavaScript и онлайн-исполнение.
А по опыту вообще не уступает ни одной локальной IDE, хороший выбор для нас, только изучающих TypeScript.
базовый тип данных
Восемь встроенных типов JS
let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
let sym: symbol = Symbol("me");
будь осторожен
нулевой и неопределенный
по умолчанию null
и undefined
является подвидом всех типов. То есть можно поставитьnull
и undefined
Присвоить другим типам.
// null和undefined赋值给string
let str:string = "666";
str = null
str= undefined
// null和undefined赋值给number
let num:number = 666;
num = null
num= undefined
// null和undefined赋值给object
let obj:object ={};
obj = null
obj= undefined
// null和undefined赋值给Symbol
let sym: symbol = Symbol("me");
sym = null
sym= undefined
// null和undefined赋值给boolean
let isDone: boolean = false;
isDone = null
isDone= undefined
// null和undefined赋值给bigint
let big: bigint = 100n;
big = null
big= undefined
Если указать в tsconfig.json"strictNullChecks":true
,null
и undefined
может быть назначен толькоvoid
и соответствующие им виды.
число и bigint
Несмотря на то чтоnumber
иbigint
Оба представляют числа, но эти два типа несовместимы.
let big: bigint = 100n;
let num: number = 6;
big = num;
num = big;
выдаст ошибку несовместимого типа ts(2322).
другие типы
Array
Есть два способа определить тип массива:
let arr:string[] = ["1","2"];
let arr2:Array<string> = ["1","2"];
определить массив типов объединения
let arr:(number | string)[];
// 表示定义了一个名称叫做arr的数组,
// 这个数组中将来既可以存储数值类型的数据, 也可以存储字符串类型的数据
arr3 = [1, 'b', 2, 'c'];
Определите массив указанных членов объекта:
// interface是接口,后面会讲到
interface Arrobj{
name:string,
age:number
}
let arr3:Arrobj[]=[{name:'jimmy',age:22}]
функция
объявление функции
function sum(x: number, y: number): number {
return x + y;
}
функциональное выражение
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
Определение типов функций с интерфейсами
interface SearchFunc{
(source: string, subString: string): boolean;
}
При использовании интерфейса функционального выражения для определения функции ограничение типа слева от знака равенства может гарантировать, что количество параметров, типов параметров и типов возвращаемых значений останутся неизменными при назначении имени функции в будущем.
необязательный параметр
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
Примечание. Обязательные параметры не допускаются после необязательных параметров.
значение параметра по умолчанию
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
остальные параметры
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
перегрузка функций
Поскольку JavaScript — динамический язык, мы обычно вызываем одну и ту же функцию с разными типами параметров, и функция будет возвращать разные типы результатов вызова в соответствии с разными параметрами:
function add(x, y) {
return x + y;
}
add(1, 2); // 3
add("1", "2"); //"12"
Поскольку TypeScript является надмножеством JavaScript, приведенный выше код можно использовать непосредственно в TypeScript, но при включенном компиляторе TypeScriptnoImplicitAny
Когда элемент конфигурации , приведенный выше код вызовет следующее сообщение об ошибке:
Parameter 'x' implicitly has an 'any' type.
Parameter 'y' implicitly has an 'any' type.
Эта информация говорит нам, что параметр x и параметр y неявно имеютany
тип. Чтобы решить эту проблему, мы можем установить тип параметра. потому что мы надеемсяadd
Функция поддерживает как строковые, так и числовые типы, поэтому мы можем определитьstring | number
Тип объединения, и мы берем псевдоним для типа объединения:
type Combinable = string | number;
После определения типа объединения Combinable обновим егоadd
функция:
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
за add
После явного ввода параметра функции предыдущее сообщение об ошибке исчезает. Тогда в это времяadd
Функция идеальна, давайте на самом деле протестируем ее:
const result = add('Semlinker', ' Kakuqo');
result.split(' ');
В приведенном выше коде мы используем'Semlinker'
и ' Kakuqo'
Эти две строки используются в качестве параметров для вызова функции добавления и сохранения результата вызова в файл с именемresult
Что касается переменной , в настоящее время мы считаем само собой разумеющимся, что тип переменной результата является строкой, поэтому мы обычно можем вызывать переменную для строкового объекта.split
метод. Но затем компилятор TypeScript выдал следующее сообщение об ошибке:
Property 'split' does not exist on type 'number'.
Это понятно number
не существует для объекта типаsplit
Атрибуты. Проблема возникает снова, как ее решить? В настоящее время мы можем воспользоваться функцией перегрузки функций, предоставляемой TypeScript.
Перегрузка функций или перегрузка методов — это возможность создавать несколько методов с одинаковыми именами и разным количеством или типами параметров.Чтобы решить проблему, обнаруженную ранее, метод заключается в предоставлении нескольких определений типа функции для одной и той же функции для выполнения перегрузки функции, и компилятор будет обрабатывать вызов функции в соответствии с этим списком.
type Types = number | string
function add(a:number,b:number):number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a:Types, b:Types) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
const result = add('Semlinker', ' Kakuqo');
result.split(' ');
В приведенном выше коде мы предоставляем определения нескольких типов функций для функции добавления для реализации перегрузки функций. После этого неприятное сообщение об ошибке снова исчезло, потому что тогда результирующая переменная имела типstring
тип.
Кортеж (кортеж)
Определение предка
Как мы все знаем, массивы обычно состоят из значений одного типа, но иногда нам нужно хранить значения разных типов в одной переменной, и в этом случае мы можем использовать кортежи. В JavaScript нет кортежей, кортежи — это специфичные для TypeScript типы, которые работают как массивы.
Наиболее важной особенностью кортежей является то, что они могут ограничивать数组元素的个数和类型
, который особенно подходит для реализации возврата нескольких значений.
Кортеж используется для хранения данных фиксированной длины и фиксированного типа данных.
let x: [string, number];
// 类型必须匹配且个数必须为2
x = ['hello', 10]; // OK
x = ['hello', 10,10]; // Error
x = [10, 'hello']; // Error
Обратите внимание, что тип кортежа может представлять только массив с известным числом и типом элементов, длина которого указана, а доступ за пределами границ вызовет ошибку. Если в массиве может быть несколько типов, число и тип которых неопределенны, то сразуany[]
Разрушение присваивания типов кортежей
Мы можем получить доступ к элементам в кортеже по подписке, когда в кортеже много элементов, этот способ не так удобен. На самом деле, кортежи также поддерживают деструктурирующее присваивание:
let employee: [number, string] = [1, "Semlinker"];
let [id, username] = employee;
console.log(`id: ${id}`);
console.log(`username: ${username}`);
После успешного выполнения приведенного выше кода консоль выведет следующее сообщение:
id: 1
username: Semlinker
Здесь следует отметить, что при деструктурирующем присваивании, если количество деструктурирующих элементов массива не может превышать количество элементов в кортеже, также будет возникать ошибка, например:
let employee: [number, string] = [1, "Semlinker"];\
let [id, username, age] = employee;
В приведенном выше коде мы добавили переменную возраста, но в это время компилятор TypeScript выдаст следующее сообщение об ошибке:
Tuple type '[number, string]' of length '2' has no element at index '2'.
Очевидно, тип кортежа[number, string]
Длина2
, по индексу позиции2
Ни один элемент не существует в .
необязательные элементы типа кортежа
В отличие от типов сигнатур функций, при определении типов кортежей мы также можем передавать?
число для объявления необязательных элементов типа кортежа, конкретные примеры приведены ниже:
let optionalTuple: [string, boolean?];
optionalTuple = ["Semlinker", true];
console.log(`optionalTuple : ${optionalTuple}`);
optionalTuple = ["Kakuqo"];
console.log(`optionalTuple : ${optionalTuple}`);
В приведенном выше коде мы определяемoptionalTuple
Для типа переменной требуется обязательный строковый атрибут и необязательный логический атрибут.После нормального выполнения кода консоль выведет следующее:
optionalTuple : Semlinker,true
optionalTuple : Kakuqo
Так что же делает на практике объявление необязательных элементов кортежа? Здесь мы приводим пример, в трехмерной оси координат точка координат может использовать(x, y, z)
В виде , для двумерной оси координат, точка координат может использовать(x, y)
для представления в виде , а для одномерных координатных осей просто используйте(x)
можно выразить в виде . В ответ на эту ситуацию в TypeScript вы можете использовать характеристики необязательного элемента типа кортежа, чтобы определить координатную точку типа кортежа.Конкретная реализация выглядит следующим образом:
type Point = [number, number?, number?];
const x: Point = [10]; // 一维坐标点
const xy: Point = [10, 20]; // 二维坐标点
const xyz: Point = [10, 20, 10]; // 三维坐标点
console.log(x.length); // 1
console.log(xy.length); // 2
console.log(xyz.length); // 3
остальные элементы типа tuple
Последний элемент в типе кортежа может быть оставшимся элементом в форме...X
,здесь X
является типом массива.Остальные элементы означают, что тип кортежа является открытым и может иметь ноль или более дополнительных элементов.Например,[number, ...string[]]
значит сnumber
элементы и любое количествоstring
Тип кортежа элемента type. Для лучшего понимания возьмем конкретный пример:
type RestTupleType = [number, ...string[]];
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];
console.log(restTuple[0]);
console.log(restTuple[1]);
тип кортежа только для чтения
TypeScript 3.4 также представил новую поддержку кортежей только для чтения. Мы можем добавить к любому типу кортежаreadonly
префикс ключевого слова, чтобы сделать его доступным только для чтения кортежем. Конкретные примеры следующие:
const point: readonly [number, number] = [10, 20];
в настоящее время используетreadonly
После изменения типа кортежа с помощью ключевого слова любая попытка изменить элементы кортежа вызовет исключение:
// Cannot assign to '0' because it is a read-only property.
point[0] = 1;
// Property 'push' does not exist on type 'readonly [number, number]'.
point.push(0);
// Property 'pop' does not exist on type 'readonly [number, number]'.
point.pop();
// Property 'splice' does not exist on type 'readonly [number, number]'.
point.splice(1, 1);
void
void
Указывает, что типа нет, он равен другим типам и не может быть напрямую назначен:
let a: void;
let b: number = a; // Error
ты можешь только дать этоnull
иundefined
(существуетstrictNullChecks
если не указано как истинное). объявить одинvoid
Переменные типа не очень полезны, и мы обычно объявляем их только тогда, когда функция не возвращает значение.
Стоит отметить, что методы без возвращаемого значения получатundefined
, но нам нужно определить его какvoid
введите вместоundefined
тип. В противном случае будет сообщено об ошибке:
function fun(): undefined {
console.log("this is TypeScript");
};
fun(); // Error
never
never
Типы представляют типы значений, которые никогда не существуют.
Две ситуации, когда значение никогда не будет существовать:
- Если функция выполняется и бросаетаномальный, то у этой функции никогда не будет возвращаемого значения (поскольку выбрасывание исключения приведет к прямому прерыванию программы, из-за чего программа будет работать меньше, чем возвращаемое значение, то есть она имеет недостижимую конечную точку, и возврата никогда не будет) ;
- Код в функции, которая выполняет бесконечный цикл (бесконечный цикл), так что программа никогда не сможет дойти до шага, на котором функция возвращает значение, и никогда не будет возврата.
// 异常
function err(msg: string): never { // OK
throw new Error(msg);
}
// 死循环
function loopForever(): never { // OK
while (true) {};
}
never
того же типаnull
иundefined
Точно так же он также является подтипом любого типа и может быть присвоен любому типу.
но типа нетnever
подтип или назначаемыйnever
типа (кромеnever
себя), даже еслиany
не может быть назначеноnever
let ne: never;
let nev: never;
let an: any;
ne = 123; // Error
ne = nev; // OK
ne = an; // Error
ne = (() => { throw new Error("异常"); })(); // OK
ne = (() => { while(true) {} })(); // OK
В TypeScript функция «никогда не вводить» может использоваться для реализации комплексных проверок. Конкретный пример выглядит следующим образом:
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
Обратите внимание, что внутри ветки else мы присваиваем foo, суженную до never, явно объявленной переменной never. Если все логически правильно, это должно скомпилироваться и пройти. Но предположим, что однажды ваш коллега изменит тип Foo:
type Foo = string | number | boolean;
Однако он забыл изменить в то же времяcontrolFlowAnalysisWithNever
Поток управления в методе, в это время тип foo ветви else будет сужен доboolean
type, так что его нельзя будет присвоить типу never, и будет сгенерирована ошибка компиляции. Таким образом, мы можем гарантировать, чтоcontrolFlowAnalysisWithNever
методы всегда исчерпывают все возможные типы Foo . Из этого примера можно сделать вывод:Используйте never, чтобы избежать добавления нового типа объединения без соответствующей реализации, и цель состоит в том, чтобы написать код, который абсолютно безопасен для типов.
any
В TypeScript любой тип может быть классифицирован как любой тип. Это делает тип any типом верхнего уровня системы типов.
Если это нормальный тип, изменение типа во время присваивания не допускается:
let a: string = 'seven';
a = 7;
// TS2322: Type 'number' is not assignable to type 'string'.
Но еслиany
тип, его можно присвоить любому типу.
let a: any = 666;
a = "Semlinker";
a = false;
a = 66
a = undefined
a = null
a = []
a = {}
Доступ к любому свойству на любом разрешен, а также вызов любого метода.
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
Если переменная объявлена без указания ее типа, она будет распознана как значение любого типа.:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
Эквивалентно
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
Во многих сценариях это слишком свободно. использоватьany
типа, легко написать код, который правильно типизирован, но имеет проблемы во время выполнения. если мы используемany
тип, вы не можете использовать расширенные механизмы защиты, предоставляемые TypeScript. пожалуйста, помните,any 是魔鬼!
Старайтесь не использовать никакие.
чтобы решитьany
Проблема, которую представил TypeScript 3.0unknown
тип.
unknown
unknown
иany
Например, все типы могут быть назначеныunknown
:
let notSure: unknown = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK
unknown
иany
Самая большая разница:
Любой тип значения может быть присвоенany
,в то же времяany
Значение типа также может быть присвоено любому типу.unknown
Ему можно присвоить любой тип значения, но присвоить его можно толькоunknown
иany
let notSure: unknown = 4;
let uncertain: any = notSure; // OK
let notSure: any = 4;
let uncertain: unknown = notSure; // OK
let notSure: unknown = 4;
let uncertain: number = notSure; // Error
Если вы не сузите тип, вы не сможетеunknown
Введите, чтобы сделать что-нибудь:
function getDog() {
return '123'
}
const dog: unknown = {hello: getDog};
dog.hello(); // Error
Этот механизм очень превентивен и безопасен, что требует, чтобы мы сузили тип, мы можем использоватьtypeof
,类型断言
и т. д., чтобы сузить неизвестный диапазон:
function getDogName() {
let x: unknown;
return x;
};
const dogName = getDogName();
// 直接使用
const upName = dogName.toLowerCase(); // Error
// typeof
if (typeof dogName === 'string') {
const upName = dogName.toLowerCase(); // OK
}
// 类型断言
const upName = (dogName as string).toLowerCase(); // OK
Число, строка, логическое значение, символ
Во-первых, давайте рассмотрим написанные с заглавной буквы типы Number, String, Boolean и Symbol, которые легко спутать с примитивными типами number, string, boolean и symbol при изучении TypeScript.Последний является соответствующим примитивным типом.包装对象
, назовем их объектными типами.
С точки зрения совместимости типов типы-примитивы совместимы с соответствующими типами объектов, а типы объектов, в свою очередь, несовместимы с соответствующими типами-примитивами.
Давайте рассмотрим конкретный пример:
let num: number;
let Num: Number;
Num = num; // ok
num = Num; // ts(2322)报错
В строке 3 примера мы могли бы присвоить номер типу Number, но присвоение Number типу Number в строке 4 дает ошибку ts(2322).
Поэтому нам нужно иметь в виду, что нельзя аннотировать тип значения с типом объекта, потому что это не имеет никакого смысла.
объект, объект и {}
Кроме того, объект (исходная строчная буква, далее «маленький объект»), Объект (начальная строчная буква, далее «большой объект») и {} (далее «пустой объект»)
Малый объект представляет все непримитивные типы, что означает, что мы не можем назначать примитивные типы, такие как число, строка, логическое значение, символ и т. д., объекту. В строгом режиме,null
иundefined
Типы также не могут быть присвоены объекту.
Следующие типы считаются примитивными типами в JavaScript:
string
,boolean
,number
,bigint
,symbol
,null
иundefined
.
Давайте рассмотрим конкретный пример:
let lowerCaseObject: object;
lowerCaseObject = 1; // ts(2322)
lowerCaseObject = 'a'; // ts(2322)
lowerCaseObject = true; // ts(2322)
lowerCaseObject = null; // ts(2322)
lowerCaseObject = undefined; // ts(2322)
lowerCaseObject = {}; // ok
Строки со 2 по 6 в этом примере вызывают ошибку ts(2322), но после того, как мы назначаем пустой объект объекту в строке 7, он может пройти определение статического типа.
Большой объект представляет все типы с помощью методов toString и hasOwnProperty, поэтому все примитивные и непримитивные типы могут быть назначены объекту. Аналогично, в строгом режиме Object не могут быть присвоены типы null и undefined.
Давайте также рассмотрим конкретный пример:
let upperCaseObject: Object;
upperCaseObject = 1; // ok
upperCaseObject = 'a'; // ok
upperCaseObject = true; // ok
upperCaseObject = null; // ts(2322)
upperCaseObject = undefined; // ts(2322)
upperCaseObject = {}; // ok
В примере строки со 2 по 4 и строку 7 могут пройти определение статического типа, а строки с 5 по 6 вызовут ошибку ts(2322).
Как видно из приведенного выше примера, большой объект содержит примитивные типы, а маленький объект содержит только непримитивные типы, поэтому большой объект кажется родительским типом маленького объекта. На самом деле большой объект является не только надтипом маленького объекта, но и подтипом маленького объекта.
Ниже мы по-прежнему используем конкретный пример для иллюстрации.
type isLowerCaseObjectExtendsUpperCaseObject = object extends Object ? true : false; // true
type isUpperCaseObjectExtendsLowerCaseObject = Object extends object ? true : false; // true
upperCaseObject = lowerCaseObject; // ok
lowerCaseObject = upperCaseObject; // ok
В примере возвращаемые типы в строках 1 и 2 оба являются истинными, а upperCaseObject и lowerCaseObject в строках 3 и 4 могут быть назначены друг другу.
Примечание. Хотя в официальной документации говорится, что вместо больших объектов можно использовать маленькие объекты, нам все же нужно понимать, что большие объекты не полностью эквивалентны маленьким объектам.
Тип объекта {}null, как и большой объект, также представляет собой набор примитивных и непримитивных типов, и в строгом режиме null и undefined не могут быть присвоены {} , как в следующем примере:
let ObjectLiteral: {};
ObjectLiteral = 1; // ok
ObjectLiteral = 'a'; // ok
ObjectLiteral = true; // ok
ObjectLiteral = null; // ts(2322)
ObjectLiteral = undefined; // ts(2322)
ObjectLiteral = {}; // ok
type isLiteralCaseObjectExtendsUpperCaseObject = {} extends Object ? true : false; // true
type isUpperCaseObjectExtendsLiteralCaseObject = Object extends {} ? true : false; // true
upperCaseObject = ObjectLiteral;
ObjectLiteral = upperCaseObject;
В примере возвращаемые типы в строках 8 и 9 являются истинными, ObjectLiteral и upperCaseObject в строках 10 и 11 могут быть присвоены друг другу, а операции присваивания в строках 2–4 и 7 соответствуют определению статического типа; и строки 5 и 6 вызовут ошибку ts(2322).
Подводя итог выводу: {}, большой объект является более широким типом (наименее специфичным), чем маленький объект, {} и большой объект могут быть заменены друг другом, использоваться для представления примитивных типов (кроме нулевого, неопределенного) и непримитивных типов. типы; в то время как small object представляет непримитивный тип.
вывод типа
{
let str: string = 'this is string';
let num: number = 1;
let bool: boolean = true;
}
{
const str: string = 'this is string';
const num: number = 1;
const bool: boolean = true;
}
Глядя на приведенный выше пример, вы, возможно, бормотали: «Переменные, определяющие базовые типы, должны быть аннотациями типов. В примере, когда мы используем let для определения переменной, нам нужно только написать аннотацию типа, ведь значение может быть изменено. Однако, используяconst
Вам также нужно написать аннотации типов для констант, что действительно проблематично.
На самом деле в TypeScript давно придумали такую простую и очевидную проблему.
Во многих случаях TypeScript автоматически выводит тип переменной на основе контекста, и нам не нужно писать аннотации типов. Таким образом, приведенный выше пример можно упростить до чего-то вроде этого:
{
let str = 'this is string'; // 等价
let num = 1; // 等价
let bool = true; // 等价
}
{
const str = 'this is string'; // 不等价
const num = 1; // 不等价
const bool = true; // 不等价
}
Мы называем способность TypeScript выводить типы на основе выражений присваивания как类型推断
.
В TypeScript переменные с инициализированными значениями, параметры функции со значениями по умолчанию и тип, возвращаемый функцией, могут быть выведены из контекста. Например, мы можем вывести тип, возвращаемый функцией, из оператора return, как показано в следующем коде:
{
/** 根据参数的类型,推断出返回值的类型也是 number */
function add1(a: number, b: number) {
return a + b;
}
const x1= add1(1, 1); // 推断出 x1 的类型也是 number
/** 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字 */
function add2(a: number, b = 1) {
return a + b;
}
const x2 = add2(1);
const x3 = add2(1, '1'); // ts(2345) Argument of type "1" is not assignable to parameter of type 'number | undefined
}
Если при его определении нет присвоения, оно будет выводиться какany
введите без проверки типа вообще:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
утверждение типа
Иногда вы столкнетесь с ситуацией, когда вы знаете больше деталей о значении, чем TypeScript. Обычно это происходит, когда вы четко знаете, что сущность имеет более точный тип, чем ее существующий тип.
Утверждения типов — это способ сказать компилятору: «Поверь мне, я знаю, что делаю». Утверждения типов аналогичны преобразованиям типов в других языках, но без специальной проверки данных и деструктурирования. Это не влияет на время выполнения, оно просто работает во время компиляции.
Распознавание типов TypeScript не может быть абсолютно интеллектуальным, в конце концов, программы не могут думать как люди. Иногда мы сталкиваемся с ситуациями, когда мы знаем фактический тип лучше, чем TypeScript, как в следующем примере:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2); // 提示 ts(2322)
где большеThan2 должно быть числом (3, если быть точным), потому что очевидно, что в arrayNumber есть члены, которые больше 2, но статическая типизация бессильна для логики времени выполнения.
С точки зрения TypeScript, тип greatThan2 может быть либо числом, либо неопределенным, поэтому приведенный выше пример вызывает ошибку ts(2322), после чего мы не можем присвоить тип undefined типу number.
Однако мы можем использовать безошибочный способ -утверждение типа(аналогично приведению только типов) сообщаем TypeScript о необходимости проверки типов по-нашему.
Например, мы можем использовать синтаксис as для утверждения типа, как показано в следующем коде:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number;
грамматика
// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
Хотя между двумя вышеуказанными методами нет разницы, формат угловых скобок вызовет синтаксический конфликт с JSX в реакции, поэтому мы предпочитаем использовать синтаксис as.
ненулевое утверждение
Новый оператор постфиксного выражения в контексте, когда проверка типов не может определить тип!
Может использоваться для подтверждения того, что объект операции не имеет нулевого и неопределенного типа.В частности, x!исключает null и undefined из диапазона x.
Подробности смотрите в следующих примерах:
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)
type NumGenerator = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}
детерминированное утверждение присваивания
Позволяет помещать свойство после экземпляра и объявления переменных!
знак, который сообщает TypeScript, что свойство будет назначено явно. Чтобы лучше понять, что он делает, давайте рассмотрим конкретный пример:
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error
function initialize() {
x = 10;
}
Очевидно, что сообщение об исключении заключается в том, что переменная x используется перед присваиванием.Чтобы исправить это, мы можем использовать детерминированное утверждение присваивания:
let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
x = 10;
}
пройти через let x!: number;
С определенным утверждением присваивания компилятор TypeScript знает, что свойству определенно присвоено значение.
литеральный тип
В TypeScript литералы могут представлять не только значения, но и типы, так называемые литеральные типы.
В настоящее время TypeScript поддерживает три типа литералов: строковые литералы, числовые литералы и логические литералы. Соответствующие строковые литералы, числовые литералы и логические литералы имеют те же типы литералов, что и их значения, соответственно. Конкретные примеры:
{
let specifiedStr: 'this is string' = 'this is string';
let specifiedNum: 1 = 1;
let specifiedBoolean: true = true;
}
Например, «это строка» (представляющая здесь тип строкового литерала) имеет тип строки (в частности, подтип строки типа), и тип строки не обязательно является «это строка» (представляющий здесь строковый литерал) Тип количества) тип, следующий конкретный пример:
{
let specifiedStr: 'this is string' = 'this is string';
let str: string = 'any string';
specifiedStr = str; // ts(2322) 类型 '"string"' 不能赋值给类型 'this is string'
str = specifiedStr; // ok
}
Например, мы используем «лошадь» в качестве метафоры строкового типа, то есть «темная лошадка» относится к типу «это строка», «темная лошадка» должна быть «лошадью», но «лошадь» не обязательно « темная лошадка», это еще может быть «белая лошадь», «серая лошадь». Таким образом, литеральный тип «это строка» может присваивать значения строковому типу, но строковый тип не может присваивать значения литеральному типу «это строка».Эта аналогия также подходит для описания отношений между числами. , булевы и другие литералы и их родительские классы.
Тип строкового литерала
В общем, мы можем использовать тип строкового литерала в качестве типа переменной, как показано в следующем коде:
let hello: 'hello' = 'hello';
hello = 'hi'; // ts(2322) Type '"hi"' is not assignable to type '"hello"'
На самом деле, определение одного литерального типа не очень полезно, его реальный сценарий применения состоит в объединении нескольких литеральных типов в тип объединения (описан ниже), который используется для практического описания коллекций с явными членами.
Как показано в приведенном ниже коде, мы описываем недвусмысленную коллекцию «вверх» и «вниз», используя тип буквального объединения, чтобы точно знать, какая структура данных нам нужна.
type Direction = 'up' | 'down';
function move(dir: Direction) {
// ...
}
move('up'); // ok
move('right'); // ts(2345) Argument of type '"right"' is not assignable to parameter of type 'Direction'
Используя тип объединения комбинации литеральных типов, мы можем ограничить параметры функции указанным набором литеральных типов, а затем компилятор проверит, являются ли параметры членами указанного набора литеральных типов.
Следовательно, использование литерального типа (комбинированного типа объединения) может ограничить параметры функции более конкретным типом, чем использование строкового типа. Это не только улучшает читабельность программы, но и обеспечивает тип параметра функции, который убивает двух зайцев одним выстрелом.
Числовые литеральные типы и логические литеральные типы
Использование числовых литеральных типов и логических литеральных типов аналогично использованию строковых литеральных типов.Мы можем использовать тип объединения литеральной композиции, чтобы ограничить параметры функции более конкретными типами, такими как объявление типа Config, как показано ниже. :
interface Config {
size: 'small' | 'big';
isEnable: true | false;
margin: 0 | 2 | 4;
}
В приведенном выше коде мы ограничиваем свойство size строковым литералом типа «маленький» | «большой», а свойство isEnable — логическим литералом типа true | false (логический литерал содержит только true и false, комбинацию true | false, за которой следует Использование логического значения напрямую не имеет значения), свойство margin является числовым литералом типа 0 | 2 | 4.
пусть и константный анализ
Давайте сначала посмотрим на пример const, как показано в следующем коде:
{
const str = 'this is string'; // str: 'this is string'
const num = 1; // num: 1
const bool = true; // bool: true
}
В приведенном выше коде мы определяем const как неизменяемую константу.В случае аннотации типа по умолчанию TypeScript делает вывод, что его тип напрямую определяется типом назначенного литерала, что также является более разумным дизайном.
Далее мы рассмотрим пример let, показанный ниже:
{
let str = 'this is string'; // str: string
let num = 1; // num: number
let bool = true; // bool: boolean
}
В приведенном выше коде тип переменной переменной аннотации явного типа по умолчанию преобразуется в супертип типа литерала присваивания, например, тип str имеет тип «это строка» (здесь представляет строковый литерал type) Строка родительского типа, тип num — это номер родительского типа типа 1.
Этот дизайн соответствует ожиданиям программирования и означает, что мы можем присваивать произвольные значения str и num соответственно (при условии, что типы являются переменными, которые являются подмножеством строки и числа):
str = 'any string';
num = 2;
bool = false;
Мы называем дизайн преобразования литерального квантового типа TypeScript в супертип «литеральным расширением», что является расширением литеральных типов. Например, строковый литеральный тип, упомянутый в приведенном выше примере, преобразуется в строковый тип. Ниже мы сосредоточимся на введении.
Расширение типа
Все переменные, определенные с помощью let или var, формальные параметры функций и свойства объектов, не предназначенные только для чтения, если они удовлетворяют условиям указания начального значения и не добавляют явно аннотацию типа, то их предполагаемый тип является указанным начальным значением. literal Type расширенный тип, это расширение буквального типа.
Давайте разберемся с расширением литерального типа на примере строковых литералов:
let str = 'this is string'; // 类型是 string
let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
const specifiedStr = 'this is string'; // 类型是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;
Поскольку строки 1–2 удовлетворяют условию let, параметр, а аннотация типа явно не объявлена, тип переменной и параметра расширяется до строки (тип параметра точно строка | неопределенный).
Поскольку константа в строке 3 является неизменной, а тип не расширяется, тип указанногоStr является типом литерала «это строка».
В строках 4–5, поскольку тип присваиваемого значения, указанногоStr, является литеральным типом и нет явной аннотации типа, типы переменных и формальных параметров также расширены. На самом деле такой дизайн отвечает реальным требованиям программирования. Давайте представим, что если бы тип str2 был выведен как «это строка», он был бы неизменяем, поскольку присвоение любого другого значения типа string вызвало бы ошибку типа.
Основываясь на условии расширения буквального типа, мы можем контролировать поведение расширения типа, добавляя явные аннотации типа, как показано ниже.
{
const specifiedStr: 'this is string' = 'this is string'; // 类型是 '"this is string"'
let str2 = specifiedStr; // 即便使用 let 定义,类型是 'this is string'
}
На самом деле, в дополнение к литеральному расширению типов, TypeScript также имеет дизайн, похожий на «Расширение типов» (расширение типов) для некоторых конкретных значений типов, давайте рассмотрим его подробно.
Например, чтобы расширить типы null и undefined, если переменные, определенные с помощью let и var, удовлетворяют аннотации типа, не объявленной явно, и им присваивается значение null или undefined, тип этих переменных предполагается любым:
{
let x = null; // 类型拓宽成 any
let y = undefined; // 类型拓宽成 any
/** -----分界线------- */
const z = null; // 类型是 null
/** -----分界线------- */
let anyFun = (param = null) => param; // 形参类型是 null
let z2 = z; // 类型是 null
let x2 = x; // 类型是 null
let y2 = y; // 类型是 undefined
}
Примечание. В некоторых старых версиях (2.0) значения null и undefined не расширялись до «любого» в строгом режиме.
Чтобы упростить понимание расширения типа, давайте рассмотрим пример для более глубокого анализа.
Предположим, вы пишете векторную библиотеку, вы сначала определяете интерфейс Vector3, а затем определяете функцию getComponent для получения значения указанной оси:
interface Vector3 {
x: number;
y: number;
z: number;
}
function getComponent(vector: Vector3, axis: "x" | "y" | "z") {
return vector[axis];
}
Однако при попытке использовать функцию getComponent TypeScript выводит следующее сообщение об ошибке:
let x = "x";
let vec = { x: 10, y: 20, z: 30 };
// 类型“string”的参数不能赋给类型“"x" | "y" | "z"”的参数。
getComponent(vec, x); // Error
Почему возникает вышеуказанная ошибка? Из сообщения об ошибке TypeScript мы знаем, что предполагается, что тип переменной x имеет тип string, и функция getComponent ожидает более конкретный тип для своего второго параметра. На практике это расширяется, поэтому приводит к ошибке.
Этот процесс сложен, потому что существует много возможных типов для любого заданного значения. Например:
const arr = ['x', 1];
Каким должен быть тип указанной выше переменной arr? Вот некоторые возможности:
- ('x' | 1)[]
- ['x', 1]
- [string, number]
- readonly [string, number]
- (string | number)[]
- readonly (string|number)[]
- [any, any]
- any[]
Без дополнительного контекста TypeScript не может узнать, какой тип является «правильным», он должен угадать ваше намерение. Каким бы умным ни был TypeScript, он не может читать ваши мысли. Это не гарантирует 100% правильность, так как мы просто видели непреднамеренные ошибки.
В приведенном ниже примере предполагается, что тип переменной x является строкой, потому что TypeScript допускает такой код:
let x = 'semlinker';
x = 'kakuqo';
x = 'lolo';
Следующий код также допустим для JavaScript:
let x = 'x';
x = /x|y|z/;
x = ['x', 'y', 'z'];
TypeScript пытается найти баланс между специфичностью и гибкостью при выводе типа x как строки. Общее правило состоит в том, что тип переменной не должен меняться после ее объявления, поэтому строка имеет больше смысла, чем строка|RegExp или строка|строка[] или что-то еще.
TypeScript предоставляет несколько способов управления процессом расширения. Одним из способов является использованиеconst
. Если вы объявите переменную с const вместо let, то ее тип будет более узким. На самом деле использование const может помочь нам исправить ошибку в предыдущем примере:
const x = "x"; // type is "x"
let vec = { x: 10, y: 20, z: 30 };
getComponent(vec, x); // OK
Поскольку x нельзя переназначить, TypeScript может вывести более узкий тип без ошибок при последующих назначениях. Поскольку строковый литерал "x" можно присвоить "x"|"y"|"z", код пройдет проверку типов.
Тем не мение,const 并不是万灵药。对于对象和数组,仍然会存在问题
.
Следующий код подходит для JavaScript:
const obj = {
x: 1,
};
obj.x = 6;
obj.x = '6';
obj.y = 8;
obj.name = 'semlinker';
В то время как в TypeScript для типа obj это может быть{readonly x:1}
тип или более общий{x:number}
тип. Конечно, это может быть{[key: string]: number}
или тип объекта. Для объектов алгоритм расширения TypeScript обрабатывает свои внутренние свойства, как если бы они были присвоены переменным, объявленным с помощью ключевого слова let, чтобы вывести типы их свойств. Итак, тип объекта{x:number}
. Это позволяет вам назначать obj.x другим переменным типа number вместо string, а также предотвращает добавление других свойств.
Таким образом, оператор в последних трех строках выдаст ошибку:
const obj = {
x: 1,
};
obj.x = 6; // OK
// Type '"6"' is not assignable to type 'number'.
obj.x = '6'; // Error
// Property 'y' does not exist on type '{ x: number; }'.
obj.y = 8; // Error
// Property 'name' does not exist on type '{ x: number; }'.
obj.name = 'semlinker'; // Error
TypeScript пытается найти баланс между специфичностью и гибкостью. Он должен вывести достаточно конкретный тип, чтобы поймать ошибку, но не неправильный тип. Он выводит тип свойства из его инициализированного значения, и, конечно же, есть несколько способов переопределить поведение TypeScript по умолчанию. Одним из них является предоставление явных аннотаций типа:
// Type is { x: 1 | 3 | 5; }
const obj: { x: 1 | 3 | 5 } = {
x: 1
};
Другой способ — использовать константные утверждения. Не путать с let и const , которые вводят символы в пространство значений. Это чистая конструкция уровня типа. Давайте посмотрим на различные предполагаемые типы для следующих переменных:
// Type is { x: number; y: number; }
const obj1 = {
x: 1,
y: 2
};
// Type is { x: 1; y: number; }
const obj2 = {
x: 1 as const,
y: 2,
};
// Type is { readonly x: 1; readonly y: 2; }
const obj3 = {
x: 1,
y: 2
} as const;
Когда вы используете утверждение const после значения, TypeScript выведет для него самый узкий тип, без расширения. Для истинных констант это обычно то, что вам нужно. Конечно, вы также можете использовать утверждения const для массивов:
// Type is number[]
const arr1 = [1, 2, 3];
// Type is readonly [1, 2, 3]
const arr2 = [1, 2, 3] as const;
Поскольку есть расширение типа, будет и сужение типа.Давайте кратко представим сужение типа.
Тип Сужение
В TypeScript мы можем сузить тип переменной от относительно широкого набора до относительно небольшого, более конкретного набора с помощью определенных операций, которые называются «сужением типов».
Например, мы можем использовать защиту типа (описанную ниже), чтобы сузить тип параметра функции с любого до явного типа, как в следующем примере:
{
let func = (anything: any) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else if (typeof anything === 'number') {
return anything; // 类型是 number
}
return null;
};
}
В VS Code наведите указатель мыши на любую переменную в строке 4, тип приглашения — строка, а в строке 6 тип приглашения — число.
Точно так же мы можем использовать защиту типов, чтобы сузить типы объединения до явных подтипов, как в следующем примере:
{
let func = (anything: string | number) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else {
return anything; // 类型是 number
}
};
}
Конечно, мы также можем свести тип объединения к более конкретному типу с помощью суждения об эквивалентности литерального типа (===) или других операторов потока управления (включая, помимо прочего, if, тернарный оператор, switch branch), как показано в следующем Код Показать:
{
type Goods = 'pen' | 'pencil' |'ruler';
const getPenCost = (item: 'pen') => 2;
const getPencilCost = (item: 'pencil') => 4;
const getRulerCost = (item: 'ruler') => 6;
const getCost = (item: Goods) => {
if (item === 'pen') {
return getPenCost(item); // item => 'pen'
} else if (item === 'pencil') {
return getPencilCost(item); // item => 'pencil'
} else {
return getRulerCost(item); // item => 'ruler'
}
}
}
В приведенной выше функции getCost принятый тип параметра является типом объединения литерального типа, а функция содержитif
3 ветви процесса оператора, где параметры функции, вызываемой каждой ветвью процесса, являются конкретными и независимыми литеральными типами.
Так почему же элемент переменной, тип которого состоит из нескольких литералов, может быть передан функции, которая принимает только один конкретный литерал?getPenCost、getPencilCost、getRulerCost
Шерстяная ткань? Это связано с тем, что в каждой ветви процесса компилятор знает, какой тип элемента находится в ветви процесса. Например, ветвь элемента === 'карандаш', тип элемента сокращается до "карандаш".
На самом деле, если мы удалим ветвь промежуточного процесса из приведенного выше примера, компилятор также сможет вывести конвергентный тип, как показано в следующем коде:
const getCost = (item: Goods) => {
if (item === 'pen') {
item; // item => 'pen'
} else {
item; // => 'pencil' | 'ruler'
}
}
Вообще говоряTypeScript
Очень хорошо определяет типы по условиям, но будьте осторожны при работе с некоторыми специальными значениями — они могут содержать вещи, которые вам не нужны! Например, следующий метод исключения null из типов объединения неверен:
const el = document.getElementById("foo"); // Type is HTMLElement | null
if (typeof el === "object") {
el; // Type is HTMLElement | null
}
потому что в JavaScripttypeof null
Результатом является "object" , поэтому вы на самом деле не исключаете эту проверкуnull
ценность. Вдобавок к этому, необработанные значения falsy создают аналогичные проблемы:
function foo(x?: number | string | null) {
if (!x) {
x; // Type is string | number | null | undefined\
}
}
Поскольку и пустая строка, и 0 являются ложными значениями, тип x в ветви может быть строковым или числовым. Еще один распространенный способ помочь программам проверки типов сузить круг типов — поместить на них явную «метку»:
interface UploadEvent {
type: "upload";
filename: string;
contents: string;
}
interface DownloadEvent {
type: "download";
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case "download":
e; // Type is DownloadEvent
break;
case "upload":
e; // Type is UploadEvent
break;
}
}
Этот шаблон также известен как «объединение меток» или «распознанное объединение», и он имеет очень широкий спектр приложений в TypeScript.
тип союза
Тип объединения указывает, что значение может быть одним из нескольких типов, используя|
Разделите каждый тип.
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'; // OK
myFavoriteNumber = 7; // OK
Типы союзов обычно связаны сnull
или undefined
использовать вместе:
const sayHello = (name: string | undefined) => {
/* ... */
};
Например, здесьname
Типstring | undefined
означает, чтоstring
или undefined
Значение передается вsayHello
функция.
sayHello("semlinker");
sayHello(undefined);
Из этого примера вы можете интуитивно понять, что объединение типов A и B — это тип, который принимает значения A и B. Кроме того, для типов объединения вы можете столкнуться со следующими вариантами использования:
let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';
в приведенном выше примере1
,2
или 'click'
называется литеральным типом и используется для ограничения значения только одним из нескольких значений.
введите псевдоним
Псевдонимы типов используются для присвоения типу нового имени. Псевдонимы типов часто используются для типов объединения.
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
Примечание. Псевдонимы типов, как следует из названия, просто дают типу новое имя, а не создают новый тип.
перекрестный тип
Кросс-тип представляет собой объединение нескольких типов в один тип. Это позволяет нам объединять существующие типы в один тип, содержащий все необходимые свойства типа, используя&
Определите тип перекрестка.
{
type Useless = string & number;
}
Очевидно, что если мы объединяем только атомарные типы, такие как примитивные типы, литеральные типы и типы функций, в перекрестные типы, это бесполезно, потому что ни один тип не может одновременно удовлетворять нескольким атомарным типам, таким как строка и число. . Таким образом, в приведенном выше коде псевдоним типа Useless имеет тип never .
Реальное использование перекрестных типов заключается в объединении нескольких типов интерфейсов в один тип, чтобы добиться эффекта эквивалентного наследования интерфейса, что является так называемым объединенным типом интерфейса, как показано в следующем коде:
type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
id: 1,
name: 'name',
age: 18
}
В приведенном выше примере мы заставляем IntersectionType иметь все атрибуты id, name и age через тип пересечения.Здесь мы можем попытаться понять тип объединенного интерфейса как объединение.
считать
Здесь давайте подумаем об этом по-разному: каков будет эффект, если в нескольких типах интерфейсов будут объединены атрибуты с одинаковыми именами?
Если типы атрибутов с одинаковыми именами несовместимы, например, типы атрибутов имени двух типов интерфейса с одинаковыми именами в приведенном выше примере, один из них является числом, а другой — строкой.После слияния тип атрибута имени будет перекрестный тип двух атомарных типов числа и строки, то есть никогда, как показано в следующем коде:
type IntersectionTypeConfict = { id: number; name: string; }
& { age: number; name: number; };
const mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ts(2322) 错误,'number' 类型不能赋给 'never' 类型
age: 2
};
На этом этапе любое значение атрибута имени, которое мы назначаем для MixedConflict, вызовет ошибку типа. И если мы не установим атрибут имени, будет выдано сообщение об ошибке, что требуемый атрибут имени отсутствует. В данном случае это означает, что тип IntersectionTypeConfict, зачеркнутый в приведенном выше коде, является бесполезным типом.
Если типы атрибутов с одинаковыми именами совместимы, например, один является числом, а другой является подтипом числа или типом числового литерала, тип объединенного атрибута имени является подтипом этих двух.
Тип свойства имени в примере, показанном ниже, является числовым литералом типа 2, поэтому мы не можем присвоить никакое значение, кроме 2, свойству имени.
type IntersectionTypeConfict = { id: number; name: 2; }
& { age: number; name: number; };
let mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ok
age: 2
};
mixedConflict = {
id: 1,
name: 22, // '22' 类型不能赋给 '2' 类型
age: 2
};
Итак, что произойдет, если свойство с тем же именем не является примитивным типом данных. Давайте рассмотрим конкретный пример:
interface A {
x:{d:true},
}
interface B {
x:{e:string},
}
interface C {
x:{f:number},
}
type ABC = A & B & C
let abc:ABC = {
x:{
d:true,
e:'',
f:666
}
}
После успешного выполнения приведенного выше кода он выведет следующие результаты:
Как видно из приведенного выше рисунка, при смешивании нескольких типов, если существует один и тот же член, а тип члена не является базовым типом данных, его можно успешно объединить.
Интерфейсы
В TypeScript мы используем интерфейсы для определения типов объектов.
что такое интерфейс
В объектно-ориентированном языке интерфейс (интерфейсы) является очень важным понятием, это абстракция поведения, и то, как действовать, должно быть реализовано классами (классами).
Интерфейс в TypeScript — очень гибкое понятие, помимо [абстрагирования части поведения класса], оно также часто используется для описания «формы объекта».
простой пример
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
В приведенном выше примере мы определяем интерфейсPerson
, а затем определить переменнуюtom
, который имеет типPerson
. Таким образом, мы связаныtom
Форма должна соответствовать интерфейсуPerson
Последовательный.
Интерфейсы обычно пишутся с большой буквы.
Не допускается определение переменной с меньшим количеством свойств, чем у интерфейса:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom'
};
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
Некоторые другие свойства также не разрешены:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
видимый,При присвоении значения форма переменной должна совпадать с формой интерфейса.
необязательный | свойство только для чтения
interface Person {
readonly name: string;
age?: number;
}
Свойства только для чтения используются для ограничения изменений значения объекта только тогда, когда он только что создан. Кроме того, TypeScript также предоставляетReadonlyArray<T>
тип, связанный сArray<T>
Аналогично, за исключением того, что все изменяемые методы удаляются, что гарантирует невозможность изменения массива после его создания.
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
произвольные свойства
Иногда мы хотим, чтобы интерфейс имел другие произвольные атрибуты в дополнение к обязательным и необязательным атрибутам, В этом случае мы можем использоватьподпись индексаформу, отвечающую вышеуказанным требованиям.
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
должны знать о том,После определения произвольного свойства тип как детерминированных, так и необязательных свойств должен быть подмножеством его типа.
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
В приведенном выше примере значение любого атрибута может бытьstring
, но необязательные атрибутыage
Значениеnumber
,number
нет string
Подсвойство , поэтому сообщается об ошибке.
Кроме того, в сообщении об ошибке видно, что в это время{ name: 'Tom', age: 25, gender: 'male' }
Предполагается, что тип{ [x: string]: string | number; name: string; age: number; gender: string; }
, который представляет собой комбинацию типов объединения и интерфейсов.
В интерфейсе может быть определено только одно произвольное свойство. Если у вас есть свойства нескольких типов в интерфейсе, вы можете использовать тип объединения в любом из свойств:
interface Person {
name: string;
age?: number; // 这里真实的类型应该为:number | undefined
[propName: string]: string | number | undefined;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
определение типа утки
Так называемыйопределение типа уткиэто像鸭子一样走路并且嘎嘎叫的就叫鸭子
, то есть обладающие характеристиками утки думают, что это утка, то есть путем формулирования правил определить, реализует ли объект этот интерфейс.
пример
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj); // OK
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
printLabel({ size: 10, label: "Size 10 Object" }); // Error
В приведенном выше коде запись объекта в параметр эквивалентна прямой передачеlabeledObj
Назначение, этот объект имеет строгое определение типа, поэтому он не может иметь больше или меньше параметров. И когда вы используете объект снаружи с другой переменнойmyObj
перенимать,myObj
Не подвергается дополнительной проверке свойств, но выводится из типа какlet myObj: { size: number; label: string } = { size: 10, label: "Size 10 Object" };
, затем поместите этоmyObj
переназначить наlabeledObj
, в настоящее время, в соответствии с совместимостью типа, два типа объектов относятся копределение типа утки, потому что у обоих естьlabel
свойства, поэтому они считаются одинаковыми, поэтому этот метод можно использовать для обхода избыточной проверки типов.
Способы обойти дополнительные проверки свойств
определение типа утки
Как показано в примере выше
утверждение типа
Смысл утверждения типа эквивалентен тому, что вы сообщаете программе, что знаете, что делаете, и программа, естественно, не будет выполнять дополнительные проверки свойств.
interface Props {
name: string;
age: number;
money?: number;
}
let p: Props = {
name: "兔神",
age: 25,
money: -100000,
girl: false
} as Props; // OK
подпись индекса
interface Props {
name: string;
age: number;
money?: number;
[key: string]: any;
}
let p: Props = {
name: "兔神",
age: 25,
money: -100000,
girl: false
}; // OK
Разница между интерфейсом и псевдонимом типа
На самом деле использование интерфейсных типов и псевдонимов типов в большинстве случаев эквивалентно, но в некоторых конкретных случаях между ними все же есть существенные различия.
Одним из основных принципов TypeScript является проверка типа структуры значения. Роль интерфейса состоит в том, чтобы назвать эти типы и определить модель данных для вашего кода или стороннего кода.
type (псевдоним типа) дает новое имя типу. type иногда похож на interface, но работает с примитивными значениями (примитивными типами), типами объединения, кортежами и любым другим типом, который нужно написать вручную. Псевдоним не создает новый тип — он создает новое имя для ссылки на этот тип. Псевдонимы примитивных типов, как правило, бесполезны, хотя их можно использовать как форму документации.
Objects / Functions
Оба могут использоваться для описания типа объекта или функции, но синтаксис отличается.
Interface
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
Type alias
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
Other Types
В отличие от интерфейсов псевдонимы типов также могут использоваться для других типов, таких как примитивные типы (примитивные значения), типы объединения, кортежи.
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement('div');
type B = typeof div;
Интерфейсы могут быть определены несколько раз, псевдонимы типов не могут
В отличие от псевдонимов типов, интерфейсы можно определять несколько раз, и они автоматически объединяются в один интерфейс.
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
расширять
Они распространяются по-разному, но не исключают друг друга. Интерфейсы могут расширять псевдонимы типов, и аналогичным образом псевдонимы типов могут расширять интерфейсы.
Расширением интерфейса является наследование, черезextends
реализовать. Расширение псевдонима типа является кросс-типом, через&
реализовать.
Интерфейс расширения интерфейса
interface PointX {
x: number
}
interface Point extends PointX {
y: number
}
псевдоним расширенного типа
type PointX = {
x: number
}
type Point = PointX & {
y: number
}
псевдоним типа расширения интерфейса
type PointX = {
x: number
}
interface Point extends PointX {
y: number
}
введите псевдоним интерфейса расширения
interface PointX {
x: number
}
type Point = PointX & {
y: number
}
Дженерики
Введение в дженерики
Если вас попросили реализовать функциюidentity
, параметр функции может быть любым значением, возвращаемое значение состоит в том, чтобы вернуть параметр как есть, и она может принимать только один параметр, что бы вы сделали?
Вы подумаете, что это очень просто, и вы можете написать такой код:
const identity = (arg) => arg;
Поскольку он может принимать любое значение, это означает, что входные параметры вашей функции и возвращаемые значения должны быть любого типа. Теперь добавим в код объявления типов:
type idBoolean = (arg: boolean) => boolean;
type idNumber = (arg: number) => number;
type idString = (arg: string) => string;
...
Тупой метод похож на вышеописанный, то есть сколько типов предоставляет JS, столько кода надо копировать и менять сигнатуру типа. Это фатально для программистов. Этот вид копирования и вставки увеличивает вероятность ошибок, затрудняет поддержку кода и влияет на все тело. И в будущем JS добавляет новые типы, вам все равно нужно изменить код, то есть ваш кодоткрыт для модификации,это не хорошо. Другой способ — использовать «универсальный синтаксис» any. Каковы недостатки? привожу пример:
identity("string").length; // ok
identity("string").toFixed(2); // ok
identity(null).toString(); // ok
...
Если вы используете any , все, что вы пишете, будет ok , что потеряет эффект проверки типов. На самом деле, я знаю, что то, что я передаю вам, является строкой, и возвращаемое значение должно быть строкой, и в строке нет метода toFixed, поэтому я хочу, чтобы он сообщал об ошибке. То есть эффект, который я действительно хочу, это:当我用到id的时候,你根据我传给你的类型进行推导
. Например, если я передаю строку, но использую метод для числа, вы должны сообщить об ошибке.
Для решения вышеуказанных проблем мыРефакторинг приведенного выше кода для использования дженериков. В отличие от нашего определения, здесь используется тип T, этоT — абстрактный тип, значение которого определяется только при вызове., что избавляет нас от необходимости копировать и вставлять бесчисленное количество копий кода.
function identity<T>(arg: T): T {
return arg;
}
вT
представлятьType, обычно используемое в качестве имени переменной первого типа при определении универсального. Но на самом делеT
Можно заменить любым допустимым именем. КромеT
Кроме того, ниже приведены значения общих общих переменных:
- K (ключ): представляет тип ключа в объекте;
- V (значение): представляет тип значения в объекте;
- E (элемент): указывает тип элемента.
Вот картинка, которая поможет вам понять
На самом деле, вместо того, чтобы определять только одну переменную типа, мы можем ввести любое количество переменных типа, которые мы хотим определить. Например, мы вводим переменную нового типаU
, который используется для расширения нашего определенияidentity
функция:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
Вместо того, чтобы явно задавать значения для переменных типа, более распространенной практикой является автоматический выбор компилятором этих типов, что делает код чище. Мы можем полностью опустить угловые скобки, например:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
Для приведенного выше кода компилятор достаточно умен, чтобы знать наши типы параметров и назначать их T и U без явного указания их разработчиком.
общие ограничения
Что делать, если я хочу распечатать атрибут размера параметра? Если вы вообще не ограничиваете TS, он сообщит об ошибке:
function trace<T>(arg: T): T {
console.log(arg.size); // Error: Property 'size doesn't exist on type 'T'
return arg;
}
Причина ошибки в том, что T теоретически может быть любого типа.В отличие от любого, вы будете сообщать об ошибке независимо от того, какие свойства или методы вы используете (если только свойства и методы не являются общими для всех коллекций). Тогда интуитивная идея состоит в том, чтобы ограничить параметры, передаваемые в функцию трассировки.Тип параметраДолжен быть тип размера, чтобы не сообщалось об ошибке. как это выразитьограничения типасмысл? Ключом к выполнению этого требования является использование ограничений типа. Это можно сделать с помощью ключевого слова extends. Проще говоря, вы определяете тип и позволяете T реализовать этот интерфейс.
interface Sizeable {
size: number;
}
function trace<T extends Sizeable>(arg: T): T {
console.log(arg.size);
return arg;
}
Некоторые люди могут сказать, что я могу напрямую ограничить параметры Trace типом Sizeable? Если вы сделаете это, существует риск потери типа, подробности см. в этой статье.A use case for TypeScript Generics.
Общий тип инструмента
Для удобства разработчиков в TypeScript встроены некоторые часто используемые типы инструментов, такие как Partial, Required, Readonly, Record и ReturnType. Однако перед конкретным введением мы должны представить некоторые базовые знания, чтобы читатели могли лучше изучить другие типы инструментов.
1.typeof
Основная цель typeof — получить тип переменной или свойства в контексте типа, давайте разберемся с этим на конкретном примере.
interface Person {
name: string;
age: number;
}
const sem: Person = { name: "semlinker", age: 30 };
type Sem = typeof sem; // type Sem = Person
В приведенном выше коде мы передаемtypeof
Оператор получает тип переменной sem и присваивает его переменной типа Sem, после чего мы можем использовать тип Sem:
const lolo: Sem = { name: "lolo", age: 5 }
Вы также можете сделать то же самое с вложенными объектами:
const Message = {
name: "jimmy",
age: 18,
address: {
province: '四川',
city: '成都'
}
}
type message = typeof Message;
/*
type message = {
name: string;
age: number;
address: {
province: string;
city: string;
};
}
*/
также,typeof
Помимо получения типа структуры объекта, оператор также может использоваться для получения типа объекта функции, например:
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
2.keyof
keyof
Представленный в TypeScript 2.1 оператор может использоваться для получения всех ключей типа, а его возвращаемый тип — тип объединения.
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
В TypeScript поддерживаются два типа сигнатур индекса: числовое индексирование и индексирование строк:
interface StringArray {
// 字符串索引 -> keyof StringArray => string | number
[index: string]: string;
}
interface StringArray1 {
// 数字索引 -> keyof StringArray1 => number
[index: number]: string;
}
Для поддержки обоих типов индексов требуется, чтобы возвращаемое значение числового индекса было подклассом возвращаемого значения строкового индекса.Причина в том, что при использовании числового индексирования JavaScript сначала преобразует числовой индекс в строковый индекс при выполнении операции индексирования.. такkeyof { [x: string]: Person }
Результат вернетсяstring | number
.
keyof также поддерживает основные типы данных:
let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"
Роль keyof
JavaScript — очень динамичный язык. Иногда уловить семантику определенных операций в статической системе типов бывает сложно. с простымprop
Пример функции:
function prop(obj, key) {
return obj[key];
}
Функция получает два параметра, obj и ключ, и возвращает значение соответствующего свойства. Различные свойства объекта могут иметь совершенно разные типы, и мы даже не знаем, как выглядит объект obj.
Итак, как определить вышеперечисленное в TypeScriptprop
Что насчет функций? Давайте попробуем:
function prop(obj: object, key: string) {
return obj[key];
}
В приведенном выше коде, чтобы избежать передачи неправильного типа параметра при вызове функции prop, мы устанавливаем типы для параметров obj и key соответственно.{}
и string
тип. Однако все не так просто. Для приведенного выше кода компилятор TypeScript выдаст следующее сообщение об ошибке:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
Элементы неявно принадлежатany
типа потому чтоstring
тип нельзя использовать для индексации{}
тип. Чтобы исправить это, вы можете использовать следующее очень грубое решение:
function prop(obj: object, key: string) {
return (obj as any)[key];
}
Очевидно, что это решение не является хорошим решением, давайте рассмотримprop
Функция функции, которая используется для получения значения атрибута указанного атрибута в объекте. Таким образом, мы ожидаем, что свойства, введенные пользователем, будут свойствами, уже существующими в объекте, так как же нам ограничить область действия имен свойств? В это время мы можем использовать главного героя этой статьиkeyof
оператор:
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
В приведенном выше коде мы использовали дженерики TypeScript и универсальные ограничения.Сначала определите тип T и используйтеextends
ключевое слово ограничивает тип как подтип типа объекта, затем используйтеkeyof
Оператор получает все ключи типа T, и его возвращаемый тип является типом объединения, и, наконец, используетextends
Тип ограничения ключевого слова K должен бытьkeyof T
Подтип типа объединения.Если это мул или лошадь, вы узнаете это на прогулке. Давайте на самом деле протестируем это:
type Todo = {
id: number;
text: string;
done: boolean;
}
const todo: Todo = {
id: 1,
text: "Learn TypeScript keyof",
done: false
}
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const id = prop(todo, "id"); // const id: number
const text = prop(todo, "text"); // const text: string
const done = prop(todo, "done"); // const done: boolean
Очевидно, используя дженерики, переопределенныеprop<T extends object, K extends keyof T>(obj: T, key: K)
функция, тип, соответствующий указанному ключу, может быть правильно выведен. Так что же происходит при доступе к свойству, которого нет в объекте todo? Например:
const date = prop(todo, "date");
Для приведенного выше кода компилятор TypeScript выдаст следующую ошибку:
Argument of type '"date"' is not assignable to parameter of type '"id" | "text" | "done"'.
Это не позволяет нам пытаться читать свойства, которые не существуют.
3.in
in
Используется для перебора типов перечисления:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
4.infer
В операторе условного типа вы можете использоватьinfer
Объявите переменную типа и используйте ее.
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
в приведенном выше кодеinfer R
Это объявление переменной для переноса типа возвращаемого значения сигнатуры входящей функции.Проще говоря, она используется для получения типа возвращаемого значения функции для удобства.
5.extends
Иногда определяемые нами дженерики не хотят быть слишком гибкими или хотят наследовать определенные классы и т. д., мы можем добавить универсальные ограничения с помощью ключевого слова extends.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
Теперь эта универсальная функция ограничена, поэтому она больше не применима к произвольным типам:
loggingIdentity(3); // Error, number doesn't have a .length property
На данный момент нам нужно передать значение, соответствующее типу ограничения, которое должно содержать атрибут длины:
loggingIdentity({length: 10, value: 3});
тип индекса
В реальной разработке мы часто сталкиваемся с такими сценариями, получая значения некоторых атрибутов в объекте, а затем устанавливая соответствующую коллекцию.
let person = {
name: 'musion',
age: 35
}
function getValues(person: any, keys: string[]) {
return keys.map(key => person[key])
}
console.log(getValues(person, ['name', 'age'])) // ['musion', 35]
console.log(getValues(person, ['gender'])) // [undefined]
В приведенном выше примере вы можете видеть, что getValues(person, ['gender']) печатает [undefined], но компилятор ts не выдает сообщения об ошибке, так как же использовать ts для наложения ограничений типа на этот режим? Здесь используется индексный тип, а функция getValues модифицирована для передачиЗапрос индексного типаидоступ к индексуоператор:
function getValues<T, K extends keyof T>(person: T, keys: K[]): T[K][] {
return keys.map(key => person[key]);
}
interface Person {
name: string;
age: number;
}
const person: Person = {
name: 'musion',
age: 35
}
getValues(person, ['name']) // ['musion']
getValues(person, ['gender']) // 报错:
// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type "gender" is not assignable to type "name" | "age".
Компилятор проверяет, является ли переданное значение частью Person. Поймите приведенный выше код с помощью следующих концепций:
T[K]表示对象T的属性K所表示的类型,在上述例子中,T[K][] 表示变量T取属性K的值的数组
// 通过[]索引类型访问操作符, 我们就能得到某个索引的类型
class Person {
name:string;
age:number;
}
type MyType = Person['name']; //Person中name的类型为string type MyType = string
После введения понятий вы сможете понять приведенный выше код. Сначала взгляните на дженерики: есть два типа T и K. Согласно выводу типа, первый параметр person — это человек, а тип будет выводиться как Person. И вывод типа второго параметра массива (K расширяет ключ T), ключевое слово keyof может получить T, то есть все имена атрибутов Person, а именно ['name', 'age']. Ключевое слово extends позволяет универсальному K наследовать все имена атрибутов Person, т.е. ['name', 'age']. Сочетание этих трех функций обеспечивает динамичность и точность кода, а также обогащает подсказки кода.
getValues(person, ['gender']) // 报错:
// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type "gender" is not assignable to type "name" | "age".
тип карты
Создайте новый тип из старого типа, мы называем его сопоставленным типом
Например, мы определяем интерфейс
interface TestInterface{
name:string,
age:number
}
Мы делаем все свойства в интерфейсе, определенные выше, необязательными.
// 我们可以通过+/-来指定添加还是删除
type OptionalTestInterface<T> = {
[p in keyof T]+?:T[p]
}
type newTestInterface = OptionalTestInterface<TestInterface>
// type newTestInterface = {
// name?:string,
// age?:number
// }
Например, мы добавляем только для чтения
type OptionalTestInterface<T> = {
+readonly [p in keyof T]+?:T[p]
}
type newTestInterface = OptionalTestInterface<TestInterface>
// type newTestInterface = {
// readonly name?:string,
// readonly age?:number
// }
Поскольку чаще всего генерируются атрибуты только для чтения и необязательные атрибуты, TS уже предоставил нам готовую реализацию Readonly/Partial, и будут представлены встроенные типы инструментов.
Типы встроенных инструментов
Partial
Partial<T>
Сделать атрибут типа необязательным
определение
type Partial<T> = {
[P in keyof T]?: T[P];
};
В приведенном выше коде первый проходkeyof T
получитьT
всех имен свойств, затем используйтеin
Перебрать и присвоить значенияP
, наконец черезT[P]
Класс для получения соответствующего значения свойства. Середина?
, чтобы сделать все свойства необязательными.
Например
interface UserInfo {
id: string;
name: string;
}
// error:Property 'id' is missing in type '{ name: string; }' but required in type 'UserInfo'
const xiaoming: UserInfo = {
name: 'xiaoming'
}
использовать Partial<T>
type NewUserInfo = Partial<UserInfo>;
const xiaoming: NewUserInfo = {
name: 'xiaoming'
}
Эта NewUserInfo эквивалентна
interface NewUserInfo {
id?: string;
name?: string;
}
но Partial<T>
Есть ограничение, то есть поддерживает только обработку свойств первого слоя, если у меня определение интерфейса такое
interface UserInfo {
id: string;
name: string;
fruits: {
appleNumber: number;
orangeNumber: number;
}
}
type NewUserInfo = Partial<UserInfo>;
// Property 'appleNumber' is missing in type '{ orangeNumber: number; }' but required in type '{ appleNumber: number; orangeNumber: number; }'.
const xiaoming: NewUserInfo = {
name: 'xiaoming',
fruits: {
orangeNumber: 1,
}
}
Видно, что второй слой не будет обрабатываться после второго слоя, если вы хотите иметь дело с несколькими слоями, вы можете реализовать это самостоятельно.
DeepPartial
type DeepPartial<T> = {
// 如果是 object,则递归类型
[U in keyof T]?: T[U] extends object
? DeepPartial<T[U]>
: T[U]
};
type PartialedWindow = DeepPartial<T>; // 现在T上所有属性都变成了可选啦
Required
Required превращает свойства типа в обязательные
определение
type Required<T> = {
[P in keyof T]-?: T[P]
};
в -?
это удалить?
Идентификатор этого модификатора. Расширить его дальше, за исключением того, что его можно применить к?
Эти модификаторы, также применяемые вreadonly
,Например Readonly<T>
этот тип
type Readonly<T> = {
readonly [p in keyof T]: T[p];
}
Readonly
Readonly<T>
Роль состоит в том, чтобы превратить все свойства типа в свойства только для чтения, что означает, что эти свойства нельзя переназначить.
определение
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Например
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users"
};
todo.title = "Hello"; // Error: cannot reassign a readonly property
Pick
Pick выбирает некоторые свойства из типа
определение
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Например
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Вы можете видеть, что в NewUserInfo есть только один атрибут имени.
Record
Record<K extends keyof any, T>
Эффект заключается вK
Значения всех атрибутов в конвертируются вT
тип.
определение
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Например
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" },
};
ReturnType
Используется для получения возвращаемого типа функции
определение
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: any;
infer
Используется здесь для извлечения типа возвращаемого значения типа функции.ReturnType<T>
Просто переместите infer R из позиции параметра в позицию возвращаемого значения, так что в это время R является типом возвращаемого значения, которое нужно вывести.
Например
type Func = (value: number) => string;
const foo: ReturnType<Func> = "1";
ReturnType
получитьFunc
Тип возвращаемого значения:string
,так,foo
Он может быть назначен только как строка.
Exclude
Exclude<T, U>
Роль состоит в том, чтобы удалить тип, принадлежащий другому типу.
определение
type Exclude<T, U> = T extends U ? never : T;
если T
может быть назначен наU
введите, то он вернетсяnever
введите, иначе вернитеT
тип. Окончательный эффект заключается вT
некоторые из которых принадлежатU
Тип удален.
Например
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
Extract
Extract<T, U>
Роль изT
извлеченный изU
.
определение
type Extract<T, U> = T extends U ? T : never;
Например
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
Omit
Omit<T, K extends keyof any>
Роль заключается в использованииT
тип кромеK
Все свойства типа для создания нового типа.
определение
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Например
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
NonNullable
NonNullable<T>
Роль используется для фильтрации типаnull
иundefined
тип.
определение
type NonNullable<T> = T extendsnull | undefined ? never : T;
Например
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
Parameters
Parameters<T>
Роль используется для получения типа кортежа, состоящего из типов параметров функции.
определение
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any
? P : never;
Например
type A = Parameters<() =>void>; // []
type B = Parameters<typeofArray.isArray>; // [any]
type C = Parameters<typeofparseInt>; // [string, (number | undefined)?]
type D = Parameters<typeofMath.max>; // number[]
tsconfig.json
Введение в tsconfig.json
tsconfig.json — это файл конфигурации для проекта TypeScript. Если в каталоге есть файл tsconfig.json, это обычно означает, что этот каталог является корневым каталогом проекта TypeScript.
tsconfig.json содержит конфигурацию, связанную с компиляцией TypeScript.Изменяя элементы конфигурации компиляции, мы можем позволить TypeScript компилировать ES6, ES5 и код узла.
tsconfig.json важные поля
- files — задает имя компилируемых файлов;
- include — задайте файлы, которые необходимо скомпилировать, поддержите сопоставление с образцом пути;
- исключить - установить файлы, которые не нужно компилировать, поддерживать сопоставление с образцом пути;
- compileOptions — установка параметров, связанных с процессом компиляции.
параметры компилятора
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}
Несколько советов по написанию эффективного кода TS
Минимизируйте дублирование кода
Для тех, кто плохо знаком с TypeScript, при определении интерфейса может случайно появиться следующий повторяющийся код. Например:
interface Person {
firstName: string;
lastName: string;
}
interface PersonWithBirthDate {
firstName: string;
lastName: string;
birth: Date;
}
Очевидно, по сравнению сPerson
Для интерфейса,PersonWithBirthDate
Интерфейс просто еще одинbirth
имущество, другое имущество сPerson
Интерфейс такой же. Итак, как избежать дублирования кода в примере? Для решения этой проблемы можно использоватьextends
Ключевые слова:
interface Person {
firstName: string;
lastName: string;
}
interface PersonWithBirthDate extends Person {
birth: Date;
}
Конечно, кроме использованияextends
В дополнение к ключевым словам также можно использовать оператор пересечения (&):
type PersonWithBirthDate = Person & { birth: Date };
Кроме того, иногда вы можете захотеть определить тип, соответствующий «форме» объекта начальной конфигурации, например:
const INIT_OPTIONS = {
width: 640,
height: 480,
color: "#00FF00",
label: "VGA",
};
interface Options {
width: number;
height: number;
color: string;
label: string;
}
На самом деле, для интерфейса Options вы также можете использовать оператор typeof, чтобы быстро получить «форму» объекта конфигурации:
type Options = typeof INIT_OPTIONS;
В реальной разработке дублирующиеся типы не всегда легко обнаружить. Иногда их затмевает синтаксис. Например, есть несколько функций с сигнатурой одного и того же типа:
function get(url: string, opts: Options): Promise<Response> { /* ... */ }
function post(url: string, opts: Options): Promise<Response> { /* ... */ }
Для описанных выше методов get и post, чтобы избежать дублирования кода, вы можете извлечь сигнатуру унифицированного типа:
type HTTPFunction = (url: string, opts: Options) => Promise<Response>;
const get: HTTPFunction = (url, opts) => { /* ... */ };
const post: HTTPFunction = (url, opts) => { /* ... */ };
заменить строковый тип более точным типом
Предположим, вы создаете музыкальную коллекцию и хотите определить жанр для альбома. В этот момент вы можете использоватьinterface
ключевое слово для определенияAlbum
тип:
interface Album {
artist: string; // 艺术家
title: string; // 专辑标题
releaseDate: string; // 发行日期:YYYY-MM-DD
recordingType: string; // 录制类型:"live" 或 "studio"
}
заAlbum
тип вы хотитеreleaseDate
Формат значения атрибутаYYYY-MM-DD
,и recordingType
Диапазон значений атрибутаlive
или studio
. Но поскольку интерфейсreleaseDate
и recordingType
Все типы атрибутов являются строками, поэтому при использованииAlbum
интерфейса могут возникнуть следующие проблемы:
const dangerous: Album = {
artist: "Michael Jackson",
title: "Dangerous",
releaseDate: "November 31, 1991", // 与预期格式不匹配
recordingType: "Studio", // 与预期格式不匹配
};
Несмотря на то что releaseDate
и recordingType
Значение не соответствует ожидаемому формату, но на данный момент компилятор TypeScript не знает о проблеме. Для решения этой проблемы следуетreleaseDate
и recordingType
Свойства определяют более точные типы, например:
interface Album {\
artist: string; // 艺术家
title: string; // 专辑标题
releaseDate: Date; // 发行日期:YYYY-MM-DD
recordingType: "studio" | "live"; // 录制类型:"live" 或 "studio"
}
переопределитьAlbum
После интерфейса для предыдущего оператора присваивания компилятор TypeScript запросит следующую информацию об исключении:
const dangerous: Album = {
artist: "Michael Jackson",
title: "Dangerous",
// 不能将类型“string”分配给类型“Date”。ts(2322)
releaseDate: "November 31, 1991", // Error
// 不能将类型“"Studio"”分配给类型“"studio" | "live"”。ts(2322)\
recordingType: "Studio", // Error
};
Для решения вышеуказанной проблемы необходимоreleaseDate
и recordingType
Свойству присваивается правильный тип, например:
const dangerous: Album = {
artist: "Michael Jackson",
title: "Dangerous",
releaseDate: new Date("1991-11-31"),
recordingType: "studio",
};
Определенный тип всегда представляет допустимое состояние
Предположим, вы создаете веб-приложение, которое позволяет пользователю указать номер страницы, а затем загружает и отображает соответствующий контент для этой страницы. Во-первых, вы можете сначала определитьState
Объект:
interface State {
pageContent: string;
isLoading: boolean;
errorMsg?: string;
}
Затем вы определитеrenderPage
Функция, используемая для отображения страницы:
function renderPage(state: State) {
if (state.errorMsg) {
return `呜呜呜,加载页面出现异常了...${state.errorMsg}`;
} else if (state.isLoading) {
return `页面加载中~~~`;
}
return `<div>${state.pageContent}</div>`;
}
// 输出结果:页面加载中~~~
console.log(renderPage({isLoading: true, pageContent: ""}));
// 输出结果:<div>大家好</div>
console.log(renderPage({isLoading: false, pageContent: "大家好呀"}));
СозданныйrenderPage
функцию, вы можете пойти дальше и определитьchangePage
Функция используется для получения соответствующих данных страницы по номеру страницы:
async function changePage(state: State, newPage: string) {
state.isLoading = true;
try {
const response = await fetch(getUrlForPage(newPage));
if (!response.ok) {
throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
}
const text = await response.text();
state.isLoading = false;
state.pageContent = text;
} catch (e) {
state.errorMsg = "" + e;
}
}
для вышеперечисленногоchangePage
функции, он имеет следующие проблемы:
- В операторе catch не ставьте
state.isLoading
Статус установлен наfalse
; - Не убрано вовремя
state.errorMsg
Значение , поэтому, если предыдущий запрос не удался, вы по-прежнему будете видеть сообщение об ошибке вместо сообщения о загрузке.
Причина вышеуказанной проблемы заключается в том, что ранее определенныеState
Тип позволяет установить обаisLoading
и errorMsg
Значение , хотя это недопустимое состояние. Для этой проблемы вы можете рассмотреть возможность введения различимого типа объединения для определения различных состояний запроса страницы:
interface RequestPending {
state: "pending";
}
interface RequestError {
state: "error";
errorMsg: string;
}
interface RequestSuccess {
state: "ok";
pageContent: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess;
interface State {
currentPage: string;
requests: { [page: string]: RequestState };
}
В приведенном выше коде 3 различных состояния запроса определяются с помощью идентифицируемых типов объединения, поэтому различные состояния запроса можно легко отличить, тем самым делая обработку бизнес-логики более понятной. Далее, на основе обновленногоState
Тип, для обновления ранее созданногоrenderPage
и changePage
функция:
Обновленная функция renderPage
function renderPage(state: State) {
const { currentPage } = state;
const requestState = state.requests[currentPage];
switch (requestState.state) {
case "pending":
return `页面加载中~~~`;
case "error":
return `呜呜呜,加载第${currentPage}页出现异常了...${requestState.errorMsg}`;
case "ok":
`<div>第${currentPage}页的内容:${requestState.pageContent}</div>`;
}
}
Обновлена функция changePage.
async function changePage(state: State, newPage: string) {
state.requests[newPage] = { state: "pending" };
state.currentPage = newPage;
try {
const response = await fetch(getUrlForPage(newPage));
if (!response.ok) {
throw new Error(`无法正常加载页面 ${newPage}: ${response.statusText}`);
}
const pageContent = await response.text();
state.requests[newPage] = { state: "ok", pageContent };
} catch (e) {
state.requests[newPage] = { state: "error", errorMsg: "" + e };
}
}
существует changePage
В функции будут установлены разные состояния запроса в соответствии с разными ситуациями, а разные состояния запроса будут содержать разную информацию. так renderPage
функция может основываться на унифицированнойstate
значение атрибута для соответствующей обработки. Следовательно, при использовании различимых типов объединения каждое состояние запроса является допустимым состоянием, и не возникает проблем с недопустимыми состояниями.
Более
Если вы не хотите практиковать фальшивую ручку, нажмите на ссылку ниже, чтобы потренировать свои руки.
вопросы по практике машинописи
Если в статье есть какая-либо ошибка, укажите ее в комментариях, если вы считаете, что она вам полезна, добро пожаловать.点赞收藏
О, ваши лайки мотивируют меня продолжать писать обновления.
Наконец
Если вы хотите узнать о новых функциях es7-es12, см.Уже почти 2022 год, вы освоили все эти знания ES7-ES12?