Умелое использование TypeScript (1)

внешний интерфейс TypeScript
Умелое использование TypeScript (1)

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

перегрузка функций

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

declare function test(a: number): number;
declare function test(a: string): string;

const resS = test('Hello World');  // resS 被推断出类型为 string;
const resN = test(1234);           // resN 被推断出类型为 number;

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

Рассмотрим следующий пример:

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

declare function test(para: User | number, flag?: boolean): number;

на этоtestВ функции нашим намерением может быть передача параметровparaдаUserвремя, не передаватьflag, когда входящийparaдаnumberпри проходеflag. TypeScript этого не знает, когда вы проходитеparaзаUserчас,flagТакже позволяет проходить:

const user = {
  name: 'Jack',
  age: 666
}

// 没有报错,但是与想法违背
const res = test(user, false);

Использование перегрузки функций может помочь нам достичь:

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

declare function test(para: User): number;
declare function test(para: number, flag: boolean): number;

const user = {
  name: 'Jack',
  age: 666
};

// bingo
// Error: 参数不匹配
const res = test(user, false);

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

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

const user = {
  name: 'Jack',
  age: 123
};

class SomeClass {

  /**
   * 注释 1
   */
  public test(para: User): number;
  /**
   * 注释 2
   */
  public test(para: number, flag: boolean): number;
  public test(para: User | number, flag?: boolean): number {
    // 具体实现
    return 11;
  }
}

const someClass = new SomeClass();

// ok
someClass.test(user);
someClass.test(123, false);

// Error
someClass.test(123);
someClass.test(user, false);

тип карты

С тех пор, как в TypeScript 2.1 появились сопоставленные типы, он постоянно улучшался и совершенствовался. В версии 2.1 можно пройтиkeyofполучить объектkeyТип, встроенныйPartial,Readonly,Record,PickТип отображения; добавлено в версии 2.3.ThisType; Добавлено в версии 2.8Exclude,Extract,NonNullable,ReturnType,InstanceType; также в этом релизе добавлены типы условий и улучшенияkeyof; версия 3.1 поддерживает отображение кортежей и массивов. Все это означает, что сопоставленные типы играют ключевую роль в TypeScript.

вThisTypeне фигурирует в официальной документации, в основном используется для ввода литералов объектовthis:

// Compile with --noImplicitThis

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>;  // Type of 'this' in methods is D & M
}

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx;  // Strongly typed this
      this.y += dy;  // Strongly typed this
    }
  }
});

obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

Это из-заThisTypeПоявление Vue 2.5 позволило улучшить поддержку TypeScript.

Хотя существует множество встроенных типов сопоставления, во многих случаях нам необходимо настроить тип сопоставления в соответствии с нашим собственным проектом:

Например, вы можете захотеть извлечь тип функции из типа интерфейса:

type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

interface Part {
  id: number;
  name: string;
  subparts: Part[];
  updatePart(newName: string): void;
}

type T40 = FunctionPropertyNames<Part>;  // "updatePart"
type T42 = FunctionProperties<Part>;     // { updatePart(newName: string): void }

Например, вы можете использовать псевдоним для метода, принадлежащего определенному атрибуту, в других местах для удобства.

Например:SomeClassследующее свойствоvalue = [1, 2, 3], возможно, вы добавили эту функциональность в свой класс в Decorators:SomeClassвызыватьthis.find()когда на самом деле звонюthis.value.find(), но TypeScript не знает об этом на данный момент:

class SomeClass {
  value = [1, 2, 3];

  someMethod() {
    this.value.find(/* ... */);  // ok
    this.find(/* ... */);        // Error:SomeClass 没有 find 方法。
  }
}

С помощью типов отображения иinterface + classСпособ объявления может достичь нашей цели:

type ArrayMethodName = 'filter' | 'forEach' | 'find';

type SelectArrayMethod<T> = {
 [K in ArrayMethodName]: Array<T>[K]
}

interface SomeClass extends SelectArrayMethod<number> {}

class SomeClass {
 value = [1, 2, 3];

