open atlas
↑ К треку
Система типов TypeScript вглубь TS · 00 · 02

Пространство типов и пространство значений

Каждый идентификатор TypeScript живёт в пространстве значений, пространстве типов или в обоих; `typeof` мостит значение в тип, а `keyof` остаётся внутри типов, и классические ошибки «refers to a type / a value» — это пересечение миров.

TS Junior ◷ 13 min
Уровень
ОсновыJuniorMiddleSenior

Вы пишете function logShape(t: Shape) {} — и редактор доволен. Пишете const s = Shape — и редактор подчёркивает Shape красным: «‘Shape’ only refers to a type, but is being used as a value here». Одно и то же слово, один файл, два совершенно разных вердикта. Причина — самая проясняющая идея во всём TypeScript: есть два параллельных пространства имён — пространство значений, существующее в рантайме, и пространство типов, которое стирается до него — и большинство путаницы новичков на самом деле есть один идентификатор, вызванный не в тот мир.

Как только вы увидите модель двух пространств, любую ошибку «only refers to» можно расшифровать за секунды — и уверенно работать с typeof, keyof и двойной природой классов.

Два параллельных пространства имён

Зачем TypeScript вообще нужны два отдельных пространства имён? Потому что типы стираются — мир рантайма и мир компиляции физически разделены, и имя может жить в одном, в другом или в обоих. Не зная, к какому миру принадлежит имя, ошибки вида «only refers to a type» будут казаться произвольными, а не закономерными.

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

const port = 3000;        // `port` только в пространстве ЗНАЧЕНИЙ
type Port = number;       // `Port` только в пространстве ТИПОВ

const x: Port = port;     // типовая позиция берёт таблицу типов, значимая — таблицу значений — ок
const y = Port;
// Error: 'Port' only refers to a type, but is being used as a value here.

Позиция аннотации типа (после :, в extends, внутри <...>) читается по таблице типов. Позиция выражения (правая часть =, аргумент функции, после return) читается по таблице значений. Port существует только в таблице типов, поэтому его вызов в позиции выражения проваливается — и ошибка прямо говорит, в каком мире он живёт.

Кто где живёт

Ключевая деталь для повседневного кода: объявления различаются тем, какие пространства имён они заполняют.

ОбъявлениеПространство значений?Пространство типов?Доживает до рантайма?
const x = … / let / functionДаНетДа
type T = … / interface IНетДаНет (стирается)
class CДа (конструктор)Да (инстансный тип)Да
enum EДа (объект)Да (тип члена)Да
namespace NДаДаДа

Именно class сбивает с толку чаще всего: его имя означает две разные вещи в зависимости от позиции. В позиции значения это конструкторная функция; в позиции типа — инстансная форма.

class Logger {
  level = "info";
}

const a = new Logger();   // позиция значения: `Logger` — конструктор
//    ^? const a: Logger
function use(l: Logger) {} // позиция типа: `Logger` — инстансный тип
const ctor: typeof Logger = Logger; // ТИП конструктора (см. следующий раздел)

Мосты: typeof (значение→тип) и keyof (тип→тип)

Часто у вас есть значение, и вы хотите говорить о его типе, не выписывая тип руками. Оператор типа typeof — это мост из пространства значений в пространство типов.

const config = { host: "localhost", port: 5432, tls: false };

type Config = typeof config;
//   ^? type Config = { host: string; port: number; tls: boolean }

function connect(c: typeof config) {} // переиспользуем выведенную форму, без дублирования

Заметьте: это типовой typeof, используемый в позиции типа — он не имеет отношения к рантайм-оператору JavaScript typeof x, возвращающему строку "object". Одно ключевое слово, два мира.

keyof — другой мост, и он целиком остаётся внутри пространства типов, отображая объектный тип в объединение его ключей.

type Config = typeof config;
type ConfigKey = keyof Config;
//   ^? type ConfigKey = "host" | "port" | "tls"

Скомпонуйте их — и получите рабочую лошадку для типизированных выборок:

function get<K extends keyof typeof config>(key: K): (typeof config)[K] {
  return config[key];
}
const p = get("port");
//    ^? number
const h = get("host");
//    ^? string
get("nope");
// Error: Argument of type '"nope"' is not assignable to parameter of type
//        'keyof typeof config'.  ("host" | "port" | "tls")
Почему это работает

Почему typeof config даёт конкретный объектный тип, а не просто object? Потому что типовой typeof читает тот статический тип, который проверяющий уже вывел для этой привязки — он ничего не выполняет. Для const, инициализатор которого — объектный литерал, проверяющий вывел { host: string; port: number; tls: boolean }, поэтому typeof config — ровно это. Если бы вы написали let config или аннотировали как Record<string, unknown>, typeof config дал бы этот более широкий тип. Оператор — указатель на результат вывода, а не свежий анализ.

Две классические ошибки, расшифрованные

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

interface User { id: string }

const a: User = { id: "1" };   // ок: User в позиции типа
const b = User;
// Error: 'User' only refers to a type, but is being used as a value here.
//        (вы использовали имя-только-тип там, где нужно значение)

const handler = () => {};
type T = handler;
// Error: 'handler' refers to a value, but is being used as a type here.
//        Did you mean 'typeof handler'?
//        (вы использовали имя-только-значение там, где нужен тип — мостите через typeof)

Логика исправления всегда одна: понять, в каком пространстве живёт имя, и либо перейти в подходящую позицию, либо использовать мост (typeof), чтобы пересечь границу намеренно. И поскольку interface/type существуют только в типах, они исчезают в рантайме — нельзя сделать instanceof User над интерфейсом — тогда как class доживает, и именно поэтому классы — это инструмент, когда нужна проверяемая в рантайме форма. Когда вы встречаете одну из этих ошибок в реальном коде, прочитайте формулировку: она прямо указывает направление пересечения и нужный мост.

Викторина

`const settings = { dark: true, lang: 'en' }`. Нужен тип — объединение `'dark' | 'lang'`. Какое выражение его даёт?

Закончи аналогию

Имя класса как слово с двумя значениями по контексту: в выражении это рантайм-конструктор, а в аннотации — инстансный ___, та форма, что имеет экземпляр.

Вспомните перед уходом
  1. 01
    Какие объявления заполняют пространство значений, типов или оба, и почему это важно в рантайме?
  2. 02
    В чём разница между типовым `typeof` и `keyof` и как они компонуются?
  3. 03
    Расшифруйте две ошибки: 'X only refers to a type, but is being used as a value here' и 'X refers to a value, but is being used as a type here'.
Итог

Разделение пространств значений и типов — линза, делающая остальной трек читаемым: позиция выбирает пространство имён, typeof и keyof — мосты между ними, а ошибки «only refers to» — просто имя в неправильном мире. Вы также увидели, что только-типовые объявления испаряются в рантайме, тогда как классы и enum остаются — прямое применение идеи стирания из предыдущего урока. Теперь, когда в редакторе появится одна из этих ошибок, вы не будете угадывать — прочитаете формулировку, определите, какая таблица оказалась пустой, и сразу потянетесь за правильным исправлением или мостом.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.