← Назад к вопросам

Для чего используется ! в TS?

1.7 Middle🔥 201 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего используется ! в TypeScript

Восклицательный знак (!) в TypeScript — это оператор non-null assertion. Он говорит компилятору: "Я знаю, что это может быть null/undefined, но я уверен, что это не null". Расскажу все подходы и когда это использовать (осторожно!).

1. Non-null assertion operator (!)

function getValue(): string | null {
  return Math.random() > 0.5 ? 'Hello' : null;
}

const value = getValue();
// const value: string | null

// ОШИБКА: Property does not exist on type 'null'
const length = value.length;

// РЕШЕНИЕ: используем !
const length = value!.length; // ОК, но осторожно!

Что происходит:

  • TypeScript видит string | null
  • Мы говорим: "я знаю, что это на самом деле string"
  • Компилятор доверяет нам и не жалуется
  • Если мы ошиблись — runtime ошибка

2. Практические примеры

Пример 1: DOM элементы

const button = document.getElementById('btn');
// const button: HTMLElement | null

// ОШИБКА: button может быть null
button.addEventListener('click', () => {});

// РЕШЕНИЕ 1: non-null assertion
button!.addEventListener('click', () => {});

// РЕШЕНИЕ 2: type guard (лучше)
if (button) {
  button.addEventListener('click', () => {});
}

// РЕШЕНИЕ 3: querySelector с as (осторожно)
const btn = document.querySelector('#btn') as HTMLButtonElement;
btn.addEventListener('click', () => {});

Пример 2: Array operations

const numbers = [1, 2, 3];

const first = numbers.find(n => n > 0);
// const first: number | undefined

// ОШИБКА
const squared = first * first;

// РЕШЕНИЕ 1: non-null assertion
const squared = first! * first!; // Плохой стиль

// РЕШЕНИЕ 2: type guard
if (first !== undefined) {
  const squared = first * first; // ОК
}

// РЕШЕНИЕ 3: nullish coalescing
const squared = (first ?? 0) * (first ?? 0); // Безопаснее

Пример 3: JSON parsing

const json = '{"name": "Alice"}';

const data = JSON.parse(json);
// const data: any

const name = data.name; // ОК (any отключает проверки)

// С типизацией
interface User {
  name: string;
  age?: number; // optional
}

const user = JSON.parse(json) as User;
// Если не уверен, что age есть
const ageDisplay = user.age!; // ОПАСНО!

// Лучше
const ageDisplay = user.age ?? 'Not specified';

3. Когда ! уместен

Хорошие случаи:

// 1. Ты явно проверил null чуть выше
let value: string | null = getValue();

if (value === null) {
  throw new Error('Value is null');
}

// Теперь ты ЗНАЕШЬ, что value не null
const length = value.length; // TypeScript могла бы понять, но не понимает
const length = value!.length; // Помогаем TS

// 2. DOM элемент, который ТОЧНО есть
const html = document.documentElement; // Всегда существует
const btn = document.querySelector('#main-button')!; // Точно существует

// 3. API response с гарантией
type ApiResponse = { data: User | null };

const response = await fetch('/api/user').then(r => r.json());
if (response.data === null) {
  throw new Error('No data');
}
const user = response.data!.name; // Помогаем TS

Плохие случаи:

// НЕ ДЕЛАЙ ТАК!

// 1. Когда не уверен
const user = getUserById(id);
const name = user!.name; // Если user === null, будет ошибка!

// 2. Лениво вместо type guard
const result = maybeNull();
const value = result!; // Плохой стиль

// Правильно
if (result !== null) {
  const value = result; // ОК
}

// 3. Для игнорирования ошибок типов
const value: number = 'string' as any as number; // ПЛОХО

4. Альтернативы (лучше, чем !)

1. Type guard (самый безопасный способ)

function printLength(value: string | null) {
  // Способ 1: if check
  if (value !== null) {
    console.log(value.length);
  }
  
  // Способ 2: type guard функция
  if (isString(value)) {
    console.log(value.length);
  }
}

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

