Изучите TypeScript заново

внешний интерфейс TypeScript
Изучите TypeScript заново

Я обратился к истории и проверил, у этой истории нет хронологии, и на каждой перекошенной странице написаны слова «доброжелательность, праведность и нравственность».
Я не мог спать ни в горизонтальном, ни в вертикальном положении.После внимательного прочтения среди ночи я увидел в трещинах слова.По всей книге были написаны два слова «Ешь людей!».

предисловие

не делай заметки, не учись

онлайнTypeScriptАдрес практики:TypeScript Playground

Зачем нужен TypeScript

TypeScriptдаJavaScriptнадмножество , так как оно расширяетJavaScript,имеютJavaScriptНичего такого. Если вы настаиваете на детско-родительских отношениях,TypeScriptдаJavaScriptПодклассы, расширяющиеся на основе наследования.

TypeScriptПервопричиной рождения являетсяJavaScriptЭто слаботипизированный язык (тип языка может быть неявно преобразован), и невозможно выполнить проверку типов на этапе компиляции для раннего обнаружения ошибок.

TypeScriptПервоначальное намерение состояло в том, чтобы выполнить проверку типов и найти ошибки на ранней стадии, поэтому「类型」является его основной особенностью. Конечно, это просто предупреждение о том, что ваш код может выполняться не так, как ожидалось, например, если вы не передаете параметры в соответствии с объявленным типом, ваш код все равно будет работать. Это существенно отличается от строго типизированных языков, строго типизированные языки будут напрямую приводить к сбою компиляции, т.к.TypeScriptПросто перевод.

иJavaScriptразные,TypeScriptиспользуемый суффикс файла.tsимя расширения. браузер не распознан.tsфайл, поэтому вы должны поместитьTSкод вJavaScriptкод. Этот процесс преобразования называется转译,编译и转译Незначительные отличия:

  • Компиляция — это преобразование исходного кода на другой язык.
  • Транспиляция — это преобразование исходного кода в другой язык того же уровня абстракции.

мне не нравитсяTypeScript, потому что, на мой взгляд, это приводит к нескольким проблемам:

  1. увеличение затрат на обучение;
  2. Количество кода увеличивается;
  3. Сложность кода увеличивается

КонечноTypeScriptПольза от этого немалая: статическая инспекция позволяет заблаговременно найти ошибки, что действительно необходимо в сегодняшней предпроектной разработке, поскольку технический уровень членов бригады разный.TypeScriptможет помочь избежать многих ошибок, конечно, если выany大法верующие, я призываю вас быть добрыми. не используйTypeScriptи использоватьTypeScript, предпосылкой его использования должно быть то, что он может помочь вам решить конкретную проблему.

мне снова нравитсяTypeScript, потому что он продвинутыйJavaScript:

TypeScriptПредоставлять новейшие и развивающиесяJavaScriptхарактеристики, в том числе от2015ГодECMAScriptи функции в будущих предложениях, такие как асинхронные функции иDecorators, чтобы помочь создавать надежные компоненты.

Если честно, видел эту штуку два года назад😂, раньше всегда читал кусками, никак не могу осилить продвинутые части, в этот раз надо и нужно уладить.

~ Увы, общая тенденция, эта штука не умрет, иначе чужой код не поймет.

~ После того, как я закончил продвинутую часть, я почувствовал от всего сердцаTSЭто и продвинуто, и сложно, молча проливая слезы овощей.

текст

базовый тип

Восемь встроенных типов JS

  • строка (строка)
  • номер
  • логический
  • неопределенный
  • ноль ноль)
  • объект
  • Большое целое число (bigInt, новое в ES6)
  • символы (символ, новое в ES6)

TSсоответствующийexample(с пробелами после двоеточия или без):

let name: string = "bob";
let age: number = 37;
let isDone: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
let bigLiteral: bigint = 100n;
let sym: symbol = Symbol("me"); 

Array

Есть два способа определить тип массива:

// 元素类型[]
let list: number[] = [1, 2, 3];
// Array<元素类型>
let list: Array<number> = [1, 2, 3];

Определите массив указанных членов объекта:

interface MyObject {
    name: string;
    age: number;
}

let arr: MyObject[] = [{name: "兔兔", age: 18}] // OK

Tuple

Приведенный выше метод определения типа массива может определить только массив, внутренняя часть которого принадлежит определенному типу. Массивы различных внутренних типов могут быть определены с использованием типов кортежей:

let x: [string, number];

x = ['hello', 10]; // OK
x = [10, 'hello']; // Error

Обратите внимание, что тип кортежа может представлять только массив с известным числом и типом элементов, длина которого указана, а доступ за пределами границ вызовет ошибку. Например, в массиве может быть несколько типов, число и тип неопределенны, тогда сразуany[].

неопределенный и нулевой

Обратите внимание, что эти два являются особенными

по умолчаниюnullиundefinedявляется подвидом всех типов. То есть можно поставитьnullиundefinedПрисвоить любой тип переменной.

eg:

let str: string = 'hello';
str = null; // OK
str = undefined; // OK

let a: null = undefined; // OK
let b: undefined = null; // OK

Конечно, вы также можете указать--strictNullChecksфлаг, чтобы включить строгую проверку режима. В этой ситуации,nullиundefinedЭто отношение равенства с другими типами и может быть назначено толькоanyи их соответствующие типы, за одним исключениемundefinedтакже может быть назначеноvoidТип (думаю, вы объявляете возвращаемый тип для функции какvoid, но функция явно неreturnВ случае возвращается значение по умолчаниюundefined, что теперь является проявлением этого исключения).

void

voidУказывает, что типа нет, он равен другим типам и не может быть напрямую назначен:

let a: void;
let b: number = a; // Error

ты можешь только дать этоnull(только в--strictNullChecksесли не указано) иundefined. объявить одинvoidПеременные типа не очень полезны, и мы обычно объявляем их только тогда, когда функция не возвращает значение.

Стоит отметить, что методы без возвращаемого значения получатundefined, но нам нужно определить его какvoidвведите вместоundefinedтип. В противном случае будет сообщено об ошибке:

function fun(): undefined {
  console.log("this is TypeScript");
};
fun(); // Error

любой и неизвестный

anyпропустит проверку типа для значения, любое значение может быть присвоеноanyтип, его часто называютtop type, значит будетanyДафа – хорошее изречение.

let notSure: any = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK

unknownиanyНапример, все типы могут быть назначеныunknown:

let notSure: unknown = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK

unknownиanyСамая большая разница:

unknownдаtop type(любой тип егоsubtype) , иanyобаtop type, Сноваbottom type(это любой видsubtype) , что приводит кanyВ основном отказ от любой проверки типов.

