Все статьи

TypeScript для Full Stack разработчика: типизация React, API и Backend

Полное руководство по TypeScript: типы, интерфейсы, Generics, Utility Types, Zod, React Hooks, типобезопасные API и интеграция с FastAPI

31 минут чтения

Представим, что вы разрабатываете SaaS-платформу для управления проектами. Frontend написан на React, Backend работает на FastAPI, а PostgreSQL хранит пользователей, организации и задачи. Всё работает стабильно. Но однажды Backend-разработчик меняет формат ответа API. Раньше Endpoint возвращал: { "id": 42, "name": "Project Alpha", "owner_id": 7 } После обновления: { "id": 42, "title": "Project Alpha", "owner": {

"id": 7, "name": "Alex" } } Backend успешно проходит тесты. Frontend собирается без ошибок. Но после деплоя пользователи видят пустые названия проектов, а в консоли появляется: Cannot read properties of undefined Причина проста: React продолжает обращаться к project.name, хотя Backend теперь возвращает project.title. В JavaScript такая ошибка может оставаться незамеченной до выполнения соответствующего участка кода.

TypeScript помогает обнаруживать многие подобные проблемы раньше. Однако возникает важный вопрос. Если TypeScript проверяет типы, почему даже TypeScript-приложения иногда падают после изменения API? Потому что TypeScript проверяет код во время разработки, но не гарантирует, что данные, полученные из сети, действительно соответствуют объявленным типам.

Для надёжного Full Stack приложения необходимы и статическая типизация, и проверка данных во время выполнения. В этой статье разберём TypeScript для Full Stack разработчика: от базовых типов до Generics, типизации React, интеграции с FastAPI, генерации типов из OpenAPI и Runtime Validation через Zod.

Главная цель — научиться создавать типобезопасное Full Stack приложение, в котором Frontend и Backend согласованно работают с данными.


1. Что такое TypeScript простыми словами

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

function calculateTotal(price, quantity) { return price * quantity; } Функция принимает любые значения. Например: calculateTotal("100", 2); JavaScript выполнит неявное преобразование типов. Но такое поведение не всегда соответствует ожиданиям разработчика. В TypeScript можно описать контракт: function calculateTotal( price: number, quantity: number ): number { return price * quantity; }

Теперь попытка передать строку вместо числа приведёт к ошибке проверки типов. calculateTotal("100", 2); TypeScript сообщит, что аргумент типа string нельзя передать туда, где ожидается number.

2. Чем TypeScript отличается от JavaScript

Главное отличие — система типов и дополнительные инструменты анализа программы. TypeScript vs JavaScript для веб-разработки — это не сравнение двух полностью независимых платформ. TypeScript использует экосистему JavaScript. Код TypeScript преобразуется в JavaScript, который затем выполняется в браузере или Node.js. Важно понимать: TypeScript не делает программу автоматически безопасной.

Он помогает проверять согласованность типов в пределах доступной компилятору информации. Если эта информация неправильная, TypeScript может не обнаружить проблему.

3. Почему TypeScript особенно полезен Full Stack разработчику

Full Stack разработчик постоянно работает с границами между системами. Например: React | v REST API | v FastAPI | v PostgreSQL Каждый компонент имеет собственное представление данных. Frontend ожидает объект пользователя. Backend формирует JSON. PostgreSQL хранит строки таблиц. Ошибки часто возникают именно на границах. Преимущества TypeScript для Full Stack разработки особенно заметны при работе с:

  • API-контрактами;
  • React-компонентами;
  • формами;
  • сложными структурами данных;
  • асинхронными запросами;
  • пользовательскими ролями;
  • состоянием приложения;
  • командной разработкой.

TypeScript позволяет явно описывать ожидания и получать предупреждения при их нарушении.

4. Как создать React-проект с TypeScript

Для нового React-приложения удобно использовать Vite. React TypeScript Vite настройка начинается с создания проекта. npm create vite@latest frontend -- --template react-ts Затем: cd frontend npm install npm run dev Проект будет содержать файлы с расширениями: src/ ├── App.tsx ├── main.tsx ├── vite-env.d.ts └── ... Расширение .ts используется для TypeScript-файлов без JSX. Расширение .tsx — для файлов, содержащих JSX.

Это стандартный подход к разработке React с TypeScript.

5. Как настроить строгую типизацию TypeScript