2. Nullish coalescing (??)

const user = getUser(); // User | null

// С !
const name = user!.name; // Ошибка если null

// С nullish coalescing
const name = user?.name ?? 'Unknown';

3. Optional chaining (?.)

const value = getValue(); // string | null

// С !
const length = value!.length; // Ошибка если null

// С optional chaining
const length = value?.length; // Безопасно

4. Default parameter

function process(value: string | null) {
  const val = value ?? 'default'; // Гарантированно string
  console.log(val.length); // ОК
}

5. ! в разных контекстах

Для properties:

interface Config {
  apiUrl?: string;
  timeout?: number;
}

const config = getConfig() as Config;

// ОК: apiUrl может быть undefined
const url = config.apiUrl?.toString();

// С !
const url = config.apiUrl!.toString(); // Может быть ошибка

Для переменных:

let value: string | null;

if (someCondition) {
  value = 'hello';
}

// TypeScript не знает, был ли if
console.log(value!.length); // Возможно undefined

Для функций:

const handler = (() => {
  if (condition) {
    return () => console.log('Click');
  }
})();
// const handler: (() => void) | undefined

// ПЛОХО
handler!(); // Может быть undefined

// ХОРОШО
handler?.();

6. Definite assignment assertion

Похож на !, но для переменных которые еще не инициализированы:

let value: string;

function init() {
  value = 'initialized';
}

init();

// ОШИБКА: value может быть не инициализирована
console.log(value.length);

// РЕШЕНИЕ 1: definite assignment assertion
let value!: string; // Говорим, что будет инициализирована

// РЕШЕНИЕ 2: инициализировать сразу
let value: string = '';

// РЕШЕНИЕ 3: optional
let value?: string;

7. Сравнение всех подходов

const value = getValue(); // string | null

// 1. Non-null assertion (опасно)
const length1 = value!.length;

// 2. Optional chaining (безопасно)
const length2 = value?.length; // number | undefined

// 3. Nullish coalescing (безопасно)
const length3 = value?.length ?? 0; // number

// 4. Type guard (очень безопасно)
if (value !== null) {
  const length4 = value.length; // number
}

// 5. Early return (очень безопасно)
function getLength(value: string | null): number {
  if (value === null) return 0;
  return value.length;
}
const length5 = getLength(value);

8. Когда ! неизбежен

Истинно редкие случаи:

// 1. Callback, который гарантирован во время выполнения
type Handler = () => void | null;

const handlers: Handler[] = [];

function execute() {
  const handler = handlers[0];
  // Мы ЗНАЕМ, что handler есть, потому что добавили его
  handler!(); // OK в этом контексте
}

// 2. Complex business logic где ты на 100% уверен
function complex(data: any) {
  if (data.user && data.user.profile) {
    return data.user.profile.name; // Без !, TS требует проверок
  }
  return null;
}

// Правильнее
function complex(data: any): string | null {
  return data?.user?.profile?.name ?? null;
}

9. Практический совет

Используй этот порядок выбора:

1. Optional chaining (?) - первый выбор
   value?.property

2. Nullish coalescing (??)
   value?.property ?? default

3. Type guard + if
   if (value !== null) { ... }

4. as (type assertion)
   value as string

5. ! (non-null assertion)
   Только если действительно необходимо

Ключевой вывод

! (non-null assertion) используется чтобы:

  1. Сказать компилятору: "это не null, я знаю лучше"
  2. Избежать ошибок типов
  3. Помочь TypeScript понять контекст

НО:

  • Это опасно (runtime ошибки если ошибся)
  • Скрывает потенциальные баги
  • Лучше использовать optional chaining (?.) и nullish coalescing (??)
  • Используй type guard (if checks) для безопасности

Правило: Если пишешь !, подумай: может ли тут быть null? Если да — используй ? или ?? вместо !

Для чего используется ! в TS? | PrepBro