Avatar ·

Чем отличаются типы String и string в TypeScript?

Всем привет! Я только начал изучать TypeScript и столкнулся с непонятной ситуацией. В документации и в разных примерах я вижу, что для объявления строковых переменных используют два разных типа: `String` и `string`. Например: ```typescript let name1: string = "Alice"; let name2: String = "Bob"; ``` Оба варианта вроде бы работают, и код компилируется без ошибок. Но я заметил, что если я использую `String` (с большой буквы), то TypeScript иногда ведет себя странно. Например, я попробовал написать так: ```typescript let greeting: String = new String("Hello"); let message: string = "World"; // А вот это не работает: // let result: string = greeting + message; // Ошибка? ``` Хотя в JavaScript все бы сложилось. Еще я видел, что некоторые разработчики ругаются, когда кто-то использует `String` вместо `string`. Подскажите, пожалуйста, в чем разница? Почему TypeScript вообще позволяет использовать оба варианта? И какой из них правильный для повседневной разработки? Заранее спасибо за разъяснение! <<>>

Ключевое отличие: примитив vs объект-обёртка

В TypeScript (и JavaScript) есть фундаментальное различие между string (строчная) и String (заглавная). Первое — это тип примитива, второе — тип объекта-обёртки (wrapper object).

Что такое string (строчная)?

Это примитивный тип данных. Когда вы пишете:

let name: string = "Alice";

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

let a: string = "hello";
let b: string = "hello";
console.log(a === b); // true — сравниваются значения

Что такое String (заглавная)?

Это тип, соответствующий встроенному объекту String из JavaScript. Когда вы используете конструктор new String(), вы создаёте объект:

let obj: String = new String("Hello");
console.log(typeof obj); // "object" — это объект, а не примитив!

Объекты String сравниваются по ссылке, а не по значению:

let x: String = new String("hello");
let y: String = new String("hello");
console.log(x === y); // false — разные объекты в памяти

Почему код иногда работает с String?

JavaScript автоматически «оборачивает» примитивные строки в объекты String, когда вы вызываете методы (например, .toUpperCase()). Это называется autoboxing. Поэтому:

let str: string = "hello";
console.log(str.toUpperCase()); // Работает — JS временно создаёт объект-обёртку

Однако обратное не всегда верно. Объект String не всегда автоматически преобразуется в примитив. Например, при сложении:

let greeting: String = new String("Hello");
let message: string = "World";
let result = greeting + message; // Работает — JS вызывает .toString() у объекта
// Но TypeScript может ругаться на несоответствие типов

Практические проблемы с String

  • Сравнение: new String("a") === new String("a") даёт false, что часто приводит к багам.
  • Типобезопасность: Функции, ожидающие string, не примут String без явного преобразования.
  • Производительность: Объекты String создаются в куче и требуют больше памяти, чем примитивы.

Правило большого пальца

Всегда используйте строчную string. TypeScript включает String только для совместимости с JavaScript и для случаев, когда вам действительно нужен объект-обёртка (что бывает крайне редко — например, для работы с String.prototype в метапрограммировании).

Вот безопасный код:

// ✅ Правильно
let firstName: string = "John";
let lastName: string = "Doe";
let fullName: string = `${firstName} ${lastName}`;
let isEqual: boolean = (firstName === "John"); // true

// ❌ Избегайте
let badName: String = new String("John");
let badCheck: boolean = (badName === new String("John")); // false — баг!

Если вы видите String в чужом коде (особенно в интерфейсах или типах), это почти наверняка ошибка или устаревший код. Исправляйте на string.

То же самое касается Number/number, Boolean/boolean и Symbol/symbol — всегда выбирайте строчные варианты.

<<>>

Дополнение: когда String может быть полезен?

Хотя в 99.9% случаев нужно использовать string, есть редкие сценарии, где объект String оправдан:

  • Метапрограммирование: Если вы расширяете прототип String своими методами (хотя это считается плохой практикой).
  • Сериализация: В некоторых библиотеках для работы с JSON или базами данных объекты String могут вести себя иначе при сериализации.
  • TypeScript типы: Иногда вы можете встретить String в типах сторонних библиотек (например, String | null). Это обычно legacy-код, который стоит заменить на string | null.

Однако на практике эти случаи настолько редки, что лучше придерживаться правила: если вы не уверены — используйте string. Это сделает ваш код предсказуемым, производительным и совместимым с большинством TypeScript-проектов.

Если вы встретили String в коде коллеги — вежливо укажите на разницу и предложите исправить. Это убережёт команду от трудноуловимых багов со сравнением объектов.