так какanyобаtop type, Сноваbottom type, так что любой тип значения может быть присвоенany,в то же времяanyЗначение типа также может быть присвоено любому типу. ноunknownТолькоtop type, ему можно присвоить любой тип значения, но присвоить его можно толькоunknownиany, потому что только два из нихtop type.

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

never

neverТипы представляют типы значений, которые никогда не существуют.

Две ситуации, когда значение никогда не будет существовать:

  1. Если функция выполняется и бросаетаномальный, то у этой функции никогда не будет возвращаемого значения (поскольку выбрасывание исключения приведет к прямому прерыванию программы, из-за чего программа будет работать меньше, чем возвращаемое значение, то есть она имеет недостижимую конечную точку, и возврата никогда не будет) ;

  2. Код в функции, которая выполняет бесконечный цикл (бесконечный цикл), так что программа никогда не сможет дойти до шага, на котором функция возвращает значение, и никогда не будет возврата.

// 异常
function err(msg: string): never { // OK
  throw new Error(msg); 
}

// 死循环
function loopForever(): never { // OK
  while (true) {};
}

neverтого же типаnullиundefinedТочно так же он также является подтипом любого типа и может быть присвоен любому типу:

let err: never;
let num: number = 4;

num = err; // OK

но типа нет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

Особенности:neverПосле объединения с другими типами нетneverиз

// type Eg2 = string | number
type Eg2 = string | number | never

утверждение типа

Утверждения типов похожи на преобразования типов в других языках. Преобразования типов обычно происходят, когда вы болееTSПри изучении более подробной информации о значении.

Два способа достижения:

// 尖括号 语法
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;

Вывод типа

Если тип явно не указан, тоTypeScriptТип выводится в соответствии с правилами вывода типов.

следующее:

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // Error

ЗачемError, потому что на самом деле это эквивалентно:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7; // Error

TypeScriptТип выводится, когда нет явно указанного типа, что является выводом типа.

Если при его определении нет присвоения, оно будет выводиться какanyвведите без проверки типа вообще:

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

тип союза

Тип объединения указывает, что значение может быть одним из нескольких типов, используя|Отделить каждый тип.

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'; // OK
myFavoriteNumber = 7; // OK

перекрестный тип

Кросс-тип представляет собой объединение нескольких типов в один тип. Это позволяет нам объединять существующие типы в один тип, содержащий все необходимые свойства типа, используя&Определите тип перекрестка.

interface A {
  name: string,
  age: number
}
interface B {
  name: string,
  gender: string
}

let a: A & B = { // OK
    name: "兔兔",
    age: 18,
    gender: "男"
};

aобаAтипа, такжеBтип.

Примечание: объединение нескольких типов принимается за тип пересечения, но еслиkeyодинаковые, но разные типы,keyзаneverтип.

type A = string & number // A 为 never 类型

let a: A = (() => {throw new Error()})(); // OK

интерфейс

Прежде всего, давайте разберемся с концепцией интерфейса здесь простым способом: общий интерфейс определения фона — это интерфейс, вызываемый внешним интерфейсом, определяющий некоторые параметры и т. д.,TSИнтерфейс аналогичный, а также его можно понимать как определение некоторых параметров, указание какие параметры в переменной, и какой тип параметров в переменной.При использовании должны быть параметры этих соответствующих типов.Если меньше или более параметров, и тип параметра неправильный, будет сообщено об ошибке. Проще говоря, вы можете понять, что это определение более подробного типа объекта.

Первый пример:

function printLabel(labeledObj: { label: string }) {
  console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj); // OK

тебе лень писатьinterface, можно записать так. Этот способ написания является более свободным и проверяет только наличие этих необходимых свойств.

Второй пример:

interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj); // OK

Этот способ написания также является свободным, как и выше, в том числе из-за присваивания.

Уведомление:Запятые и точки с запятой можно использовать в типе и интерфейсе, но не в классе. Однако все три поддерживают окончания строк без символов.

Почему присваивание усложняет проверку типов

Посмотрите внимательнее на эту фразу:

Одним из основных принципов TypeScript является проверка типа структуры значения. его иногда называютопределение типа уткиилиструктурное подтипирование.

Так называемыйопределение типа уткиэто像鸭子一样走路并且嘎嘎叫的就叫鸭子, то есть обладающие характеристиками утки думают, что это утка, то есть путем формулирования правил, определяющих, реализует ли объект этот интерфейс (разумеется, вTSТак не сказано).

В приведенном выше коде запись объекта в параметр эквивалентна прямой передачеlabeledObjНазначение, этот объект имеет строгое определение типа, поэтому он не может иметь больше или меньше параметров. И когда вы используете объект снаружи с другой переменнойmyObjперенимать,myObjНе подвергается дополнительной проверке свойств, но выводится из типа какlet myObj: { size: number; label: string } = { size: 10, label: "Size 10 Object" };, затем поместите этоmyObjпереназначить наlabeledObj, в настоящее время, в соответствии с совместимостью типа, два типа объектов относятся копределение типа утки, потому что у обоих естьlabelсвойства, поэтому они считаются одинаковыми, поэтому этот метод можно использовать для обхода избыточной проверки типов.

interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}

printLabel({ size: 10, label: "Size 10 Object" }); // Error

необязательный атрибут

interface Props { 
  name: string; 
  age: number; 
  money?: number;
}

Необязательный атрибут — добавить имя необязательного атрибута после определения.?символ, подтверждающий, что это свойство является необязательным.

свойство только для чтения

interface Point {
  readonly x: number;
  readonly y: number;
}

let p: Point = { x: 10, y: 20 };
p.x = 5; // Error

использовать перед именем свойстваreadonlyключевое слово, чтобы указать свойство только для чтения, свойство объекта может изменить свое значение только тогда, когда объект только что создан, иconstпохоже, ноconstМожно запретить изменение только базового типа.Для ссылочных типов можно запретить изменение только ссылочного адреса.Можно изменить внутренние свойства.Чтобы предотвратить изменение внутренних свойств ссылочного типа, следует использовать .readonly.

ReadonlyArray

Для массивовTSиReadonlyArray<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

В последней строке видно, что даже если весьReadonlyArrayПрисвоение нормальному массиву также невозможно, и в этом случае можно использовать утверждение типа:

a = ro as number[];

Уведомление:readonlyТип массива только для чтения, объявленный с помощьюReadonlyArrayОбъявленный тип массива только для чтения, оба эквивалентны.

доказывать:

let arr1: readonly number[] = [1, 2];
let arr2: ReadonlyArray<number> = [1, 2, 3];

arr1[0] = 0; // Error
arr2[0] = 0; // Error
arr1.push(3); //Errpr
arr2.push(4); //Error
arr1 = arr2; // OK