 someMethod() {
   this.forEach(/* ... */)        // ok
   this.find(/* ... */)           // ok
   this.filter(/* ... */)         // ok
   this.value                     // ok
   this.someMethod()              // ok
 }
}

const someClass = new SomeClass();
someClass.forEach(/* ... */)        // ok
someClass.find(/* ... */)           // ok
someClass.filter(/* ... */)         // ok
someClass.value                     // ok
someClass.someMethod()              // ok

экспортSomeClassтакже можно использовать класс.

Может быть немного не хватает, в этом кодеinterface SomeClass extends SelectArrayMethod<number> {}Вам нужно вручную добавить конкретный тип дженерика (лучшего способа я пока не придумал).

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

Утверждения типа используются для явного указания TypeScript подробного типа значения, и разумное использование может снизить нашу рабочую нагрузку.

Например, переменная не имеет начального значения, но мы знаем информацию о ее типе (она может быть возвращена из бэкенда), можно ли как-то правильно вывести информацию о типе и нормально работать? В Интернете рекомендуется установить начальное значение, а затем использоватьtypeofПолучить тип (может использоваться в другом месте). Однако я могу быть ленивым и не люблю задавать начальные значения, в этом случае использование утверждений типа может решить такие проблемы:

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

export default class NewRoom extends Vue {
  private user = {} as User;
}

При настройке инициализации, добавлении утверждений нам не нужно добавлять начальные значения, и редактор может нормально давать подсказки по коду. еслиuserЕсть много атрибутов, которые могут решить много ненужной работы, определениеinterfaceТакже можно использовать в другом месте.

тип перечисления

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

// https://github.com/Microsoft/TypeScript/blob/master/src/compiler/types.ts#L3859
export const enum ObjectFlags {
  Class            = 1 << 0,  // Class
  Interface        = 1 << 1,  // Interface
  Reference        = 1 << 2,  // Generic type reference
  Tuple            = 1 << 3,  // Synthesized generic tuple type
  Anonymous        = 1 << 4,  // Anonymous
  Mapped           = 1 << 5,  // Mapped
  Instantiated     = 1 << 6,  // Instantiated anonymous or mapped type
  ObjectLiteral    = 1 << 7,  // Originates in an object literal
  EvolvingArray    = 1 << 8,  // Evolving array type
  ObjectLiteralPatternWithComputedProperties = 1 << 9,  // Object literal pattern with computed properties
  ContainsSpread   = 1 << 10, // Object literal contains spread operation
  ReverseMapped    = 1 << 11, // Object contains a property from a reverse-mapped type
  JsxAttributes    = 1 << 12, // Jsx attributes type
  MarkerType       = 1 << 13, // Marker type used for variance probing
  JSLiteral        = 1 << 14, // Object type declared in JS - disables errors on read/write of nonexisting members
  ClassOrInterface = Class | Interface
}

на машинописном языкеsrc/compiler/typesВ исходном коде определено большое количество константных перечислений, основанных на числовых типах, как показано выше.Это эффективный способ хранения и представления коллекций логических значений..

существует«Глубокое понимание TypeScript»Есть пример использования:

enum AnimalFlags {
  None        = 0,
  HasClaws    = 1 << 0,
  CanFly      = 1 << 1,
  HasClawsOrCanFly = HasClaws | CanFly
}

interface Animal {
  flags: AnimalFlags;
  [key: string]: any;
}

function printAnimalAbilities(animal: Animal) {
  var animalFlags = animal.flags;
  if (animalFlags & AnimalFlags.HasClaws) {
    console.log('animal has claws');
  }
  if (animalFlags & AnimalFlags.CanFly) {
    console.log('animal can fly');
  }
  if (animalFlags == AnimalFlags.None) {
    console.log('nothing');
  }
}

var animal = { flags: AnimalFlags.None };
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws;
printAnimalAbilities(animal); // animal has claws
animal.flags &= ~AnimalFlags.HasClaws;
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly;
printAnimalAbilities(animal); // animal has claws, animal can fly

в коде выше|=используется для добавления флага,&=и~убрать флаг,|Используется для объединения флагов.