Одна из важнейших настроек — strict. TypeScript strict mode включает набор более строгих проверок. Пример фрагмента tsconfig.json: { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noFallthroughCasesInSwitch": true } } Эти настройки помогают находить дополнительные категории ошибок.

Например, при noUncheckedIndexedAccess обращение к элементу массива по индексу учитывает возможность отсутствия элемента. const names = ["Alex", "Maria"]; const name = names[10]; Тип name может включать undefined. Это заставляет разработчика явно обрабатывать ситуацию, когда элемент отсутствует.

6. Базовые типы TypeScript

Начнём с простых значений. Базовые типы TypeScript включают: const username: string = "Alex"; const age: number = 28; const isActive: boolean = true; const tags: string[] = [ "react", "typescript", "fastapi" ]; TypeScript также поддерживает вывод типов. Например: const username = "Alex"; Компилятор самостоятельно определит тип значения. Поэтому не нужно указывать тип каждой переменной вручную.

Хорошая практика — использовать вывод типов там, где он очевиден, и явные типы там, где они помогают описать контракт.

7. Что такое Type Inference

Type Inference — автоматический вывод типов. Type Inference в TypeScript позволяет компилятору определять типы на основе значений и контекста. Например: const price = 1200; const discountedPrice = price * 0.9; TypeScript понимает, что price и discountedPrice имеют числовой тип. Но в некоторых ситуациях явная аннотация полезнее. Например: function getProjectName( project: Project ): string { return project.title; }

Здесь сигнатура функции выступает документацией и контрактом.

8. Как типизировать объекты

Представим объект пользователя: const user = { id: 42, email: "alex@example.com", isActive: true }; Можно описать его структуру отдельно. Типизация объектов TypeScript помогает повторно использовать контракты. type User = { id: number; email: string; isActive: boolean; }; Теперь: const user: User = { id: 42, email: "alex@example.com", isActive: true };

Если забыть обязательное поле или передать неправильный тип, компилятор сообщит об ошибке.

9. Type и Interface: что выбрать

TypeScript поддерживает два распространённых способа описания объектных структур. Type vs Interface TypeScript — частый вопрос начинающих разработчиков. Пример Interface: interface User { id: number; email: string; } Пример Type: type User = { id: number; email: string; }; Для многих объектных контрактов они взаимозаменяемы. Но между ними существуют различия. Interface поддерживает Declaration Merging.

Type Alias удобен для Union Types, кортежей и других составных типов. Например: type UserId = string | number; На практике важнее выбрать понятный стиль и использовать его последовательно.

10. Как работают Optional Properties

Не все поля объекта обязательны. Например, пользователь может не указать фотографию профиля. Optional Properties TypeScript позволяют обозначить необязательное поле. interface User { id: number; email: string; avatarUrl?: string; } Теперь допустим объект: const user: User = { id: 42, email: "alex@example.com" }; Но при чтении avatarUrl необходимо учитывать возможность undefined.

const avatar = user.avatarUrl ?? "/default.png"; Оператор ?? использует значение справа, если слева находится null или undefined.

11. Что такое Union Types

Union Type позволяет значению иметь один из нескольких типов. Union Types TypeScript особенно полезны для моделирования ограниченных состояний. Например: type ProjectStatus = | "draft" | "active" | "archived"; Теперь: const status: ProjectStatus = "active"; Но: const status: ProjectStatus = "unknown"; будет ошибкой. Такой подход лучше, чем использовать произвольный string, если набор значений известен заранее.

12. Как использовать Literal Types

Literal Types позволяют описывать конкретные значения. Literal Types в TypeScript помогают создавать строгие контракты. Например: type UserRole = | "owner" | "admin" | "member" | "viewer"; Эти значения можно использовать в React, API-моделях и бизнес-логике. Но важно помнить: тип на Frontend не гарантирует, что пользователь действительно имеет соответствующую роль. Права доступа всегда должны проверяться на Backend.

13. Что такое Discriminated Union

Представим, что API-запрос может находиться в нескольких состояниях. Плохой вариант: type RequestState = { loading?: boolean; data?: Project[]; error?: string; }; Такой тип допускает противоречивые состояния. Например, одновременно loading: true и заполненную ошибку. Discriminated Union TypeScript позволяет описать допустимые состояния явно. type RequestState = | { status: "idle"; } | { status: "loading"; } | {

status: "success"; data: Project[]; } | { status: "error"; message: string; }; Теперь TypeScript может сужать тип на основе status. function renderState(state: RequestState) { if (state.status === "success") { return state.data.length; } return 0; } В ветке success поле data гарантированно существует согласно объявленному типу.

14. Как работает Type Narrowing

Type Narrowing — сужение типа после проверки условия. Type Narrowing в TypeScript позволяет безопасно работать с Union Types. Например: function formatId(id: string | number) { if (typeof id === "number") { return id.toFixed(0); } return id.toUpperCase(); } После проверки typeof компилятор знает, какой тип находится в каждой ветке. Это уменьшает необходимость ручных приведений типов.

15. Что такое Type Guards

Type Guard — проверка, помогающая TypeScript уточнить тип значения. Type Guards TypeScript могут использоваться для обработки данных неизвестной структуры. Например: function isString(value: unknown): value is string { return typeof value === "string"; } Теперь: const value: unknown = "Hello"; if (isString(value)) { console.log(value.toUpperCase()); }

Но Type Guard корректен только тогда, когда его реализация действительно проверяет заявленный контракт. Неправильно написанный Type Guard может создать ложное ощущение безопасности.

16. Почему unknown лучше any

В TypeScript существует тип any. Он фактически отключает значительную часть проверки типов для соответствующего значения. Например: function processData(data: any) { return data.user.profile.name; } TypeScript не предупредит о возможном отсутствии этих полей. Any vs Unknown TypeScript — важное различие. Тип unknown требует проверки перед использованием. function processData(data: unknown) {

if (typeof data === "string") { return data.toUpperCase(); } return null; } Для внешних JSON-ответов, содержимого файлов и других недоверенных данных unknown часто является более безопасной отправной точкой.

17. Что такое Generics

Generics позволяют создавать функции и типы, работающие с разными структурами данных без потери информации о типах. Generics TypeScript для начинающих удобно объяснить на простой функции. function identity<T>(value: T): T { return value; } Примеры: const numberValue = identity(42); const stringValue = identity("Hello"); TypeScript сохраняет информацию о типе переданного значения. T — параметр типа.

Он не означает конкретный тип заранее.

18. Как использовать Generics для API

Представим, что API возвращает данные в общей оболочке. { "success": true, "data": { "id": 42, "title": "Project Alpha" } } Можно создать универсальный тип. Generic API Response TypeScript: type ApiResponse<T> = { success: boolean; data: T; }; Теперь: type Project = { id: number; title: string; }; type ProjectResponse = ApiResponse<Project>; Для списка: type ProjectsResponse = ApiResponse<Project[]>;

Это уменьшает дублирование типов. Однако TypeScript не проверит реальный JSON автоматически. Для этого понадобится Runtime Validation.

19. Как ограничивать Generics через extends

Иногда Generic должен поддерживать определённые свойства. Generic Constraints TypeScript позволяют установить ограничения. Например: function getEntityId<T extends { id: string }>( entity: T ): string { return entity.id; } Теперь функция принимает объекты с полем id: string. getEntityId({ id: "project-123", title: "Project Alpha" }); Но объект без id не соответствует контракту.

20. Что такое keyof

Оператор keyof позволяет получить объединение ключей типа. Keyof TypeScript полезен при создании универсальных функций. Например: type User = { id: number; email: string; name: string; }; type UserKey = keyof User; UserKey соответствует: "id" | "email" | "name" Это можно использовать для безопасного доступа к свойствам объекта.

21. Как создать типобезопасную функцию getProperty

Объединим keyof и Generics. function getProperty< T extends object, K extends keyof T >( object: T, key: K ): T[K] { return object[key]; } Пример: const user = { id: 42, email: "alex@example.com" }; const email = getProperty(user, "email"); TypeScript определит, что email имеет тип string. А попытка обратиться к несуществующему полю вызовет ошибку.

Типобезопасный доступ к свойствам TypeScript особенно полезен при создании универсальных компонентов и вспомогательных функций.

22. Что такое Utility Types

TypeScript содержит встроенные инструменты преобразования типов. Они называются Utility Types. Utility Types TypeScript позволяют создавать новые типы на основе существующих. Наиболее распространённые:

  • Partial<T>;
  • Required<T>;
  • Pick<T, K>;
  • Omit<T, K>;
  • Record<K, T>;
  • Readonly<T>;
  • ReturnType<T>;
  • Awaited<T>.

Рассмотрим их на практических примерах.

23. Как использовать Partial

Представим, что пользователь может изменить только часть полей профиля. Partial TypeScript делает свойства объекта необязательными. type User = { name: string; email: string; avatarUrl: string; }; type UpdateUser = Partial<User>; Теперь допустимо: const update: UpdateUser = { name: "Alex" }; Но есть нюанс. Partial<T> не делает глубокую рекурсивную типизацию вложенных объектов.

Он изменяет необязательность только свойств верхнего уровня.

24. Как использовать Pick и Omit

Представим внутреннюю модель пользователя: type User = { id: string; email: string; passwordHash: string; createdAt: string; }; Frontend не должен получать passwordHash. Можно описать публичную модель. Pick и Omit TypeScript позволяют выбирать или исключать свойства. type PublicUser = Omit< User, "passwordHash" >; Или: type UserPreview = Pick< User, "id" | "email" >; Но это только типы.

Они не удаляют поля из реального объекта во время выполнения. Backend должен самостоятельно формировать безопасный ответ API.

25. Как использовать Record

Record позволяет описывать объект с заданным набором ключей. Record TypeScript полезен для словарей и таблиц соответствия. Например: type UserRole = | "owner" | "admin" | "member"; const roleLabels: Record<UserRole, string> = { owner: "Владелец", admin: "Администратор", member: "Участник" }; Если добавить новую роль, TypeScript поможет обнаружить отсутствие соответствующего значения в объекте.

Это удобно для интерфейсов с несколькими состояниями.

26. Как типизировать React Props

В React компоненты получают данные через Props. Типизация React Props TypeScript позволяет явно описать входные параметры компонента. type ProjectCardProps = { id: number; title: string; description?: string; }; export function ProjectCard({ id, title, description }: ProjectCardProps) { return ( <article> <h2>{title}</h2> <p>{description ?? "Нет описания"}</p> <small>Project ID: {id}</small> </article> ); }

Теперь при использовании компонента TypeScript проверяет переданные Props. <ProjectCard id={42} title="Project Alpha" /> Если передать id как строку, компилятор сообщит об ошибке.

27. Как типизировать children

Некоторые React-компоненты принимают вложенное содержимое. Например: <Card> <h2>Dashboard</h2> </Card> React Children TypeScript обычно описывают через ReactNode.

import type { ReactNode } from "react";

type CardProps = { children: ReactNode; }; export function Card({ children }: CardProps) { return <div className="card">{children}</div>; } ReactNode подходит для большинства сценариев отображения вложенного содержимого. Но если компонент требует строго определённый контракт, типы следует выбирать исходя из его реального API.

28. Как типизировать useState

React Hook useState поддерживает TypeScript. Типизация useState React TypeScript часто выводится автоматически. Например: const [count, setCount] = useState(0); TypeScript понимает, что count — число. Но если начальное значение равно null, лучше явно указать тип. type User = { id: string; email: string; }; const [user, setUser] = useState<User | null>(null); Теперь состояние может содержать пользователя или null.

При обращении к полям необходимо учитывать оба варианта.

29. Как типизировать useReducer

Для сложного состояния удобно использовать useReducer. Типизация useReducer TypeScript особенно хорошо сочетается с Discriminated Union. type State = { count: number; }; type Action = | { type: "increment" } | { type: "decrement" } | { type: "reset"; value: number }; function reducer( state: State, action: Action ): State { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement":

return { count: state.count - 1 }; case "reset": return { count: action.value }; } } Теперь TypeScript проверяет допустимые действия и соответствующие Payload.