Способы обойти дополнительные проверки свойств

  1. Тип совместимый

    Пример в начале был объяснен очень подробно, и используется операция присваивания, поэтому я не буду повторяться.

  2. утверждение типа

    Тип утверждения равнозначен тому, что вы говорите программе, что точно знаете, что делаете, тогда программа не будет дополнительно изучать естественные свойства.

    interface Props { 
      name: string; 
      age: number; 
      money?: number;
    }
    
    let p: Props = {
      name: "兔神",
      age: 25,
      money: -100000,
      girl: false
    } as Props; // OK
    
  3. подпись индекса

    interface Props { 
      name: string; 
      age: number; 
      money?: number;
      [key: string]: any;
    }
    
    let p: Props = {
      name: "兔神",
      age: 25,
      money: -100000,
      girl: false
    }; // OK
    

произвольные свойства

TypeScriptПоддерживаются два типа подписи индекса: строки и числа.

После определения произвольного свойства тип как детерминированных, так и необязательных свойств должен быть подмножеством его типа, поскольку детерминированные и необязательные свойства также считаются типом произвольного свойства:

interface Person {
    name: boolean; // Error
    age?: number; // Error
    sex: string; // OK
    girl: undefined; // OK
    [propName: string]: string;
}

Оба типа индексов могут использоваться одновременно, но возвращаемое значение числового индекса должно быть подтипом типа возвращаемого значения строкового индекса. Это связано с тем, что при использованииnumberПри индексации,JavaScriptпреобразует его вstringЗатем перейдите к объекту index.

class Animal {
  name: string;
}
class Dog extends Animal {
  breed: string;
}

interface NotOkay {
  [x: number]: Animal; // Error
  [x: string]: Dog;
}

interface Okay {
  [x: number]: Dog; // OK
  [x: string]: Animal;
}

Еще одна вещь, на которую мы должны обратить внимание, когда какой-либо атрибут использует тип объединения и в атрибуте есть необязательные атрибуты, объединение должно бытьundefinedtype, иначе компиляция сообщит об ошибке по понятным причинам, т.к. необязательные атрибуты необязательны:

interface Props { 
  name: string; 
  age: number; 
  money?: number; // 这里真实的类型应该为:number | undefined
  [key: string]: string | number | undefined;
}

let p: Props = {
  name: "兔神",
  age: 25,
  money: -100000
}; // OK

Кроме того, вы можете сделать любое свойство доступным только для чтения, предотвращая присвоение свойств.

интерфейс наследует интерфейс

Интерфейс наследует интерфейс, используя ключевые словаextends, Суть наследования заключается в копировании и извлечении общего кода, поэтому подинтерфейс имеет определение типа родительского интерфейса:

interface Shape {
  color: string;
}
interface Square extends Shape {
  sideLength: number;
}

let square: Square = { sideLength: 1 }; // Error
let square1: Square = { sideLength: 1, color: 'red' }; // OK

TSДругой момент: интерфейсы могут наследоваться несколько раз.

interface Shape {
  color: string;
}
interface PenStroke {
  penWidth: number;
}
interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square: Square = { sideLength: 1 } // Error
let square1: Square = { sideLength: 1, color: 'red' } // Error
let square2: Square = { sideLength: 1, color: 'red', penWidth: 2 } // OK

Здесь следует отметить, что большинство языков не поддерживают множественное наследование, по понятным причинам множественное наследование может вызвать путаницу:

  1. Если родительский класс, унаследованный подклассом, имеет ту же переменную-член, подкласс не сможет отличить, какую переменную-член родительского класса использовать при ссылке на переменную;
  2. Если подкласс наследует несколько родительских классов с одним и тем же методом, и подкласс не переопределяет метод (если он переопределен, метод в подклассе будет использоваться напрямую), то при вызове этого метода невозможно будет определить какой метод родительского класса вызывать.

pythonПоддержка множественного наследования, так называемого множественного наследования, сутьmixin,JSтакже доступныmixinРеализовать множественное наследование.

существуетTS, если два или более родительских интерфейса множественного наследования имеют одинаковые свойства, но определенные типы различаются,TSОн сообщит об ошибке напрямую, и она не будет принятаmixinСтратегия. В связи с этим хотелось бы задать вопрос: если вы извлекаете общий код, то почему вы везде ставите общий код?TSЭто то, что я думал, это должно прояснить вашу путаницу. Следовательно, при использовании нескольких наследований сначала убедитесь, что родительский интерфейс не имеет общих свойств или что типы, определенные общими свойствами, одинаковы.

interface Shape {
  name: string;
  color: string;
}
interface PenStroke {
  name: number;
  penWidth: number;
}
interface Square extends Shape, PenStroke { // Error
  sideLength: number;
}
interface Shape {
  name: string;
  color: string;
}
interface PenStroke {
  name: string;
  penWidth: number;
}
interface Square extends Shape, PenStroke { // OK
  sideLength: number;
}
let square: Square = { // OK
    sideLength: 1, 
    color: 'red', 
    penWidth: 12, 
    name: '兔神'
}

в интерфейсеnew

существуетTSНа официальном сайте, например, видны

interface ClockConstructor {
  new (hour: number, minute: number): any;
}

Этот способ написания очень запутан. Официальный сайт для использования в интерфейсеnewПодробных инструкций тоже нет, только примеры, что отстой.

Мое понимание:newЗа ним следует конструктор, который используется для создания экземпляра. И интерфейс используется для описания типа объекта, так что же это за тип объекта, который содержит конструктор? ответ类 class.

// 例1
interface ClockConstructor {
  new (hour: number, minute: number): any;
}

let C:ClockConstructor = class {} // OK

// 例2
interface CPerson {
  new(name: string): Date;
}

let p: CPerson = class People extends Date {} // OK

мы не показываем заявлениеconstructor, так что будет пустоconstructorфункция сверху. Здесь я нахожу обнаружение довольно странным:

interface ClockConstructor {
  new (hour: number, minute: number): any;
}

let C:ClockConstructor = class { // OK
    constructor() {}
}

let C1:ClockConstructor = class { // OK
    constructor(h: number) {}
}
 
let C2:ClockConstructor = class { // OK
    constructor(h: number, m: number) {}
}

let C3:ClockConstructor = class { // Error
    constructor(h: string, m: number) {}
}

let C4:ClockConstructor = class { // Error
    constructor(h: number, m: number, b: number) {}
}

Что мы видим в механизме обнаружения здесь, так это то, что параметров мало, и есть много совместимых параметров, которые не являются строгими. Я был очень озадачен.Когда я научился обратно, я узнал, что есть双向协变Концепция чего-либо:

TsПри сравнении параметров функции по умолчанию применяется стратегия двусторонней ковариации: только когда параметр исходной функции может быть присвоен целевой функции или наоборот, присвоение может быть выполнено успешно.

тип функции

объявление функции

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

существуетTypeScriptВ определении типа ,=>Используется для представления определения функции, левая сторона — это тип ввода, который необходимо заключить в круглые скобки, а правая сторона — это тип вывода. Избегайте всеми средствамиES6Функции стрелок перепутаны.

Определение типов функций с интерфейсами

interface SearchFunc{
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(source: string, subString: string) { // OK
  let result = source.search(subString);
  return result >-1;
};

При использовании интерфейса функционального выражения для определения функции ограничение типа слева от знака равенства может гарантировать, что количество параметров, типов параметров и типов возвращаемых значений останутся неизменными при назначении имени функции в будущем.

в функцииthisутверждение

TypeScriptЭто будет выведено с помощью анализа потока кода.thisЧто должно быть в функции, мы также можем указать явно в функцииthisКакой тип должен быть. Пример выглядит следующим образом:

interface Obj {
    fn: (this: Obj, name: string) => void;
}

let obj: Obj = {
    fn(name: string) {}
}

obj.fn("兔兔"); // OK

так какJavaScriptВ спецификации указано, что у вас не может быть файла с именемthisпараметры, поэтомуTypeScriptИспользуйте это синтаксическое пространство, чтобы вы могли объявить в теле функцииthisтип.

Примечание: этоthisДекларация типа должна идти первой в параметре:

interface Obj {
    // Error:A 'this' parameter must be the first parameter
    fn: (name: string, this: Obj) => void;
}

Вот лучший пример, чтобы почувствовать:

interface Obj {
    fn: (this: Obj, name: string) => void;
}

let obj: Obj = {
    fn(name: string) {}
}

let rab: Obj ={
    fn(name: string) {}
}

obj.fn("兔兔"); // OK
obj.fn.call(rab, "兔兔"); // OK
obj.fn.call(window, "兔兔"); // Error: this 应该为 Obj 类型

необязательный параметр

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

перегрузка

Перегрузка позволяет функции вести себя по-разному, когда она принимает другое количество или тип аргументов.

重载концепция обученияJAVA(JAVA中的重载) при контакте,JSНет такого понятия,TSПерегрузку личного чувства следует назвать более函数签名重载. Поскольку окончательная реализация функции по-прежнему зависит от типа решения для обработки, предыдущее определение функции предназначено только для точного выражения типа вывода, соответствующего типу ввода.

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

встроенные объекты

ECMAScriptВстроенные объекты, предусмотренные стандартом:

String,Number,Boolean,Error,Date,RegExp Ждать.

let s: String = new String('兔神');
let n: Number = new Number(123);
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

DOMиBOMВстроенные объекты:Document,HTMLElement,Event,NodeList Ждать.

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

массивоподобный объектIArguments:

function sum() {
    let args: IArguments = arguments;
}

IArgumentsНа самом деле это:

interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

Конечно, еще много, что можно увидеть здесьФайл определения для основной библиотеки TypeScript

введите псевдоним

Псевдоним типа — это присвоение типу определенного имени, а затем, где бы этот тип ни использовался, это имя можно использовать вместо него в качестве типа. Это просто имя, оно не создает новый тип. использоватьtypeключевое слово для определения:

type StringType = string;
let str: StringType;
str = 'hello';
str = 123 // Error

Примечание. Псевдонимы типов не могут бытьextendsиimplements, и не может появляться нигде справа от объявления.

typeреализовать наследование, можно использовать кросс-типыtype A = B & C & D.

Тип строкового литерала

Тип строкового литерала используется, чтобы ограничить значение только одной из нескольких строк.

type Name = 'ALisa' | 'Bob' | 'Cola'

let name: Name = 'Alisa'; // Error ①
let name1: Name = 'ALisa'; // OK
let name2: Name = 'Bob'; // OK
let name3: Name = 'Cola'; // OK
let name4: Name = '兔兔'; // Error

Причина вышеуказанной ошибки ①:

По умолчанию,TS будет DOM typingsПоскольку это глобальная среда выполнения, поэтому, когда мы объявляемnameкогда, сDOMглобальный вwindowпод объектомnameНедвижимость имеет такое же имя. Таким образом, сообщаетсяCannot redeclare block-scoped variable 'name' Ошибка.

Тип перечисления Enum

Перечисление — это набор именованных целочисленных констант, которые часто встречаются в повседневной жизни, например день недели.SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAYявляется перечислением.

Перечисление — это структура данных, с помощью которой мы можем определить некоторые константы с именами, четко выразить намерение или создать набор различных вариантов использования.TSПоддерживаются числовые и строковые перечисления.

Перечисление номеров

Используйте перечисление, чтобы определить 7 дней в неделю:

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

Членам перечисления присваиваются значения из0Начиная с увеличивающегося числа, мы можем получить доступ к членам перечисления так же, как к свойствам объекта:

console.log(Days.Sun) // 0
console.log(Days.Mon) // 1
......
console.log(Days.Sat) // 6

Мы также можем инициализировать элементы перечисления, тогда члены после инициализированного члена будут автоматически расти на его основе.1:

enum Days {Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days.Sun) // 1
console.log(Days.Mon) // 2
......
console.log(Days.Sat) // 7

Перечисление строк

Перечисления строк просты, назначьте каждому элементу строковый литерал напрямую:

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

Неоднородная нумерация

Гетерогенные перечисления То есть перечисления могут смешивать строковые и числовые члены:

enum Direction {
    name = '兔兔',
    age = 18
}

Члены, которые могут быть подсчитаны

Вычисляемые члены означают, что при инициализации члена перечисления его можно динамически вычислить с помощью выражений, функций и т. д. или ссылки на ранее определенный элемент перечисления-константы.

Момент, требующий большого внимания: предпосылка использования вычислимых членов состоит в том, что текущее перечисление должно быть числовым перечислением, то есть все члены должны бытьnumberтип.

enum Direction {
    None,
    Read = 1 << 1,
    Write = 1 << 2,
    ReadWrite = Read | Write,
    G = "123".length,
    Age = 18,
    Sex = getSex()
}

function getSex() {
    return 12
}

Если константное выражение enum оценивается какNaNилиInfinity, на этапе компиляции будет сообщено об ошибке. Оригинальные слова официального сайта:

It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity.

Но про тест не сообщит об ошибке.

перечисление времени выполнения

Перечисления — это объекты, которые реально существуют во время выполнения.Посмотрите, как перечисления выглядят при компиляции:

image.png

Узнал снова здесь, здесьLIFEНаписание в функции очень умное,E["X"] = 0; E[0] = 'X';прямо упрощает доE[E["X"] = 0] = "X";. Вот почему перечисления имеют обратное сопоставление, следует отметить, что члены строкового перечисления не имеют обратного сопоставления.

Поскольку перечисление — это объект, реально существующий во время выполнения, мы можем передать его функции:

enum E {
    X, Y, Z
}
function f(obj: { X: number }) {
    return obj.X;
}

f(E); // OK

константное перечисление

с помощью перечисленияconstмодификатор, чтобы избежать накладных расходов на дополнительный сгенерированный код и дополнительный косвенный доступ к элементам перечисления.

Это предложение может быть непростым для понимания, просто посмотрите на картинку и скажите:image.png

Мы видим, чтоconstПосле компиляции перечисление не компилируется вLIFEФункция напрямую удаляется, а константа прямо заполняется в том месте, где она применяется. Целью этого является сохранение производительности.

своего рода

публичный модификатор

Модификатор атрибута по умолчанию имеет значениеpublicПубличный, то есть к свойствам и методам класса можно получить доступ извне, и вы можете решить, объявлять ли его явно в соответствии с вашими личными предпочтениями.

class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

частный модификатор

privateиpublicНапротив, к приватным модификаторам, то есть к свойствам и методам класса, нельзя получить доступ извне.

class Animal {
    public static age: number = 18;
    private static title: string = '兔兔';
}

Animal.age; // OK
Animal.title; // Error

защищенный модификатор

protectedмодификатор сprivateМодификаторы ведут себя аналогично, с одним отличием,protectedчлены вПроизводный класспо-прежнему доступен. Обратите внимание, вотв производном классе, вместоэкземпляр, экземпляр подкласса.

пример 1:

class Animal {
    private age: number = 18;
    protected title: string = '兔兔';
}

class Dog extends Animal {
    getAge() {
        console.log(this.age) // Error
    }

