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

#typescript

#typescript

Вопрос:

Я расширяю цепочку String prototype с помощью нового метода, но когда я пытаюсь его использовать, он выдает мне ошибку : 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 файлах объявлений *.

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

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

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

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

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

Часть 2 — Реализация

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

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

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

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

Импортируйте JavaScript

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

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

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

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

Комментарии:

1. Я использую версию v2.1.0-dev.20161001. Я пробовал это раньше, но это тоже выдает ошибку, указанную выше declare global . Кроме того, я заметил, что многие ответы от stackoverflow говорят нам о расширении без прототипа, но сгенерированный код его не использует… в чем здесь подвох? Почему это так сложно?

2. кроме того, если вы попытаетесь поместить его в .d.ts файл в 2.5.1, вы получите следующую ошибку: Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.

3. @GregRos Да, в моем файле были другие материалы .d.ts . Я помещаю его в корень types.d.ts , и, похоже, он работает без объявления. Спасибо за разъяснение импорта.

4. Эй, это отличный ответ, мне было интересно, знаете ли вы какой-либо способ изменить глобальный интерфейс в не окружающем контексте?

5. @PatrickRoberts Если объявления типа находятся внутри пакета, только приложение, которое import использует пакет, сможет их увидеть. Кроме этого, на самом деле нет способа сделать это напрямую. Но, возможно, есть альтернативы. Откройте вопрос и объясните, что вы имеете в виду.

Ответ №2:

Если вы хотите увеличить 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 {}
  

Комментарии:

1. Я сделал второй вариант, но по какой-то причине во время выполнения я все равно получаю IsNullOrEmpty не является функцией.

2. Удалось выяснить мою ошибку: Я расширил экземпляр, что бессмысленно, когда вы пытаетесь проверить значение Null ИЛИ Empty, учитывая, что если переменная равна null, вы получите исключение, которое у меня было. Я изменил его на расширение класса, который решил мою проблему. Спасибо за понимание в вашем ответе.

3. Так печально, что такой плохо разработанный язык приобрел такую большую популярность…

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

5. В моих экспериментах установка declare global выдает ошибку компиляции — «Дополнения для глобальной области могут быть непосредственно вложены только во внешние модули или объявления окружающего модуля.ts». Возможно ли по-прежнему улучшать String без необходимости каждый раз что-то импортировать?

Ответ №3:

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

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

 interface String {
    toCamelCase(): string;
}
  

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

 String.prototype.toCamelCase = function(): string { 
    return this.replace(/(?:^w|[A-Z]|-|bw)/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

Ответ №4:

Для меня следующее работало в проекте 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));
  

Комментарии:

1. Единственный ответ, который работает для узла. JS, TypeScript 3.3.1

2. Не могли бы вы любезно объяснить, как использовать файл typing.d.ts?

3. Если ваш файл использует import s, ваш интерфейс неявно станет привязанным к модулю, поэтому declare global потребуется.

Ответ №5:

2022 typescript 4.9

Решение довольно простое, но добраться до этого решения не удалось. Игнорируйте ответ, за который проголосовали больше всего; большая часть этого не нужна и больше не работает.

index.d.ts

Не требуется!

strings.ts

 interface String {
    toProperCase(): string;
    bool(): boolean;
}

String.prototype.toProperCase = function (): string {
    return this.toLowerCase().replace(/bw/g, (c: string) => c.toUpperCase())
}

String.prototype.bool = function (): boolean {
    return this.toLowerCase() == 'true';
};
  

используя его

 import './lib/strings'

var a = 'hello'
var b = a.toProperCase()
  

выводит:

Здравствуйте

Комментарии:

1. Если у нас есть некоторый импорт (скажем, вспомогательная функция для ведения журнала) поверх interface String in strings.ts . TypeScript выдает ошибку Property 'x' does not exist on type 'String' . @toddmo у вас есть какие-либо подсказки, как это решить?

2. @JDSolanki, ваши инструкции интерфейса должны быть первыми в файле для компилятора. Операторы импорта могут опускаться ниже и не обязательно должны быть сверху. Они должны быть только выше того места, где они используются.

3. У меня все работало нормально.