30. Как типизировать обработчики событий React

React предоставляет типы для событий. React Event Types TypeScript помогают корректно описывать обработчики форм и элементов. Например:

import type { ChangeEvent } from "react";

function handleChange( event: ChangeEvent<HTMLInputElement> ) { console.log(event.target.value); } Для отправки формы:

import type { FormEvent } from "react";

function handleSubmit( event: FormEvent<HTMLFormElement> ) { event.preventDefault(); } Такие типы помогают избежать путаницы между событиями разных HTML-элементов.

31. Как типизировать useRef

useRef используется для хранения ссылки на DOM-элемент или изменяемое значение. Типизация useRef TypeScript зависит от назначения. Пример DOM-ссылки:

import { useRef } from "react";

export function SearchInput() { const inputRef = useRef<HTMLInputElement>(null); const focusInput = () => { inputRef.current?.focus(); }; return ( <> <input ref={inputRef} /> <button onClick={focusInput}> Фокус </button> </> ); } Оператор ?. учитывает, что ссылка может быть null.

32. Как типизировать React Context

Context позволяет передавать данные через дерево компонентов. React Context TypeScript удобно использовать для темы, пользовательской сессии и других общих данных. Пример:

import {

createContext, useContext } from "react"; type AuthContextValue = { userId: string | null; logout: () => Promise<void>; }; const AuthContext = createContext<AuthContextValue | null>(null); export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error( "useAuth must be used within AuthProvider" ); } return context; }