    getTitle() {
        console.log(this.title) // OK
    }
}

Пример 2:

class Animal {
    private static age: number = 18;
    protected static  title: string = '兔兔';
}

class Dog extends Animal {
    getAge() {
        console.log(Dog.age) // Error
    }

    getTitle() {
        console.log(Dog.title) // OK
    }
}

свойства параметра

Мы также можем использовать параметр внутреннего метода классаpublic、private、protectedМодификатор, его роль заключается в том, чтобы упростить нам определение и инициализацию члена в одном месте.

class Animal {
    constructor(public name: string, private age: number, protected sex: string) {}
}

Эквивалентно:

class Animal {
    public name: string;
    private age: number;
    protected sex: string;
    constructor(name: string, age: number, sex: string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

доказывать:

class Animal {
    constructor(public name: string) {}
    getName() {
        console.log(this.name)
    }
}

let animal = new Animal('兔兔');
animal.getName(); // "兔兔"

абстрактный класс

Абстрактные классы используются в качестве базовых классов для других производных классов и не могут быть созданы. В отличие от интерфейсов, абстрактные классы могут содержать детали реализации членов. 

abstractКлючевые слова используются для определения абстрактных классов и определения абстрактных методов в абстрактных классах.

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log("roaming the earth...");
    }
}

let animal = new Animal(); // Error: 抽象类不允许被实例化

Абстрактные методы абстрактного класса не содержат конкретной реализации и должны быть реализованы в производном классе. Методы абстрактного синтаксиса и методы интерфейса. Оба являются определенными сигнатурами методов, но не имеют тела. Однако он должен содержать абстрактные методыabstractключевое слово и может содержать модификаторы доступа.

abstract class Department {
    constructor(public name: string) {
    }
    printName(): void {
        console.log('Department name: ' + this.name);
    }
    abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
    constructor() {
        super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
    }
    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }
    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}
let department: Department; // OK:允许创建一个对抽象类型的引用
department = new Department(); // Error: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // OK:允许对一个抽象子类进行实例化和赋值
department.printName(); // OK
department.printMeeting(); // OK
department.generateReports(); // Error: 方法在声明的抽象类中不存在

класс реализует интерфейс

иC#илиJavaОсновная функция интерфейса такая же,TypeScriptИнтерфейсы также можно использовать для явного принуждения класса к соблюдению контракта.

То есть мы также можем использовать классы для реализации интерфейсов, используя здесь ключевые словаimplements:

interface Title{
  title: string;
}
class title implements Title{
  title: string = '兔兔';
  age: number = 18; // 在实现接口的基础上,也可以添加其他的属性和方法
}

Класс может реализовывать несколько интерфейсов:

interface Age {
  age: number;
}

interface Title{
  title: string;
}
class title implements Title, Age{
  title: string = '兔兔';
  age: number = 18;
}

Разница между абстрактным классом и интерфейсом

Класс может наследовать абстрактный класс, а класс также может реализовывать интерфейс.В этих двух ситуациях я всегда чувствую, что абстрактный класс очень похож на интерфейс, поэтому давайте проанализируем его.

Абстрактные классы используются для захвата общих характеристик подклассов, тогда как интерфейсы представляют собой наборы абстрактных методов; абстрактные классы не могут быть созданы, они могут использоваться только как суперклассы подклассов и используются для создания подклассов в шаблоне иерархии наследования, в то время как интерфейс — это просто форма, интерфейс сам по себе ничего не может.

Во-вторых, абстрактные классы могут иметь реализации методов по умолчанию.Подклассы используют ключевое слово extends для наследования абстрактных классов.Если подклассы не являются абстрактными классами, они должны предоставлять реализации всех объявленных методов в абстрактных классах. Интерфейс полностью абстрактный, в нем нет реализации методов, подклассы используют ключевое слово Implements для реализации интерфейса, он должен обеспечить реализацию всех объявленных методов в интерфейсе.

статическая часть и часть экземпляра

Сначала посмотрите на пример: определите интерфейс с сигнатурой конструктора и попытайтесь реализовать этот интерфейс:

interface Person {
  new(name: string)
}
class People implements Person {
  constructor(name: string) {
    // ...
  }
}
// 报错:no match for the signature 'new (name: string): any'.

Это потому что:Когда класс реализует интерфейс, проверяется тип только экземплярной части.constructorсуществует в статическом разделе, поэтому не проверяется. (Здесь статическая часть относится к конструктору, причина в том, что статические свойства или статические методы напрямую связаны с конструктором)

Итак, сделайте следующее:

// 针对类构造函数的接口
interface CPerson {
  new(name: string);
}
// 针对类的接口
interface IPerson {
  name: string;
  age: number;
}
function create(c: CPerson, name: string): IPerson {
  return new c(name);
}
class People implements IPerson {
  name: string;
  age: number;
  // 这里未声明 构造函数,根据 ES6 规定会有默认的顶上来
}

