Avatar ·

Ошибка TypeScript: Type 'string' is not assignable to type — как исправить?

Всем привет! Я только начал изучать TypeScript и столкнулся с непонятной ошибкой. У меня есть интерфейс для пользователя: ```typescript interface User { name: string; age: number; role: 'admin' | 'user'; } ``` Я пытаюсь создать объект пользователя из данных, полученных из формы. Данные приходят в виде строк (даже возраст — это строка из input). Я написал такой код: ```typescript const formData = { name: 'Иван', age: '25', // строка, хотя нужно число role: 'admin' }; const user: User = { name: formData.name, age: formData.age, // ошибка: Type 'string' is not assignable to type 'number' role: formData.role // ошибка: Type 'string' is not assignable to type '"admin" | "user"' }; ``` TypeScript ругается на age и role. Я понимаю, что age должна быть числом, а role — строго 'admin' или 'user', но как правильно преобразовать типы? Пробовал использовать `as` — работает, но слышал, что это плохая практика. Подскажите, как грамотно решить эту проблему? Возможно, есть какие-то стандартные подходы для обработки данных из форм или API? Заранее спасибо за помощь! <<>>

Почему возникает ошибка и как её правильно исправить

Ошибка Type 'string' is not assignable to type возникает, когда TypeScript видит несоответствие между ожидаемым типом (указанным в интерфейсе) и фактическим типом значения, которое вы пытаетесь присвоить. В вашем случае:

  • age — ожидается number, а приходит string
  • role — ожидается union-тип 'admin' | 'user', а приходит string

Использование as (type assertion) действительно не рекомендуется в таких ситуациях, потому что это подавляет проверку типов и может привести к ошибкам в рантайме. Вместо этого нужно явно преобразовывать данные.

Правильное решение: валидация и преобразование

Лучший подход — создать функцию-валидатор, которая принимает "сырые" данные, проверяет их и возвращает объект с корректными типами. Вот пример:

interface User {
  name: string;
  age: number;
  role: 'admin' | 'user';
}

function createUser(rawData: { name: string; age: string; role: string }): User {
  const age = Number(rawData.age);
  if (isNaN(age) || age < 0) {
    throw new Error('Некорректный возраст');
  }

  if (rawData.role !== 'admin' && rawData.role !== 'user') {
    throw new Error('Роль должна быть admin или user');
  }

  return {
    name: rawData.name,
    age,
    role: rawData.role as 'admin' | 'user' // здесь as безопасен, так как мы уже проверили
  };
}

// Использование
const formData = { name: 'Иван', age: '25', role: 'admin' };
const user = createUser(formData); // ✅ Работает

Альтернативный подход: type guard

Можно использовать пользовательский type guard для проверки role:

function isUserRole(value: string): value is 'admin' | 'user' {
  return value === 'admin' || value === 'user';
}

function parseUser(data: any): User {
  if (typeof data.name !== 'string') throw new Error('name должен быть строкой');
  if (typeof data.age !== 'string') throw new Error('age должен быть строкой');
  if (!isUserRole(data.role)) throw new Error('Некорректная роль');

  const age = Number(data.age);
  if (isNaN(age)) throw new Error('age не является числом');

  return {
    name: data.name,
    age,
    role: data.role
  };
}

Что делать, если данных много?

Для больших проектов рекомендую использовать библиотеки валидации, например Zod:

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  age: z.coerce.number().positive(),
  role: z.enum(['admin', 'user'])
});

const rawData = { name: 'Иван', age: '25', role: 'admin' };
const user = UserSchema.parse(rawData); // ✅ age автоматически преобразуется в число
// user автоматически получает тип User

Важно: Никогда не используйте as для "затыкания" ошибок типов без проверки данных. TypeScript — это инструмент для безопасности, а не просто синтаксический сахар.

<<>>

Простое решение для небольших проектов

Если вы не хотите писать сложные валидаторы, можно использовать промежуточный интерфейс для "сырых" данных и явное приведение типов с проверкой:

interface RawUser {
  name: string;
  age: string;
  role: string;
}

interface User {
  name: string;
  age: number;
  role: 'admin' | 'user';
}

function convertToUser(raw: RawUser): User | null {
  const age = parseInt(raw.age, 10);
  if (isNaN(age)) return null;

  const validRoles = ['admin', 'user'] as const;
  if (!validRoles.includes(raw.role as any)) return null;

  return {
    name: raw.name,
    age,
    role: raw.role as 'admin' | 'user'
  };
}

Дополнительный совет: работа с формами

Для данных из HTML-форм можно использовать HTMLFormElement и FormData. Вот пример с обработкой submit:

document.getElementById('myForm')?.addEventListener('submit', (e) => {
  e.preventDefault();
  const form = e.target as HTMLFormElement;
  const formData = new FormData(form);
  
  const rawUser = {
    name: formData.get('name') as string,
    age: formData.get('age') as string,
    role: formData.get('role') as string
  };
  
  const user = convertToUser(rawUser);
  if (user) {
    console.log('Пользователь создан:', user);
  } else {
    console.error('Некорректные данные формы');
  }
});

Этот подход не такой надёжный, как полноценная валидация, но для простых скриптов или прототипов вполне подходит. Главное — всегда проверять данные перед приведением типов.