Такой подход позволяет избежать постоянных проверок null в компонентах, использующих корректно настроенный Provider.

33. Как типизировать API-запросы

Представим Endpoint: GET /api/projects/42 Он возвращает: { "id": 42, "title": "Project Alpha", "status": "active" } Можно создать тип: type Project = { id: number; title: string; status: "draft" | "active" | "archived"; }; Затем написать функцию: async function getProject( id: number ): Promise<Project> { const response = await fetch( `/api/projects/${id}` ); if (!response.ok) { throw new Error("Request failed"); }

return response.json(); } Но здесь скрыта проблема. Типизация Fetch API TypeScript не проверяет фактическую структуру JSON. Аннотация Promise<Project> говорит компилятору, что функция возвращает Project. Однако реальный сервер может вернуть другой объект.

34. Почему TypeScript не проверяет JSON

TypeScript выполняет статический анализ. Но HTTP-ответ поступает во время выполнения приложения. Например: { "id": "wrong-type", "title": null, "status": "unexpected" } Такой ответ не соответствует объявленному Project. Но простой вызов response.json() не выполняет проверку схемы. Runtime Validation TypeScript API решает именно эту проблему. Нужно проверить реальные данные после получения ответа.

Для этого удобно использовать Zod.

35. Что такое Zod

Zod — библиотека описания и проверки схем данных для TypeScript. Она позволяет определить структуру объекта и проверить неизвестное значение во время выполнения. Zod TypeScript валидация данных особенно полезна для:

  • ответов API;
  • форм;
  • переменных окружения;
  • данных из localStorage;
  • внешних интеграций;
  • JSON-файлов.

Установим библиотеку: npm install zod Создадим схему проекта:

import { z } from "zod";

const ProjectSchema = z.object({ id: z.number(), title: z.string(), status: z.enum([ "draft", "active", "archived" ]) }); Теперь можно проверять реальные данные.

36. Как использовать Zod parse и safeParse

Zod предоставляет два распространённых способа проверки. Zod parse vs safeParse отличаются поведением при ошибке. parse возвращает данные или выбрасывает исключение. const project = ProjectSchema.parse(data); safeParse возвращает объект результата. const result = ProjectSchema.safeParse(data); if (result.success) { console.log(result.data.title); } else { console.error(result.error.issues); }

Для обработки API-ответов safeParse часто удобен, если приложение должно контролируемо реагировать на ошибку контракта.

37. Как выводить TypeScript тип из Zod Schema

Одна из сильных сторон Zod — возможность получать TypeScript-тип из схемы. Zod infer TypeScript уменьшает дублирование.

import { z } from "zod";

const ProjectSchema = z.object({ id: z.number(), title: z.string(), status: z.enum([ "draft", "active", "archived" ]) }); type Project = z.infer<typeof ProjectSchema>; Теперь тип Project соответствует результату проверки схемы. Но если Zod-схема использует преобразования, входной и выходной типы могут различаться. Для таких случаев существуют z.input и z.output.

38. Как создать безопасный API Client

Объединим Fetch и Zod. Типобезопасный API Client TypeScript должен проверять HTTP-статус и структуру ответа.

import { z } from "zod";