let p = create(People, 'funlee'); // 可以

Подход здесь состоит в том, чтобы определить новый интерфейс без подписи конструктора и позволить классу реализовать этот интерфейс.Цель состоит в том, чтобы использовать принцип совместимости типов для проверки типа конструктора, поэтому мы также можем использовать функциональные выражения для записи :

interface CPerson {
  new(name: string):any;
}

interface IPerson {
  name: string;
  age: number;
}

let p: CPerson = class People implements IPerson {
    name: string;
    age: number;
    constructor(name: string) {}
}

Класс наследования интерфейса

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

Какие? Интерфейс еще может наследовать класс, что это за операция. До сих пор мы знали, что интерфейсы могут наследовать только интерфейсы, потому что они относятся к одной и той же категории.Для классов наследования интерфейсов официальное объяснение таково:существуетTSПри объявлении класса он также объявляет тип экземпляра класса..

Итак, мы можем объявить переменную какGreeterтип:let greeter: Greeter = new Greeter("world");, здесь после двоеточияGreeterНа данный момент он существует как тип экземпляра класса.newНазадGreeterсуществует как конструктор.

Делая шаг вперед, мы знаемclassСуть в томfunctionСинтаксический сахар для:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

перевести наES5:

"use strict";
var Greeter = /** @class */ (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());

тип экземпляра этого классаGreeterсоответствующий переводуES5в конструктореGreeterТип экземпляра , поскольку он относится к типу экземпляра, поэтому, когда интерфейс наследует класс, конструктор, статические свойства и статические методы не включаются (конечно, тип экземпляра не должен включать конструктор, статические свойства или статические методы). методы):

class Point {
    /** 静态属性,坐标系原点 */
    static origin = new Point(0, 0);
    /** 静态方法,计算与原点距离 */
    static distanceToOrigin(p: Point) {
        return Math.sqrt(p.x * p.x + p.y * p.y);
    }
    /** 实例属性,x 轴的值 */
    x: number;
    /** 实例属性,y 轴的值 */
    y: number;
    /** 构造函数 */
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    /** 实例方法,打印此点 */
    printPoint() {
        console.log(this.x, this.y);
    }
}

Тип, объявленный одновременно, эквивалентен:

interface PointInstanceType {
    x: number;
    y: number;
    printPoint(): void;
}

let p1: Point;
let p2: PointInstanceType; // p1 的类型与 p2 等价

Я считаюКласс наследования интерфейса, впредь пользоваться не буду, не надо возиться.

Дженерики

Обобщения относятся к функции, при которой при определении функции, интерфейса или класса конкретный тип не указывается заранее, но тип указывается при его использовании.

общее определение

Использование дженериков<类型变量>определение:

function identity<T>(arg: T): T {
    return arg;
}

Также возможно определить сразу несколько параметров типа:

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

Используйте общие переменные

function identity<T>(arg: T): T {
    return arg;
}

// 使用
identity<number>(1); // OK:明确的指定`T`是`number`类型
identity(1); // OK:让编译器自己推断类型

общие ограничения

Когда универсальная переменная используется внутри функции, ее свойствами или методами нельзя манипулировать произвольно, поскольку заранее неизвестно, какого она типа.

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length); // Error
    return arg;
}

В приведенном выше примере общийTне обязательно содержит атрибутыlength, поэтому при компиляции сообщается об ошибке.

Решение:

// 1
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // OK
    return arg;
}

// 2
function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // OK
    return arg;
}

Приведенные выше два способа записи по существу указывают на то, что параметрArrayтип, поэтому вы можете использоватьlengthАтрибуты.

Утверждения типа не допускаются для универсальных типов, поскольку дженерики представляют любой или все типы, например.stringТип не может быть утвержден какobjectтип. Итак, чтобы наложить ограничения на дженерики, нам нужно использовать интерфейс наследования для достижения:

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // OK
    return arg;
}

loggingIdentity({length: 10, value: 3}); // OK
loggingIdentity([1,2]); // OK

универсальный интерфейс

