Модуль 12.5
Roadmap: куда расти дальше
Карта развития TypeScript-разработчика
Введение: что дальше
Поздравляю! Вы прошли базовый курс TypeScript и освоили основные концепции. Но это только начало пути. TypeScript — это не просто язык, это экосистема с сотнями инструментов, фреймворков и направлений.
В этом уроке мы составим карту развития — roadmap, который поможет вам выбрать направление и понять, что изучать дальше. Не пытайтесь освоить всё сразу — выберите одно направление и углубляйтесь в него.
1. Фронтенд разработка с TypeScript
1.1 React + TypeScript
React — самый популярный UI фреймворк, а TypeScript делает его ещё мощнее. Изучение React + TypeScript — отличный выбор для карьеры.
// Пример: Типизированный React компонент
import React, { useState, useEffect, useCallback } from 'react';
interface User {
id: number;
name: string;
email: string;
}
interface UserCardProps {
userId: number;
onSelect: (user: User) => void;
}
export const UserCard: React.FC<UserCardProps> = ({ userId, onSelect }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const fetchUser = useCallback(async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data: User = await response.json();
setUser(data);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
}, [userId]);
useEffect(() => {
void fetchUser();
}, [fetchUser]);
if (loading) return <div className="loading">Loading...</div>;
if (!user) return <div className="error">User not found</div>;
return (
<div className="user-card" onClick={() => onSelect(user)}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
};
- React hooks с типами
- Context API + TypeScript
- React Router с типизацией
- React Query / SWR
- Formуправление: React Hook Form
- Create React App + TypeScript
- Vite + React + TypeScript
- Storybook с TypeScript
- React Testing Library
2-3 месяца для базового уровня
1.2 Vue + TypeScript
// Пример: Vue 3 Composition API с TypeScript
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
interface User {
id: number;
name: string;
email: string;
}
const users = ref<User[]>([]);
const searchQuery = ref<string>('');
const loading = ref<boolean>(true);
const filteredUsers = computed(() =>
users.value.filter(user =>
user.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
);
async function fetchUsers(): Promise<void> {
try {
const response = await fetch('/api/users');
users.value = await response.json();
} finally {
loading.value = false;
}
}
onMounted(() => {
void fetchUsers();
});
</script>
<template>
<div>
<input v-model="searchQuery" placeholder="Search..." />
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="user in filteredUsers" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
</template>
1.3 Angular
Angular использует TypeScript «из коробки» и обеспечивает полную типизацию. Это отличный выбор для enterprise-проектов.
// Пример: Angular компонент с TypeScript
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { UserService } from '../services/user.service';
import { User } from '../models/user.model';
@Component({
selector: 'app-user-list',
template: `
<div *ngIf="loading">Loading...</div>
<ul *ngIf="!loading">
<li *ngFor="let user of users"
(click)="selectUser(user)">
{{ user.name }}
</li>
</ul>
`
})
export class UserListComponent implements OnInit {
@Input() searchTerm: string = '';
@Output() userSelected = new EventEmitter<User>();
users: User[] = [];
loading: boolean = true;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUsers().subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (err) => {
console.error('Error:', err);
this.loading = false;
}
});
}
selectUser(user: User): void {
this.userSelected.emit(user);
}
}
2. Бэкенд разработка с TypeScript
2.1 Node.js + Express/Fastify
// Пример: Express сервер с TypeScript
import express, { Request, Response, NextFunction } from 'express';
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
const app = express();
app.use(express.json());
// Middleware для логирования
app.use((req: Request, res: Response, next: NextFunction) => {
console.log(`${req.method} ${req.path}`);
next();
});
// GET /api/users
app.get('/api/users', async (req: Request, res: Response) => {
try {
const users: User[] = await UserService.getAll();
const response: ApiResponse<User[]> = {
success: true,
data: users,
};
res.json(response);
} catch (error) {
const response: ApiResponse<never> = {
success: false,
error: 'Failed to fetch users',
};
res.status(500).json(response);
}
});
// GET /api/users/:id
app.get('/api/users/:id', async (req: Request, res: Response) => {
const id = parseInt(req.params.id, 10);
if (isNaN(id)) {
res.status(400).json({ success: false, error: 'Invalid ID' });
return;
}
const user = await UserService.getById(id);
if (!user) {
res.status(404).json({ success: false, error: 'User not found' });
return;
}
res.json({ success: true, data: user });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
2.2 NestJS
NestJS — это фреймворк для создания масштабируемых серверных приложений, вдохновлённый Angular. Он использует TypeScript из коробки и предоставляет архитектуру на основе модулей.
// Пример: NestJS модуль с TypeScript
import { Controller, Get, Post, Body, Param, Inject } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Controller('users')
export class UsersController {
constructor(
@Inject(UsersService)
private readonly usersService: UsersService,
) {}
@Get()
async findAll(): Promise<User[]> {
return this.usersService.findAll();
}
@Get(':id')
async findOne(@Param('id') id: string): Promise<User> {
return this.usersService.findOne(id);
}
@Post()
async create(@Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.create(createUserDto);
}
}
// DTO (Data Transfer Object)
export class CreateUserDto {
name: string;
email: string;
password: string;
}
2.3 Deno и Bun
Deno
- TypeScript из коробки
- Встроенная безопасность
- Нативная поддержка Web API
- Совместимость с Node.js (частично)
- Простой деплой
Bun
- TypeScript из коробки
- В 10-100 раз быстрее Node.js
- Встроенный тестовый раннер
- Встроенный пакетный менеджер
- Совместимость с Node.js API
3. Full-stack разработка
3.1 Next.js + TypeScript
// Пример: Next.js страница с TypeScript
// pages/users/[id].tsx
import { GetServerSideProps, NextPage } from 'next';
import { useRouter } from 'next/router';
import { User } from '../../types';
interface UserPageProps {
user: User | null;
error?: string;
}
const UserPage: NextPage<UserPageProps> = ({ user, error }) => {
const router = useRouter();
const { id } = router.query;
if (error) {
return <div>Error: {error}</div>;
}
if (!user) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={() => router.back()}>Go Back</button>
</div>
);
};
export const getServerSideProps: GetServerSideProps<UserPageProps> = async (context) => {
const { id } = context.params!;
try {
const response = await fetch(`http://localhost:3000/api/users/${id}`);
if (!response.ok) {
return { props: { user: null, error: 'User not found' } };
}
const user: User = await response.json();
return { props: { user } };
} catch {
return { props: { user: null, error: 'Failed to fetch user' } };
}
};
export default UserPage;
3.2 Remix
// Пример: Remix страница с TypeScript
// app/routes/users/$id.tsx
import { LoaderFunctionArgs, json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
interface User {
id: string;
name: string;
email: string;
}
export async function loader({ params }: LoaderFunctionArgs): Promise<Response> {
const { id } = params;
if (!id) {
throw new Response('Not Found', { status: 404 });
}
const response = await fetch(`http://localhost:3000/api/users/${id}`);
if (!response.ok) {
throw new Response('Not Found', { status: 404 });
}
const user: User = await response.json();
return json({ user });
}
export default function UserPage() {
const { user } = useLoaderData<typeof loader>();
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
3.3 SvelteKit
// Пример: SvelteKit страница
// src/routes/users/[id]/+page.server.ts
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
interface User {
id: string;
name: string;
email: string;
}
export const load: PageServerLoad = async ({ params }) => {
const { id } = params;
const response = await fetch(`http://localhost:3000/api/users/${id}`);
if (!response.ok) {
error(404, 'User not found');
}
const user: User = await response.json();
return { user };
};
// src/routes/users/[id]/+page.svelte
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h1>{data.user.name}</h1>
<p>{data.user.email}</p>
4. Мобильная разработка
4.1 React Native + TypeScript
// Пример: React Native компонент с TypeScript
import React, { useState } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
interface Todo {
id: string;
text: string;
completed: boolean;
}
export const TodoApp: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [input, setInput] = useState<string>('');
const addTodo = () => {
if (input.trim()) {
const newTodo: Todo = {
id: Date.now().toString(),
text: input.trim(),
completed: false,
};
setTodos([...todos, newTodo]);
setInput('');
}
};
const toggleTodo = (id: string) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const renderTodo = ({ item }: { item: Todo }) => (
<View style={styles.todoItem}>
<Text
style={[styles.todoText, item.completed && styles.completed]}
onPress={() => toggleTodo(item.id)}>
{item.text}
</Text>
</View>
);
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="Add todo..."
/>
<Button title="Add" onPress={addTodo} />
<FlatList
data={todos}
renderItem={renderTodo}
keyExtractor={(item) => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
input: { borderWidth: 1, borderColor: '#ccc', padding: 10, marginBottom: 10 },
todoItem: { padding: 15, borderBottomWidth: 1, borderBottomColor: '#eee' },
todoText: { fontSize: 16 },
completed: { textDecorationLine: 'line-through', color: '#999' },
});
Альтернативы:
- Expo — платформа для React Native с упрощённой настройкой
- React Native Paper — UI библиотека с Material Design
- React Navigation — навигация для React Native
- Flutter — альтернатива (язык Dart, похож на TypeScript)
5. DevOps и инфраструктура
// Пример: Docker + TypeScript
// Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --production
EXPOSE 3000
CMD ["node", "dist/index.js"]
// docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
6. Типизация баз данных
6.1 Prisma
// schema.prisma — Схема Prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
MODERATOR
}
// Использование в коде
import { PrismaClient, User, Post } from '@prisma/client';
const prisma = new PrismaClient();
async function createUser(name: string, email: string): Promise<User> {
return prisma.user.create({
data: { name, email },
});
}
async function getUserWithPosts(userId: number): Promise<User & { posts: Post[] }> {
return prisma.user.findUniqueOrThrow({
where: { id: userId },
include: { posts: true },
});
}
async function createPost(authorId: number, title: string): Promise<Post> {
return prisma.post.create({
data: {
title,
author: { connect: { id: authorId } },
},
});
}
6.2 TypeORM и Drizzle
TypeORM
- Decorator-based ORM
- Поддержка Active Record и Data Mapper
- Миграции
- Сложные запросы
- TypeScript-first
Drizzle ORM
- Легковесный ORM
- SQL-like API
- Высокая производительность
- Type-safe запросы
- Минимальный runtime
7. Тестирование как карьера
// Пример: E2E тесты с Playwright
import { test, expect, Page } from '@playwright/test';
test.describe('User Management', () => {
let page: Page;
test.beforeEach(async ({ browser }) => {
page = await browser.newPage();
});
test('should display user list', async () => {
await page.goto('/users');
const userCards = page.locator('[data-testid="user-card"]');
await expect(userCards).toHaveCount(5);
const firstUserName = userCards.first().locator('h3');
await expect(firstUserName).toBeVisible();
});
test('should create new user', async () => {
await page.goto('/users/new');
await page.fill('[data-testid="name-input"]', 'Alice');
await page.fill('[data-testid="email-input"]', 'alice@example.com');
await page.click('[data-testid="submit-button"]');
await expect(page).toHaveURL(/\/users\/\d+/);
await expect(page.locator('h1')).toContainText('Alice');
});
test('should handle validation errors', async () => {
await page.goto('/users/new');
await page.click('[data-testid="submit-button"]');
const error = page.locator('[data-testid="error-message"]');
await expect(error).toBeVisible();
await expect(error).toContainText('Name is required');
});
});
// Пример: API тесты
import request from 'supertest';
import app from '../src/app';
test('GET /api/users should return users', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body.success).toBe(true);
expect(Array.isArray(response.body.data)).toBe(true);
});
test('POST /api/users should create user', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@example.com' })
.expect(201);
expect(response.body.data.name).toBe('Alice');
});
8. Архитектурные паттерны
// 1. Clean Architecture с TypeScript
// domain/entities/User.ts
export interface User {
id: string;
name: string;
email: string;
}
// domain/repositories/UserRepository.ts
export interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<void>;
}
// domain/use-cases/CreateUser.ts
export class CreateUserUseCase {
constructor(private userRepository: UserRepository) {}
async execute(name: string, email: string): Promise<User> {
const existing = await this.userRepository.findByEmail(email);
if (existing) {
throw new Error('User already exists');
}
const user: User = {
id: crypto.randomUUID(),
name,
email,
};
await this.userRepository.save(user);
return user;
}
}
// infrastructure/repositories/PostgresUserRepository.ts
import { UserRepository } from '../../domain/repositories/UserRepository';
import { User } from '../../domain/entities/User';
export class PostgresUserRepository implements UserRepository {
async findById(id: string): Promise<User | null> {
// Реализация с PostgreSQL
const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
return result.rows[0] || null;
}
async findByEmail(email: string): Promise<User | null> {
const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
return result.rows[0] || null;
}
async save(user: User): Promise<void> {
await db.query(
'INSERT INTO users (id, name, email) VALUES ($1, $2, $3)',
[user.id, user.name, user.email]
);
}
}
// 2. Microservices Architecture
// shared/types/Event.ts
export interface DomainEvent {
type: string;
timestamp: Date;
payload: Record<string, unknown>;
}
// services/user-service/src/events/UserCreated.ts
export interface UserCreatedEvent extends DomainEvent {
type: 'USER_CREATED';
payload: {
userId: string;
name: string;
email: string;
};
}
// services/order-service/src/handlers/UserCreatedHandler.ts
export function handleUserCreated(event: UserCreatedEvent) {
// Обработка события
}
9. Открытый исходный код
Почему стоит участвовать в Open Source:
- Портфолио — реальные проекты для демонстрации навыков
- Нетворкинг — общение с опытными разработчиками
- Обучение — чтение чужого кода лучший способ учиться
- Карьера — мейнтейнеры часто получают предложения о работе
- Вклад в сообщество — помогает другим разработчикам
// Как начать: // 1. Найдите проект, который используете // 2. Изучите CONTRIBUTING.md // 3. Начните с "good first issue" меток // 4. Fork → Branch → PR // Пример: Contributions к TypeScript экосистеме // - Исправление опечаток в документации // - Добавление примеров кода // - Исправление багов (issues) // - Написание тестов // - Улучшение документации
10. Сертификации и обучение
Сертификации
- Microsoft: TypeScript Certification
- AWS: Developer Associate
- Google: Cloud Developer
- Meta: Frontend Developer
Платформы обучения
- Frontend Masters
- Udemy
- Coursera
- Pluralsight
- Official TypeScript Handbook
11. Портфолио и карьера
Проекты для портфолио:
- TODO приложение — CRUD операции, фильтрация, сортировка
- Блог платформа — авторизация, посты, комментарии
- API клиент — работа с REST/GraphQL API
- Чат приложение — WebSocket, реальное время
- E-commerce — корзина, оплата, заказы
- Dashboard — графики, аналитика, таблицы
// Структура проекта для портфолио: project/ ├── src/ │ ├── domain/ # Business logic │ ├── application/ # Use cases │ ├── infrastructure/ # External integrations │ ├── presentation/ # UI layer │ └── shared/ # Shared utilities ├── tests/ │ ├── unit/ │ ├── integration/ │ └── e2e/ ├── docs/ │ ├── architecture.md │ └── api.md ├── .github/ │ └── workflows/ │ └── ci.yml ├── tsconfig.json ├── jest.config.ts ├── eslint.config.js ├── .prettierrc └── README.md
12. Ресурсы для обучения
Документация
- TypeScript Handbook
- TypeScript Deep Dive
- MDN Web Docs
- React/Vue/Angular Docs
Практика
- LeetCode / HackerRank
- Exercism (TypeScript track)
- Type Challenges
- Build your own X
- "TypeScript in Depth" — Kirill Konchenko
- "Effective TypeScript" — Dan Vanderkam
- "Learning TypeScript" — Ethan Niser
- "TypeScript Design Patterns" — Vilcov
13. Визуальные библиотеки и UI фреймворки
13.1 Библиотеки компонентов
// Пример: Типизированный компонент с Material UI
import React from 'react';
import { TextField, Button, Box, Typography } from '@mui/material';
interface LoginFormProps {
onSubmit: (email: string, password: string) => void;
loading?: boolean;
}
export const LoginForm: React.FC<LoginFormProps> = ({ onSubmit, loading = false }) => {
const [email, setEmail] = React.useState<string>('');
const [password, setPassword] = React.useState<string>('');
const [errors, setErrors] = React.useState<Record<string, string>>({});
const validate = (): boolean => {
const newErrors: Record<string, string> = {};
if (!email) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
newErrors.email = 'Invalid email format';
}
if (!password) {
newErrors.password = 'Password is required';
} else if (password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validate()) {
onSubmit(email, password);
}
};
return (
<Box component="form" onSubmit={handleSubmit} sx={{ maxWidth: 400, mx: 'auto' }}>
<Typography variant="h4" gutterBottom>Login</Typography>
<TextField
fullWidth
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
error={Boolean(errors.email)}
helperText={errors.email}
margin="normal"
/>
<TextField
fullWidth
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
error={Boolean(errors.password)}
helperText={errors.password}
margin="normal"
/>
<Button
fullWidth
type="submit"
variant="contained"
disabled={loading}
sx={{ mt: 3 }}
>
{loading ? 'Logging in...' : 'Login'}
</Button>
</Box>
);
};
- Material UI (MUI)
- Ant Design
- Chakra UI
- RADIX UI
- Shadcn/UI
- Vuetify
- Element Plus
- PrimeVue
- Naive UI
- Quasar
13.2 Tailwind CSS с TypeScript
// Tailwind CSS с TypeScript
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a8a',
},
},
},
},
plugins: [],
};
export default config;
// Типизированный компонент с Tailwind
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size: 'sm' | 'md' | 'lg';
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({
variant,
size,
children,
onClick,
disabled = false,
}) => {
const variantClasses = {
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
danger: 'bg-red-500 hover:bg-red-600 text-white',
};
const sizeClasses = {
sm: 'px-3 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
return (
<button
className={`${variantClasses[variant]} ${sizeClasses[size]} rounded font-medium transition-colors`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
};
14. Управление состоянием (State Management)
// 1. Redux Toolkit с TypeScript
import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
interface Todo {
id: string;
text: string;
completed: boolean;
}
interface TodoState {
items: Todo[];
filter: 'all' | 'active' | 'completed';
}
const initialState: TodoState = {
items: [],
filter: 'all',
};
const todoSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
state.items.push({
id: crypto.randomUUID(),
text: action.payload,
completed: false,
});
},
toggleTodo: (state, action: PayloadAction<string>) => {
const todo = state.items.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
setFilter: (state, action: PayloadAction<TodoState['filter']>) => {
state.filter = action.payload;
},
},
});
export const { addTodo, toggleTodo, setFilter } = todoSlice.actions;
// 2. Zustand (лёгкий state management)
import create from 'zustand';
interface BearState {
bears: number;
increase: (by: number) => void;
}
const useBearStore = create<BearState>((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}));
// 3. Jotai (атомарный state management)
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const doubledAtom = atom((get) => get(countAtom) * 2);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
{count}
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
15. Работа с формами
// React Hook Form с TypeScript
import { useForm, SubmitHandler, FieldErrors } from 'react-hook-form';
interface FormData {
firstName: string;
lastName: string;
email: string;
age: number;
password: string;
}
export const UserProfileForm: React.FC = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
defaultValues: {
firstName: '',
lastName: '',
email: '',
age: 0,
password: '',
},
});
const onSubmit: SubmitHandler<FormData> = async (data) => {
console.log(data);
// API call
};
const onError = (errors: FieldErrors<FormData>) => {
console.log('Form errors:', errors);
};
return (
<form onSubmit={handleSubmit(onSubmit, onError)}>
<input
{...register('firstName', { required: 'First name is required' })}
/>
{errors.firstName && <span>{errors.firstName.message}</span>}
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Invalid email',
},
})}
/>
{errors.email && <span>{errors.email.message}</span>}
<input
type="number"
{...register('age', {
required: 'Age is required',
min: { value: 18, message: 'Must be at least 18' },
max: { value: 120, message: 'Must be at most 120' },
})}
/>
{errors.age && <span>{errors.age.message}</span>}
<input
type="password"
{...register('password', {
required: 'Password is required',
minLength: { value: 8, message: 'Min 8 characters' },
})}
/>
{errors.password && <span>{errors.password.message}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
};
План действий
Выберите одно направление и следуйте ему:
- Неделя 1-2: Выберите направление (фронтенд/бэкенд/full-stack)
- Неделя 3-4: Изучите основы фреймворка
- Месяц 2: Создайте мини-проект
- Месяц 3: Добавьте тесты и документацию
- Месяц 4: Задеплойте проект
- Месяц 5: Внесите вклад в Open Source
- Месяц 6: Обновите портфолио и LinkedIn
Помните: постоянное обучение — ключ к успеху. Технологии меняются, но фундаментальные знания остаются. TypeScript — это инструмент, который будет полезен на любом этапе вашей карьеры.
Roadmap: куда расти дальше
10 вопросов