async function fetchAndValidate<T>( url: string, schema: z.ZodType<T> ): Promise<T> { const response = await fetch(url, { credentials: "include" }); if (!response.ok) { throw new Error( `HTTP error: ${response.status}` ); } const json: unknown = await response.json(); return schema.parse(json); } Использование: const project = await fetchAndValidate( "/api/projects/42", ProjectSchema );

Теперь приложение не просто предполагает, что ответ соответствует контракту. Оно действительно проверяет полученные данные. Для более сложных схем с преобразованиями Generic-сигнатуру можно строить на z.output конкретной схемы.

39. Как типизировать ошибки API

API может возвращать разные ошибки. Например: { "error": { "code": "PROJECT_NOT_FOUND", "message": "Project not found" } } Типизация ошибок REST API TypeScript позволяет создать единый формат обработки. type ApiError = { error: { code: string; message: string; }; }; Но этот тип не гарантирует, что сервер действительно вернул такую структуру. Для внешних данных лучше использовать Runtime Validation.

Также необходимо учитывать ошибки сети, таймауты и некорректный JSON. Не каждый неуспешный HTTP-ответ содержит ожидаемый JSON-объект.

40. Как типизировать React Query

TanStack Query помогает управлять серверным состоянием. TanStack Query TypeScript хорошо работает с типизированными функциями API. Например:

import { useQuery } from "@tanstack/react-query";

function useProject(id: number) { return useQuery({ queryKey: ["project", id], queryFn: () => fetchAndValidate( `/api/projects/${id}`, ProjectSchema ) }); } TypeScript может вывести тип data из возвращаемого значения функции. Это удобнее, чем вручную повторять типы в каждом компоненте. Но Runtime Validation по-прежнему остаётся важной частью границы API.

41. Как типизировать формы React

Формы часто содержат множество полей и правил проверки. Например, форма создания проекта: Название проекта Описание Статус Типизация форм React TypeScript позволяет описать структуру данных. type CreateProjectForm = { title: string; description: string; status: "draft" | "active"; }; Но для реальной формы необходимо проверять:

  • обязательные поля;
  • длину строк;
  • допустимые значения;
  • форматы;
  • бизнес-ограничения.

Статических типов недостаточно.

42. Как использовать React Hook Form и Zod

Для форм удобно сочетать React Hook Form и Zod. React Hook Form Zod TypeScript позволяет использовать одну схему для проверки формы и вывода типов. Установка: npm install react-hook-form zod @hookform/resolvers Пример схемы:

import { z } from "zod";

const CreateProjectSchema = z.object({ title: z.string().min(3).max(100), description: z.string().max(1000), status: z.enum(["draft", "active"]) }); type CreateProjectForm = z.infer<typeof CreateProjectSchema>; Пример компонента:

import { useForm } from "react-hook-form";

import { zodResolver } from "@hookform/resolvers/zod";

export function CreateProjectFormView() { const { register, handleSubmit, formState: { errors } } = useForm<CreateProjectForm>({ resolver: zodResolver(CreateProjectSchema), defaultValues: { title: "", description: "", status: "draft" } }); const onSubmit = (data: CreateProjectForm) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("title")} /> {errors.title && (

<p>{errors.title.message}</p> )} <textarea {...register("description")} /> <select {...register("status")}> <option value="draft">Черновик</option> <option value="active">Активный</option> </select> <button type="submit"> Создать проект </button> </form> ); } Важно: проверка формы на Frontend не заменяет Backend-валидацию. Сервер обязан проверять входящие данные независимо от React.

43. Как типизировать Backend на Node.js

