in оператор для сужения
Оператор
inв TypeScript (и JavaScript) проверяет наличие свойства в объекте:"prop" in obj. TypeScript использует эту проверку как type guard — в веткеtrueкомпилятор сужает тип до вариантов, у которых данное свойство точно существует.
Зачем нужно
in позволяет различать объекты по наличию уникального свойства без discriminated union с явным дискриминатором. Это удобно при работе с внешними данными или когда добавление поля kind в тип нецелесообразно.
Где используется
- Различение типов в union по наличию уникального поля
- Проверка опциональных свойств перед использованием
- Narrowing
unknownперед обращением к полям - Пользовательские type guard функции
Основной контент
Базовое использование
interface Admin {
role: "admin";
permissions: string;
}
interface RegularUser {
role: "user";
name: string;
}
function describe(account: Admin | RegularUser): string {
if ("permissions" in account) {
// account: Admin — у RegularUser нет permissions
return `Admin with ${account.permissions.length} permissions`;
}
// account: RegularUser
return `User: ${account.name}`;
}
in с опциональными полями
interface WithId {
id: string;
}
interface WithUuid {
uuid: string;
}
type Entity = WithId | WithUuid;
function getIdentifier(entity: Entity): string {
if ("id" in entity) {
return entity.id; // entity: WithId
}
return entity.uuid; // entity: WithUuid
}
Narrowing unknown через in
// in требует что левый операнд — string, правый — object
function isError(value: unknown): value is Error {
return (
typeof value === "object" &&
value !== null &&
"message" in value &&
"stack" in value
);
}
function handleError(err: unknown): string {
if (isError(err)) {
return err.message; // err: Error
}
return String(err);
}
Сравнение с typeof и instanceof
type Cat = { meow: => void };
type Dog = { bark: => void };
function makeSound(animal: Cat | Dog): void {
// typeof не поможет — оба "object"
// instanceof требует класс
// in — работает с object literal types
if ("meow" in animal) {
animal.meow; // Cat
} else {
animal.bark; // Dog
}
}
in в пользовательском type guard
interface SuccessResponse<T> {
data: T;
status: "success";
}
interface ErrorResponse {
error: string;
status: "error";
}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
function isSuccess<T>(resp: ApiResponse<T>): resp is SuccessResponse<T> {
return "data" in resp;
}
async function fetchUser: Promise<ApiResponse<{ name: string }>> {
return { data: { name: "Alice" }, status: "success" };
}
const resp = await fetchUser;
if (isSuccess(resp)) {
console.log(resp.data.name); // TypeScript знает: resp.data существует
}
Важное отличие от hasOwnProperty
// in проверяет все свойства (включая прототип)
"toString" in {}; // true (из Object.prototype)
"nonExistent" in {}; // false
// in как type guard работает только с заранее известными свойствами
// для динамических ключей narrowing не происходит
const key = "name";
if (key in obj) {
// obj[key] всё ещё unknown — TypeScript не сужает по динамическому ключу
}
Частые ошибки
- Применять
inк примитивам —"prop" in nullвыбросит TypeError; всегда проверяйте что значение — объект передin. - Ожидать narrowing по динамическому ключу —
inкак type guard работает только с string literal ("prop" in obj), не с переменной. - Путать
inсhasOwnProperty—inпроверяет цепочку прототипов; это ок для type guard, но учитывайте при работе с прототипами. - Использовать
inдляundefined-свойств — свойство сundefinedзначением всё равно «в объекте»;inвозвращаетtrueдля{ x: undefined }.
Связанные темы
- Type Guards -- typeof и instanceof
- Пользовательские Type Guards
- Discriminated Unions
- any, unknown -- различия
- _MOC TypeScript