Как расширить прототип String и использовать его далее, в Typescript?

Я расширяю цепочку прототипов String с помощью нового метода, но когда я пытаюсь его использовать, он вызывает ошибку: property 'padZero' does not exist on type 'string'. Может ли кто-нибудь решить это для меня?

Код ниже. Вы также можете увидеть ту же ошибку в Typescript Playground.

interface NumberConstructor {
    padZero(length: number);
}
interface StringConstructor {
    padZero(length: number): string;
}
String.padZero = (length: number) => {
    var s = this;
    while (s.length < length) {
      s = '0' + s;
    }
    return s;
};
Number.padZero = function (length) {
    return String(this).padZero(length);
}

Ответ 1

Этот ответ относится к TypeScript 1.8+. Есть много других ответов на этот вопрос, но все они, похоже, охватывают более старые версии.

Есть две части для продления прототипа в TypeScript.

Часть 1 - Объявить

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

Расширение модулей таким образом может быть выполнено только в специальном объявлении .d.ts files *.

//in a .d.ts file:
declare global {
    interface String {
        padZero(length : number) : string;
    }
}

Типы во внешних модулях имеют имена, которые включают кавычки, такие как "bluebird".

Имя модуля для глобальных типов, таких как Array<T> и String, равно global без кавычек. Однако во многих версиях TypeScript вы можете полностью отказаться от объявления модуля и сразу объявить интерфейс, чтобы иметь что-то вроде:

declare interface String {
        padZero(length : number) : string;
}

Это в некоторых версиях pre-1.8, а также в некоторых версиях post-2.0, таких как самая последняя версия, 2.5.

Обратите внимание, что вы не можете иметь ничего, кроме операторов declare в файле .d.ts, иначе это не сработает.

Эти объявления добавляются в объем окружения вашего пакета, поэтому они будут применяться во всех файлах TypeScript, даже если вы никогда не import или ///<reference файла напрямую. Однако вам все равно нужно импортировать реализацию, которую вы пишете во второй части, и если вы забудете сделать это, вы столкнетесь с ошибками во время выполнения.

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

Часть 2 - Внедрение

Часть 2 фактически реализует элемент и добавляет его к объекту, в котором он должен существовать, как и в JavaScript.

String.prototype.padZero = function (this : string, length: number) {
    var s = this;
    while (s.length < length) {
      s = '0' + s;
    }
    return s;
};

Обратите внимание на несколько вещей:

  • String.prototype вместо String, который является конструктором String, а не его прототипом.
  • Я использую явный function вместо функции стрелки, потому что function будет правильно принимать параметр this, откуда он вызывается. Функция стрелки всегда будет использовать тот же this, что и место, в котором оно было объявлено. Одно время, когда мы не хотим, чтобы это произошло, это расширение прототипа.
  • Явный this, поэтому компилятор знает тип this, который мы ожидаем. Эта часть доступна только в TS 2.0+. Удалите аннотацию this, если вы компилируете для 1.8-. Без аннотации this может быть неявно напечатан any.

Импорт JavaScript

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

Вы импортируете файл следующим образом:

import '/path/to/implementation/file';

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

Ответ 2

Вот рабочий пример, простой модификатор строки Camel Case.

в моем index.d.ts для моего проекта

interface String {
    toCamelCase(): string;
}

в моем корневом каталоге .ts где-то доступном

String.prototype.toCamelCase = function(): string { 
    return this.replace(/(?:^\w|[A-Z]|-|\b\w)/g, 
       (ltr, idx) => idx === 0
              ? ltr.toLowerCase()
              : ltr.toUpperCase()
    ).replace(/\s+|-/g, '');
};

Это все, что мне нужно было сделать, чтобы он работал в typescript ^2.0.10.

Я использую его как str.toCamelCase()

Обновление

Я понял, что у меня тоже есть эта необходимость, и это то, что у меня было

interface String {
    leadingChars(chars: string|number, length: number): string;
}


String.prototype.leadingChars = function (chars: string|number, length: number): string  {
    return (chars.toString().repeat(length) + this).substr(-length);
};

поэтому console.log('1214'.leadingChars('0', 10)); получает меня 0000001214

Ответ 3

Для меня следующие работали в проекте Angular 6, используя TypeScript 2.8.4.

В файле typings.d.ts добавьте:

interface Number {
  padZero(length: number);
}

interface String {
  padZero(length: number);
}

Примечание. Не нужно "объявлять глобальное".

В новом файле с именем string.extensions.ts добавьте следующее:

interface Number {
  padZero(length: number);
}

interface String {
  padZero(length: number);
}

String.prototype.padZero = function (length: number) {
  var s: string = String(this);
  while (s.length < length) {
    s = '0' + s;
  }
  return s;
}

Number.prototype.padZero = function (length: number) {
  return String(this).padZero(length)
}

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

import '../../string.extensions';

Очевидно, что ваш оператор import должен указывать на правильный путь.
Внутри вашего конструктора классов или любого метода:

var num: number = 7;
var str: string = "7";
console.log("padded number: ", num.padZero(5));
console.log("padding string: ", str.padZero(5));

Ответ 4

Если вы хотите увеличить class, а не instance, добавьте конструктор:

declare global {
  interface StringConstructor {
    padZero(s: string, length: number): string;
  }
}

String.padZero = (s: string, length: number) => {
  while (s.length < length) {
    s = '0' + s;
  }
  return s;
};

console.log(String.padZero('hi', 5))

export {}

* Пустой экспорт в нижней части требуется, если вы declare global в .ts и ничего не экспортируете. Это заставляет файл быть модулем. *

Если вы хотите использовать функцию instance (он же prototype)

declare global {
  interface String {
    padZero(length: number): string;
  }
}

String.prototype.padZero = function (length: number) {
  let d = String(this)
  while (d.length < length) {
    d = '0' + d;
  }
  return d;
};

console.log('hi'.padZero(5))

export {}