TypeScript можно использовать не только во Frontend. Если Backend написан на Node.js, типы могут описывать сервисы, контроллеры и бизнес-логику. TypeScript Backend Node.js позволяет использовать единую систему типов на обеих сторонах приложения. Например: type CreateProjectInput = { title: string; ownerId: string; }; async function createProject( input: CreateProjectInput ) { // Сохранение проекта в базе данных. }

Но типы TypeScript не выполняют Runtime Validation автоматически. Даже в полностью TypeScript-стеке необходимо проверять HTTP-запросы.

44. Как типизировать API между React и FastAPI

Если Backend написан на Python, напрямую импортировать TypeScript-типы из FastAPI нельзя. Но FastAPI умеет генерировать OpenAPI Schema. Это позволяет создать типы для Frontend. Типизация API React FastAPI может строиться на следующей схеме: Pydantic Models | v FastAPI | v OpenAPI Schema | v TypeScript Types | v React Frontend Такой подход уменьшает необходимость вручную синхронизировать модели.

45. Как Pydantic создаёт API-контракты

FastAPI использует Pydantic для описания и проверки данных. Pydantic FastAPI OpenAPI TypeScript позволяет сделать Backend источником контракта API. Например: from typing import Literal from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ProjectResponse(BaseModel): id: int title: str status: Literal[ "draft", "active", "archived" ] @app.get( "/api/projects/{project_id}",

response_model=ProjectResponse ) async def get_project(project_id: int): return ProjectResponse( id=project_id, title="Project Alpha", status="active" ) FastAPI формирует OpenAPI-описание на основе маршрута и модели ответа. Обычно схема доступна по адресу:

http://localhost:8000/openapi.json

46. Как генерировать TypeScript типы из OpenAPI

Для генерации типов можно использовать openapi-typescript. OpenAPI TypeScript генерация типов позволяет получать TypeScript-контракты из схемы FastAPI. Установка: npm install -D openapi-typescript Генерация: npx openapi-typescript \

http://localhost:8000/openapi.json \

-o src/api/schema.d.ts После этого в проекте появляется файл с типами. Пример использования:

import type {

components } from "./api/schema"; type Project = components["schemas"]["ProjectResponse"]; Теперь Frontend использует тип, сформированный на основе Backend-контракта. Важно: генерация типов не проверяет реальные HTTP-ответы во время выполнения.

47. Как создать типизированный OpenAPI Client

Помимо генерации типов можно использовать библиотеки, которые помогают типизировать вызовы API. Например, openapi-fetch. OpenAPI Fetch TypeScript FastAPI позволяет связывать методы и пути API с OpenAPI-схемой. Установка: npm install openapi-fetch Пример:

import createClient from "openapi-fetch";

import type {

paths } from "./api/schema"; const client = createClient<paths>({ baseUrl: "http://localhost:8000", credentials: "include" }); const { data, error } = await client.GET( "/api/projects/{project_id}", { params: { path: { project_id: 42 } } } ); TypeScript проверяет соответствие пути и параметров OpenAPI-контракту. Но сетевые ошибки, невалидные ответы и нарушения контракта сервером всё равно требуют обработки.

48. Почему генерация типов лучше ручного дублирования

Представим, что Backend-разработчик переименовал поле name в title. Если Frontend использует вручную написанные типы, их необходимо обновить отдельно. Если типы генерируются из OpenAPI, изменение может обнаружиться при следующей генерации и проверке TypeScript. Синхронизация типов Frontend и Backend помогает уменьшить количество ошибок интеграции. Например: Backend Change | v OpenAPI Updated | v TypeScript Generated

| v tsc --noEmit | v Compile Error Это позволяет обнаруживать часть несовместимых изменений до деплоя. Но для полноценной защиты полезно дополнять типизацию контрактными и интеграционными тестами.

49. Как автоматизировать генерацию типов

Можно добавить команду в package.json. Автоматическая генерация TypeScript типов из OpenAPI может быть частью локальной разработки и CI/CD. { "scripts": { "dev": "vite", "typecheck": "tsc --noEmit", "generate:api": "openapi-typescript http://localhost:8000/openapi.json -o src/api/schema.d.ts" } } Теперь: npm run generate:api npm run typecheck Для CI/CD лучше использовать воспроизводимый источник OpenAPI-схемы.

Например, экспортировать её из Backend в отдельный файл и использовать фиксированную версию контракта. Это уменьшает зависимость генерации от доступности работающего сервера.

50. Как использовать TypeScript в монорепозитории

Если Frontend и Backend написаны на TypeScript, можно использовать общий пакет типов. TypeScript Monorepo Shared Types позволяет организовать структуру: project/ ├── apps/ │ ├── frontend/ │ └── backend/ │ ├── packages/ │ └── shared/ │ └── src/ │ └── types.ts │ └── package.json Общий пакет может содержать: export type ProjectStatus = | "draft" | "active" | "archived";

Но если Backend написан на Python, прямое совместное использование TypeScript-файлов не решает проблему синхронизации. В таком случае OpenAPI часто оказывается более подходящим контрактным слоем.

51. Что такое Contract-First API Development

Contract-First — подход, при котором API-контракт проектируется до реализации. Contract First API TypeScript помогает согласовать работу команд. Например:

  1. Команда определяет OpenAPI Schema.
  2. Frontend генерирует TypeScript-типы.
  3. Backend реализует Endpoint.
  4. Контрактные тесты проверяют соответствие.
  5. CI/CD контролирует изменения.

Преимущество — явные договорённости о структуре API. Но этот подход требует дисциплины обновления контрактов.

52. Как избежать Breaking Changes в API

Breaking Change — изменение, нарушающее совместимость с существующими клиентами. Например: Было: GET /api/projects/{id} Response: { "name": "Project Alpha" } Стало: { "title": "Project Alpha" } Если Frontend ожидает name, изменение может сломать интерфейс. Breaking Changes REST API TypeScript можно обнаруживать с помощью сравнения OpenAPI-схем и проверки клиентского кода.

Для совместимых изменений часто используют постепенную миграцию. Например, временно возвращают оба поля: { "name": "Project Alpha", "title": "Project Alpha" } Но такая совместимость должна быть осознанной и ограниченной по времени.

53. Как типизировать переменные окружения

Frontend-приложения часто используют переменные окружения. Например: VITE_API_URL=https://api.example.com В TypeScript можно описать ожидаемые переменные. Типизация env переменных Vite TypeScript помогает улучшить автодополнение. interface ImportMetaEnv { readonly VITE_API_URL: string; } interface ImportMeta { readonly env: ImportMetaEnv; }

Но TypeScript не проверяет, что переменная действительно присутствует при запуске или сборке. Для этого нужна Runtime Validation или явная проверка конфигурации. Также важно помнить: переменные VITE_*, используемые в браузерной сборке, не должны содержать секреты.

54. Как проверять конфигурацию через Zod

Можно описать схему конфигурации. Zod Environment Variables Validation позволяет обнаруживать некорректные значения.

import { z } from "zod";

const EnvSchema = z.object({ VITE_API_URL: z.url() }); const env = EnvSchema.parse({ VITE_API_URL: import.meta.env.VITE_API_URL }); export const API_URL = env.VITE_API_URL; Здесь используется современный API Zod с z.url(). Для проектов на другой версии библиотеки может потребоваться соответствующий способ проверки URL.

Если конфигурация не соответствует схеме, приложение получит понятную ошибку на этапе инициализации.

55. Как использовать satisfies

Оператор satisfies проверяет соответствие значения типу, сохраняя более точную информацию о самом значении. TypeScript satisfies operator полезен для конфигураций. Например: type AppConfig = { apiUrl: string; retryCount: number; }; const config = { apiUrl: "https://api.example.com", retryCount: 3 } satisfies AppConfig;

TypeScript проверяет контракт, но не заменяет тип объекта на AppConfig так же, как это делает явная аннотация. Это особенно удобно при работе с объектами конфигурации и таблицами соответствия.

56. Что такое as const

as const помогает сохранить максимально узкие Literal Types и делает свойства соответствующего выражения доступными как readonly на уровне типов. TypeScript as const часто используется для неизменяемых наборов значений. const PROJECT_STATUSES = [ "draft", "active", "archived" ] as const; Теперь можно вывести тип: type ProjectStatus = typeof PROJECT_STATUSES[number]; Получится: "draft" | "active" | "archived"

Это удобный способ избежать дублирования списка значений и соответствующего Union Type. Но as const не замораживает объект во время выполнения.

57. Как типизировать асинхронные функции

Асинхронные функции возвращают Promise. Async Await TypeScript Promise позволяет описывать результат. Например: async function getUser( id: string ): Promise<User> { const response = await fetch( `/api/users/${id}` ); if (!response.ok) { throw new Error("Failed to load user"); } return response.json(); } Как и в предыдущих примерах, Promise<User> не означает автоматическую проверку JSON.

Для данных из внешнего источника нужно использовать валидатор.

58. Как типизировать ошибки try/catch

В JavaScript можно выбросить не только объект Error. Например: throw "Something went wrong"; Поэтому значение в catch не следует автоматически считать экземпляром Error. TypeScript Catch Unknown Error требует проверки. try { await loadProjects(); } catch (error: unknown) { if (error instanceof Error) { console.error(error.message); } else { console.error("Unknown error"); } }

Такой подход помогает избежать обращения к несуществующим свойствам.

59. Как тестировать TypeScript-код

TypeScript не заменяет тестирование. Он помогает обнаруживать ошибки типов, но не гарантирует правильность бизнес-логики. Тестирование TypeScript React Vitest позволяет проверять поведение функций и компонентов. Например:

import {

describe, expect, it } from "vitest"; function calculateTotal( price: number, quantity: number ) { return price * quantity; } describe("calculateTotal", () => { it("calculates total price", () => { expect( calculateTotal(100, 3) ).toBe(300); }); }); Для React-компонентов можно использовать React Testing Library. Для проверки API-контрактов — интеграционные и контрактные тесты.

60. Как проверять типы в CI/CD

Проверка TypeScript должна быть частью автоматического Pipeline. TypeScript Typecheck GitHub Actions помогает предотвращать деплой кода с ошибками типов. Например: name: Frontend Checks on: push: pull_request: jobs: typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "22" cache: "npm" - run: npm ci - run: npm run typecheck

Этот пример предполагает, что package.json и lockfile находятся в корне репозитория. Для монорепозитория необходимо указать правильную рабочую директорию. В реальном Pipeline также стоит запускать линтер, Unit Tests и сборку приложения.

61. Как организовать типы в большом React-проекте

Когда приложение растёт, количество типов увеличивается. Появляются модели пользователей, организаций, проектов, задач и уведомлений. Архитектура TypeScript типов React проекта должна помогать находить контракты, а не создавать огромный файл types.ts. Один из возможных вариантов: src/ ├── api/ │ ├── client.ts │ └── schema.d.ts │ ├── features/ │ ├── auth/ │ │ ├── types.ts │ │ └── api.ts │ │ │ ├── projects/

│ │ ├── types.ts │ │ ├── api.ts │ │ └── components/ │ │ │ └── tasks/ │ ├── types.ts │ └── api.ts │ └── shared/ └── types/ Контракты API можно генерировать из OpenAPI. Локальные UI-типы удобно хранить рядом с компонентами и функциями, которые их используют.

62. Типичные ошибки TypeScript-разработчиков

Первая ошибка — использовать any вместо продуманной типизации. Вторая — считать as SomeType проверкой данных. Третья — объявлять тип API-ответа без Runtime Validation. Четвёртая — дублировать одни и те же контракты вручную. Пятая — отключать strict, чтобы скрыть ошибки. Шестая — злоупотреблять Non-Null Assertion Operator !. Седьмая — использовать слишком сложные Generics там, где достаточно простого типа.

Восьмая — не учитывать null и undefined. Девятая — путать Optional Properties и поля со значением undefined. Десятая — считать TypeScript заменой Backend-валидации. Одиннадцатая — использовать типы Frontend как механизм проверки прав доступа. Двенадцатая — не проверять сетевые ошибки. Тринадцатая — забывать про изменения OpenAPI-контрактов. Четырнадцатая — хранить секреты в переменных окружения Vite.

Пятнадцатая — считать Readonly или as const полноценной Runtime-защитой от изменений. Эти ошибки TypeScript в Full Stack разработке часто создают иллюзию типобезопасности без реальных гарантий на границах системы.

63. Практическая архитектура типобезопасного Full Stack приложения

Объединим React, TypeScript, FastAPI, Pydantic и OpenAPI. Архитектура типобезопасного React FastAPI приложения может выглядеть так: PostgreSQL | v FastAPI | v Pydantic Models | v OpenAPI Schema | v openapi-typescript | v TypeScript Types | v OpenAPI Client | v Zod Validation | v TanStack Query | v React Components Не каждому проекту нужны все перечисленные инструменты.

Например, если API полностью контролируется вашей командой и хорошо покрыт контрактными тестами, можно выбирать разные уровни Runtime Validation в зависимости от риска. Но принцип остаётся прежним: Статические типы описывают ожидания, а Runtime Validation проверяет фактические данные.

64. Чек-лист TypeScript для Production

Перед выпуском приложения проверьте следующие моменты. TypeScript: включён ли строгий режим проверки? Типы: используется ли unknown вместо необоснованного any? React: корректно ли типизированы Props, Hooks и события? API: синхронизированы ли контракты Frontend и Backend? OpenAPI: генерируются ли типы из актуальной схемы FastAPI? Runtime Validation: проверяются ли данные на важных границах системы?

Forms: валидируются ли пользовательские данные до отправки? Backend: выполняет ли сервер собственную независимую валидацию? Errors: обрабатываются ли HTTP-ошибки, ошибки сети и некорректный JSON? Security: не используются ли Frontend-типы вместо проверки прав? Environment: не попадают ли секреты в браузерную сборку? CI/CD: запускаются ли tsc, тесты и сборка автоматически?

Такой чек-лист TypeScript Full Stack Production помогает повысить надёжность проекта без чрезмерного усложнения архитектуры.


FAQ

TypeScript — язык программирования, расширяющий JavaScript статической системой типов и дополнительными инструментами анализа кода. Да, желательно уверенно понимать JavaScript, функции, объекты, массивы, Promise и асинхронное программирование. Обычно TypeScript-код преобразуется в JavaScript перед выполнением в браузере. Он помогает типизировать Props, Hooks, события, API-ответы и состояние приложения.

Для многих объектных контрактов подходят оба варианта. Выбор зависит от структуры типов и соглашений команды. Generics — механизм параметризации типов, позволяющий создавать повторно используемые функции и структуры данных без потери типовой информации. Это встроенные инструменты TypeScript для преобразования типов, например Partial, Pick, Omit и Record.

any отключает значительную часть проверок типов и позволяет выполнять потенциально некорректные операции без предупреждений компилятора. unknown требует проверки типа перед использованием значения, а any позволяет выполнять операции практически без ограничений. Сам по себе TypeScript не проверяет фактические данные HTTP-ответа во время выполнения. Для этого нужны Runtime Validation или контрактные проверки.

Zod — библиотека для описания и проверки схем данных с интеграцией в систему типов TypeScript. Не обязательно, но он полезен для форм, API-ответов и других данных, требующих проверки во время выполнения. Да. TypeScript используется на Frontend, а FastAPI может предоставлять OpenAPI Schema для генерации типов. Один из удобных способов — генерировать TypeScript-типы из OpenAPI-схемы FastAPI.

Это инструмент генерации TypeScript-типов из OpenAPI Schema. Это набор более строгих проверок типов, помогающих обнаруживать дополнительные категории ошибок. Нет. TypeScript проверяет согласованность типов, но не гарантирует правильность бизнес-логики. Да. TypeScript широко используется в Backend-разработке на Node.js и совместимых средах.

Оба варианта подходят. Выбор зависит от требований проекта, опыта команды и используемой инфраструктуры. Один из практичных вариантов — React, TypeScript, FastAPI, Pydantic, PostgreSQL, OpenAPI и Zod.


Заключение

TypeScript — важный инструмент современной Full Stack разработки. Он помогает описывать контракты данных, создавать надёжные React-компоненты, типизировать API и обнаруживать ошибки до запуска приложения. Generics позволяют создавать повторно используемые функции. Utility Types уменьшают дублирование. Discriminated Union помогает моделировать состояния интерфейса.

Type Guards и unknown позволяют безопаснее работать с неопределёнными значениями. Но главный принцип TypeScript заключается в понимании его ограничений. TypeScript проверяет код на этапе разработки. Он не гарантирует, что Backend действительно вернёт объект нужной структуры. Поэтому для Full Stack приложения особенно важны границы между системами. FastAPI и Pydantic позволяют описывать и проверять Backend-контракты.

OpenAPI помогает передавать эти контракты во Frontend. openapi-typescript позволяет генерировать TypeScript-типы. Zod помогает проверять фактические данные во время выполнения. А React использует полученные типы для безопасной работы с состоянием и интерфейсом. В результате приложение становится проще сопровождать, особенно когда Backend и Frontend развиваются независимо.

Главный принцип: TypeScript обеспечивает статическую согласованность кода, но настоящая типобезопасность Full Stack приложения возникает только при сочетании типов, Runtime Validation, актуальных API-контрактов и автоматического тестирования.


Наверх