interface GenericIdentityFn {
    <T>(arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;

Мы можем увеличить общий параметр до имени интерфейса:

interface Person<T> {
    name: T;
    getAge(arg: T): T;
}

let myIdentity: Person<string> = {
    name: "兔兔",
    getAge(name) {
        return name
    }
};

универсальный класс

Подобно универсальным интерфейсам, дженерики также могут использоваться в определении типа класса:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();

Следует отметить одну вещь: универсальный класс относится к типу части экземпляра, поэтому статические свойства класса не могут использовать универсальный тип.

class GenericNumber<T> {
    name: T;
    static zeroValue: T; // Error
    add: (x: T, y: T) => T;
    constructor(name: T) {
        this.name = name;
    }
}

Типы по умолчанию для универсальных параметров

существуетTypeScript 2.3Позже мы можем указать типы по умолчанию для параметров типа в дженериках. Этот тип по умолчанию работает, когда дженерики используются без указания параметра типа непосредственно в коде, и его нельзя вывести из фактического параметра значения.

function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

тип гвардии

Типы Union подходят для ситуаций, когда значения могут быть разных типов. Но что, если мы хотим точно знать, что представляет собой параметр определенного типа?

interface A {
    name: string;
    age: number;
}
interface B {
    sex: string;
    home: string;
}

function doSomething(person: A | B): void {
    if(person.name) { // Error
        // ...
    }
}

Приведенный выше способ записи приводит к ошибке компиляции, потому что нет уверенности, что во время выполненияpersonТипAвсе ещеB, вы можете подумать, что даже если типBчас,nameсвойство не существует, также возвращаетundefinedчтобы не вводить нынешнее суждение, которое верно, но вотTS, для атрибутов, не определенных в типе, доступ сообщит об ошибке, которую можно назватьпроверка типов. КонечноAиBДоступ к общим свойствам можно получить обычным образом, потому что они есть у всех.

Чтобы сообщить программе, что мы знаем, что они делают, подтвердите, что мы используем, чтобы заставить ее работать:

function doSomething(person: A | B): void {
    if((person as A).name) { // OK
        // ...
    }
}

Недостатки этого способа очевидны, то есть нам приходится многократно использовать утверждения типа при необходимости доступа к свойствам типа.Тип защитный механизмЧтобы помочь нам с такими проблемами, охранники типов — это выражения, которые проверяются во время выполнения, чтобы гарантировать, что тип находится в определенной области. Эта концепция звучит похожеJSсерединаwithсинтаксис, но здесь он ограничен типом в области видимости.

Охранники пользовательского типа

Использование определения типа

Чтобы определить защиту типа, мы просто определяем функцию, возвращаемое значение которой представляет собойпредикат типа:

function isA(person: A | B): person is A{
    return(person as A).name !== undefined;
}

// 使用
function doSomething(person: A | B): void {
    if(isA(person)) { // OK
        // ...
    }
}

Так называемыйпредикат типа, что значитparameterName is TypeparameterNameДолжно быть имя параметра из текущей сигнатуры функции.

Приведенный выше код понимается так: возвращаемое значение равноtrueчас,person is Aучредил.

Этот способ написания слишком болезненный, потому что необходимо определить дополнительную функцию, чтобы судить, является ли это указанным типом.Если есть несколько типов, которые нужно оценить, необходимо дополнительно определить соответствующее количество функций.Каковы преимущества этого способа письма? Преимущество, упомянутое выше, заключается в том, что типы в области могут быть ограничены.

Простой пример для иллюстрации:

function isString1(test: any): test is string{
    return typeof test === "string";
}
function isString2(test: any): boolean{
    return typeof test === "string";
}

Вот два примера, один из которых возвращает предикат типа, а другой возвращает логическое значение, используя:

function doSomething(param: any): void {
    if(isString1(param)) {
        console.log(param.toFixed(2)); // 编译时 Error
    }
    if(isString2(param)) {
        console.log(param.toFixed(2)); // 编译时 OK,但运行时 Error
    }
}

doSomething("兔兔");

ПервыйifУсловные суждения используют предикаты типа, поэтому в этомifв областиparamтип ограниченstring,stringтип нетtoFixed()метод, поэтому ошибка компиляции;

секундаifУсловное суждение использует возвращаемое значение логического типа,paramТип не ограничиваетсяstring,В настоящее времяparamимеет типanyanyПроверка типов пропускается, поэтому компиляция проходит.

использоватьinоператор

inОператор используется для поиска свойств объекта, обратите внимание, что он найдет свойства в цепочке прототипов. мы обычно используемinБольше всего места должно быть при обходе объектаfor...in...цикл.

Здесь мы используемinТакже хорошей стратегией является определение того, существует атрибут или нет:

function doSomething(person: A | B): void {
    if("name" in person) { // OK
        // ...
    }
}

typeofтип гвардии

inхорошо, этоtypeofЕстественно тоже. Фактически используется предыдущая перегрузка функцийtypeofТип охранник.

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") { // OK
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") { // OK
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

будь осторожен:typeofРаспознаются только две формы типгардов:typeof v === "typename"иtypeof v !== "typename","typename"должно быть"number","string","boolean"или"symbol". ноTypeScriptНе мешает вам сравнивать с другими строками, язык не распознает эти выражения как охранники типа.

instanceofтип гвардии

typeofвышли, чтоinstanceofНе удивляйтесь, мы также можем использоватьinstanceofуточнить тип. иtypeofТочно так же здесь не приведены примеры.

keyofОператоры запроса типа индекса

для любого типаT,keyof TРезультатом является объединение всех имен общедоступных свойств типа:

interface Eg1 {
  name: string,
  readonly age: number,
}
// T1的类型实则是 "name" | "age"
type T1 = keyof Eg1

class Eg2 {
  private name: string;
  public readonly age: number;
  protected home: string;
}
// T2实则被约束为 "age"
// 因为name和home不是公有属性,所以不能被keyof获取到
type T2 = keyof Eg2

Другой пример:

interface Eg1 {
  name: string,
  readonly age: number,
}

interface Eg2 {
  sex: string
}
// T1的类型实则是 "name" | "age" | { sex: string }
type T1 = keyof Eg1 | Eg2

let a: T1 = "name"; // OK
let b: T1 = "age"; // OK
let c: T1 = { // OK
  sex: "男"
}

Уведомление:keyof anyРезультатstring | number | symbol, Причина также очень проста для размышления. Разве это не три общих типа ключ-значение, которые у нас есть?

TypeScript 2.8действующий по поперечному типуkeyofпреобразуется для воздействия на поперечиныkeyofСоюз. другими словами,keyof (A & B)будет преобразован вkeyof A | keyof B. Это изменение должно исправить этоkeyofПроблема несогласованности вывода выражений.

type A = { a: string };
type B = { b: string };
type T1 = keyof (A & B);  // "a" | "b"
type T2<T> = keyof (T & B);  // keyof T | "b"
type T3<U> = keyof (A & U);  // "a" | keyof U
type T4<T, U> = keyof (T & U);  // keyof T | keyof U
type T5 = T2<A>;  // "a" | "b"
type T6 = T3<B>;  // "a" | "b"
type T7 = T4<A, B>;  // "a" | "b"

typeofоператор

существуетTSсередина,typeofОператоры могут использоваться для получения типа переменной или объекта. то естьTSправильноtypeofОператор расширяется:

type Person = {
  name: string;
  age: number;
}

let man: Person = {
  name: "兔兔",
  age: 18
}

type Human = typeof man;
// type Human = {
//   name: string;
//   age: number;
// }

Это часто бывает полезно. Конкретный случай: я используюimport * as options from '...'Импортировать все модули, на данный моментoptionsПо умолчаниюanyтип, когда я обращаюсь к свойствам объекта с помощью индексации строк (т.е.options[key]), это невозможно, поэтому вам нужно определить свойство, чтобы судитьkeyСуществует ли защита типа в указанном объекте:

export function isValidKey(
  key: string | number | symbol,
  object: object // 不要忘了, any可以赋给object
): key is keyof typeof object {
  return key in object
}

T[K]оператор доступа к индексу

interface Eg1 {
  name: string,
  readonly age: number,
}
// string
type V1 = Eg1['name']
// string | number
type V2 = Eg1['name' | 'age']
// any
type V3 = Eg1['name' | 'age2222'] // Error
// string | number
type V4 = Eg1[keyof Eg1]

T[keyof T]способ получитьTвсеkeyТип объединения, состоящий из типов;T[keyof K]образом, полученноеTсерединаkeyи существовать одновременноKТип объединения, состоящий из типов времени.

Примечание: если[]серединаkeyСуществует лиTв , естьany;так какTSЯ не знаюkeyкакой тип в конечном итоге, такany; и также сообщит об ошибке.

тип карты

TypeScriptПредоставляет способ создания новых типов из старых типов —тип карты. В сопоставленном типе новый тип таким же образом преобразует каждое свойство старого типа.

ключевое слово требуетсяin:

interface Rabbit {
    name: string;
    age: number;
}

type ReadonlyRabbit<T> = { // 映射类型
    readonly [K in keyof T]: T[K];
}

// 使用
let rabbit: ReadonlyRabbit<Rabbit> = {
    name: "兔兔",
    age: 18
}

rabbit.name = "蛋黄" // Error:readonly 

Вопрос: почемуtypeможет использоваться для сопоставления типов, аinterfaceнет?

Ответ: Потому что это называетсяtypescriptне называетсяinterfacescript, Хахаха, шучу. Причина в том, чтоtypeподдерживает вычисляемые свойства,interfaceне поддерживается,typescriptнастройки, так что перестаньте спрашивать, почему.

Давайте рассмотрим простейший тип отображения и его компоненты:

type Keys = 'option1' | 'option2';
type Flags = { [K in Keys]: boolean };

Его синтаксис такой же, как тип синтаксиса сигнатуры индекса, который используется внутренне.for .. in. Имеет три части:

  1. переменная типаK, которое по очереди связано с каждым свойством.
  2. строковое литеральное объединениеKeys, который содержит коллекцию имен свойств для повторения.
  3. Тип результата свойства.

Условный тип

Условные типы будут выполнять определение отношений типов с условным выражением, чтобы выбрать один из двух типов, используя ключевое словоextendsС тернарным оператором:

T extends U ? X : Y

Вышеупомянутый тип означает, что еслиTможет быть назначен наU, то типX, в противном случаеY.

type TypeName<T> =
    T extends string ? "string" :
    T extends number ? "number" :
    T extends boolean ? "boolean" :
    T extends undefined ? "undefined" :
    T extends Function ? "function" :
    "object";
type T0 = TypeName<string>;  // "string"
type T1 = TypeName<"a">;  // "string"
type T2 = TypeName<true>;  // "boolean"
type T3 = TypeName<() => void>;  // "function"
type T4 = TypeName<string[]>;  // "object"

Распределенные условные типы

Распределенные условные типыэтоextendsКогда предыдущий параметр является типом объединения, он будет декомпозирован (обход всех подтипов по очереди для условной оценки) для оценки типа объединения. Конечный результат затем объединяется в новый тип объединения.

Значит этоT extends U ? X : Y,какTимеет типA | B | C, он будет проанализирован как(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

// type A1 = 1
type A1 = 'x' extends 'x' ? 1 : 2;

// type A2 = 2
type A2 = 'x' | 'y' extends 'x' ? 1 : 2;

// type A3 = 1 | 2
type P<T> = T extends 'x' ? 1 : 2;
type A3 = P<'x' | 'y'>

Посмотрите на пример, чтобы говорить, так что выше сказаноКогда параметр перед extends является типом объединения, он будет разложендолжно быть точнее, какКогда тип перед extends является универсальным типом, а универсальный тип передается как тип объединенияразложится.

  • Предотвратить ключевое слово extends для функции распределения типов объединения

Если вы не хотите, чтобы вас разлагали (распределяли), подход также очень прост, вы можете обернуть следующее в простой тип кортежа:

type P<T> = [T] extends ['x'] ? 1 : 2;
// type A4 = 2
type A4 = P<'x' | 'y'>

inferоператор

в условном типеextendsВ подоператорах допускается появлениеinferобъявление, которое вводит переменную типа для вывода.

Информация, которую мы получили:

  1. inferоператор разрешен только вextendsв подведомственности;
  2. Он используется для вывода переменных типа.

один пример:

// ReturnType 为内置工具类型,作用:由函数类型 T 的返回值类型构造一个类型。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type func = () => number;
type variable = () => string;
type funcReturnType = ReturnType<func>; // funcReturnType 类型为 number
type varReturnType = ReturnType<variable>; // varReturnType 类型为 string

inferЕго также можно использовать для распаковки, вот пример распаковки типов элементов в массиве:

type Ids = number[];
type Names = string[];
type Unpacked<T> = T extends (infer R)[] ? R : T;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string

inferЕсть еще одна очень важная особенность:

  • inferВыведенные имена одинаковы, и оба находятся вковариантный, результатом вывода будеттип союза;
  • inferВыведенные имена одинаковы, и оба находятся вИнверсия, результатом вывода будетперекрестный тип.

ковариантныйиИнверсияОбъяснение можно найти здесьосновные статьи

Ковариантный пример:

// 例1
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T10 = Foo<{ a: string; b: string }>; // T10类型为 string
type T11 = Foo<{ a: string; b: number }>; // T11类型为 string | number

// 例2
type ElementOf<T> = T extends (infer R)[] ? R : never;

type Tuple = [string, number];
type Union = ElementOf<Tuple>; // Union 类型为 string | number

Пример контравариантности:

type Bar<T> = T extends {
  a: (x: infer U) => void;
  b: (x: infer U) => void;
} ? U : never;

// type T1 = string
type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;

// type T2 = never
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;

Тип шаблона строки

Строки шаблона могут в определенной степени ограничивать текст.ES6аналогично, кроме${}Встроенный — это тип, который не поддерживает вычисления:

type HTTP = `http://${string}`
type HTTPS = `https://${string}`

let a: HTTP = 'http://small-rabbit.top'; // OK
let b: HTTPS = 'https://small-rabbit.top'; // OK

В сочетании с использованием дженериков:

type EventName<T extends string> = `${T}Changed`;
type T0 = EventName<'foo'>;  // 'fooChanged'
type T1 = EventName<'foo' | 'bar' | 'baz'>;  // 'fooChanged' | 'barChanged' | 'bazChanged'

let t: T0 = 'fooChanged'; // OK
let a: T1 = 'fooChanged'; // OK
let b: T1 = 'barChanged'; // OK
let c: T1 = 'bazChanged'; // OK
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;
type T = Concat<'Hello', 'World'>;  // 'HelloWorld'

let t: T = 'HelloWorld'; // OK

Типы союзов в строковых шаблонах расширяются и комбинируются:

type T1 = "top" | "bottom";
type T2 = "left" | "right";
type T3 = `${T1}-${T2}`; // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
 
let t1: T3 = 'top-left'; // OK
let t2: T3 = 'top-right'; // OK
let t3: T3 = 'bottom-left'; // OK
let t4: T3 = 'bottom-right'; // OK

Чтобы упростить изменение типов строковых литералов, были также добавлены некоторые новые псевдонимы служебных типов для изменения регистра букв. Псевдоним нового типаUppercase,Lowercase,CapitalizeиUncapitalize. Первые два преобразуют каждый символ в строке, последние два преобразуют только первый символ в строке.

type Cases<T extends string> = `${Uppercase<T>} ${Lowercase<T>} ${Capitalize<T>} ${Uncapitalize<T>}`;
type T = Cases<'bar'>;  // 'BAR bar Bar bar'

let t: T = 'BAR bar Bar bar'; // OK

Ссылаться на

Начало работы с TypeScript

Руководство пользователя TypeScript 4.0

Ts Masters: 22 примера, подробно объясняющих самые малоизвестные продвинутые инструменты Ts