最后更新于:2026年7月

React 19 + TypeScript
React 19 + TypeScript:现代前端开发的黄金组合

React 19 来了! 从 Server Components 到 Actions,从 useOptimistic 到新的 use Hook,React 19 带来了自 Hooks 以来最大的一次范式转变。配合 TypeScript 的类型安全,现代前端开发的效率和质量都上了一个新台阶。

但很多开发者还在用 React 17/18 的老思维写 React 19 的代码,不仅没有享受到新特性的便利,反而可能踩坑。本文从基础到进阶,带你系统掌握 React 19 + TypeScript 的最佳实践。


一、React 19 新特性详解

1.1 React Server Components (RSC)

什么是 Server Components?

Server Components 是在服务器端渲染的 React 组件,它们的代码不会发送到客户端,可以:

Client vs Server 组件对比:

特性Server ComponentsClient Components
运行环境服务器浏览器
交互能力❌ 不能用 useState、useEffect✅ 可以
访问后端✅ 直接访问数据库/API❌ 需要通过 API
包体积不增加客户端包大小增加
使用场景数据获取、静态内容交互、状态管理

使用方式:

TSX
// 默认是 Server Component(不需要 'use client')
export default function BlogPosts() {
  // 直接在组件中获取数据
  const posts = await db.posts.findMany();

  return (
    <div>
      <h1>博客文章</h1>
      {posts.map((post) => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

// Client Component 需要 'use client' 指令
'use client';

import { useState } from 'react';

export function LikeButton({ postId }: { postId: string }) {
  const [likes, setLikes] = useState(0);

  return (
    <button onClick={() => setLikes(likes + 1)}>
      👍 {likes}
    </button>
  );
}

1.2 Actions

Actions 是 React 19 最重要的新特性之一,它让表单处理和数据变更变得更简单。

基础用法:

TSX
// Server Action(在 Server Component 中定义)
async function updateName(formData: FormData) {
  'use server';
  
  const name = formData.get('name') as string;
  await db.user.update({ name });
}

export default function Profile() {
  return (
    <form action={updateName}>
      <input type="text" name="name" />
      <button type="submit">更新</button>
    </form>
  );
}

配合 useActionState 使用:

TSX
'use client';

import { useActionState } from 'react';

async function updateName(prevState: { error?: string }, formData: FormData) {
  'use server';
  
  const name = formData.get('name') as string;
  
  if (name.length < 2) {
    return { error: '名字太短了' };
  }
  
  await db.user.update({ name });
  return { error: undefined };
}

export default function ProfileForm() {
  const [state, formAction, isPending] = useActionState(updateName, {});

  return (
    <form action={formAction}>
      <input type="text" name="name" />
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      <button type="submit" disabled={isPending}>
        {isPending ? '更新中...' : '更新'}
      </button>
    </form>
  );
}

1.3 useOptimistic Hook

乐观更新是提升用户体验的利器:

TSX
'use client';

import { useOptimistic, useState } from 'react';

type Message = {
  id: string;
  text: string;
  sending?: boolean;
};

async function sendMessage(text: string): Promise<Message> {
  // 模拟 API 调用
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return { id: Date.now().toString(), text };
}

export function Chat() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [optimisticMessages, addOptimistic] = useOptimistic(
    messages,
    (state, newMessage: string) => [
      ...state,
      { id: 'temp', text: newMessage, sending: true },
    ]
  );

  async function handleSubmit(formData: FormData) {
    const text = formData.get('message') as string;
    addOptimistic(text);
    
    const message = await sendMessage(text);
    setMessages((prev) => [...prev, message]);
  }

  return (
    <div>
      <div>
        {optimisticMessages.map((msg) => (
          <div key={msg.id} style={{ opacity: msg.sending ? 0.5 : 1 }}>
            {msg.text} {msg.sending && '发送中...'}
          </div>
        ))}
      </div>
      <form action={handleSubmit}>
        <input type="text" name="message" />
        <button type="submit">发送</button>
      </form>
    </div>
  );
}

1.4 use Hook

新的 use Hook 让读取异步资源更简单:

TSX
import { use } from 'react';

// 读取 Promise
function UserProfile({ userId }: { userId: string }) {
  const user = use(fetchUser(userId)); // 自动处理 Suspense
  
  return <div>{user.name}</div>;
}

// 读取 Context
function ThemeButton() {
  const theme = use(ThemeContext);
  
  return <button style={{ background: theme.primary }}>按钮</button>;
}

// 配合 Suspense 使用
function App() {
  return (
    <Suspense fallback={<Loading />}>
      <UserProfile userId="1" />
    </Suspense>
  );
}

1.5 其他重要新特性

useRef 初始值函数:

TSX
// 之前:初始值只在第一次渲染时计算,但语法不直观
const ref = useRef(createExpensiveObject());

// React 19:支持函数初始化,只执行一次
const ref = useRef(() => createExpensiveObject());

forwardRef 已废弃,用 ref 作为 prop:

TSX
// 之前:forwardRef
const MyInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

// React 19:直接接收 ref prop
function MyInput({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

useEffect 清理函数返回值:

TSX
// 之前:清理函数不接收参数
useEffect(() => {
  const controller = new AbortController();
  fetchData(controller.signal);
  return () => controller.abort();
}, []);

// React 19:清理函数可以接收一个参数(表示为什么清理)
useEffect(() => {
  // ...
  return (reason) => {
    console.log('Cleanup reason:', reason); // 'unmount' | 'replay' | 'dep-change'
  };
}, []);

二、TypeScript 类型最佳实践

2.1 组件 Props 类型定义

基础用法:

TSX
interface ButtonProps {
  text: string;
  onClick?: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
}

function Button({ 
  text, 
  onClick, 
  variant = 'primary', 
  disabled = false 
}: ButtonProps) {
  return (
    <button 
      onClick={onClick} 
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {text}
    </button>
  );
}

扩展原生 HTML 元素:

TSX
import type { ButtonHTMLAttributes } from 'react';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary';
  loading?: boolean;
}

function Button({ 
  variant = 'primary', 
  loading = false,
  children,
  disabled,
  ...props 
}: ButtonProps) {
  return (
    <button 
      className={`btn btn-${variant}`}
      disabled={disabled || loading}
      {...props}
    >
      {loading && <Spinner />}
      {children}
    </button>
  );
}

带 children 的组件:

TSX
import type { ReactNode } from 'react';

interface CardProps {
  title: string;
  children: ReactNode;
  footer?: ReactNode;
}

function Card({ title, children, footer }: CardProps) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

2.2 useState 类型推断

自动推断:

TSX
// TypeScript 会自动推断类型
const [count, setCount] = useState(0); // number
const [name, setName] = useState(''); // string
const [user, setUser] = useState({ name: 'Tom', age: 20 }); // { name: string; age: number }

显式指定类型(当初始值为 null 或 undefined 时):

TSX
interface User {
  id: string;
  name: string;
  email: string;
}

// 可能为 null
const [user, setUser] = useState<User | null>(null);

// 可能为 undefined
const [selectedId, setSelectedId] = useState<string | undefined>();

函数式更新:

TSX
interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

const [todos, setTodos] = useState<Todo[]>([]);

function toggleTodo(id: string) {
  setTodos((prev) =>
    prev.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  );
}

2.3 useReducer 类型

TSX
interface TodoState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
}

type TodoAction =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: string }
  | { type: 'DELETE_TODO'; payload: string }
  | { type: 'SET_FILTER'; payload: TodoState['filter'] };

function todoReducer(state: TodoState, action: TodoAction): TodoState {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, { id: Date.now().toString(), text: action.payload, completed: false }],
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
        ),
      };
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      };
    case 'SET_FILTER':
      return { ...state, filter: action.payload };
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: 'all',
  });
  // ...
}

2.4 Context 类型定义

TSX
import { createContext, useContext, useState, type ReactNode } from 'react';

interface Theme {
  primary: string;
  secondary: string;
  mode: 'light' | 'dark';
}

interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>({
    primary: '#007bff',
    secondary: '#6c757d',
    mode: 'light',
  });

  const toggleTheme = () => {
    setTheme((prev) => ({
      ...prev,
      mode: prev.mode === 'light' ? 'dark' : 'light',
    }));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

// 使用
function MyComponent() {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>切换主题</button>;
}

2.5 自定义 Hook 类型

TSX
import { useState, useEffect } from 'react';

interface UseFetchResult<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(url);
      const json = await res.json();
      setData(json);
    } catch (e) {
      setError(e as Error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [url]);

  return { data, loading, error, refetch: fetchData };
}

// 使用
function UserProfile({ userId }: { userId: string }) {
  const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>出错了:{error.message}</div>;
  if (!user) return <div>用户不存在</div>;
  
  return <div>{user.name}</div>;
}

三、状态管理方案选择

3.1 各方案对比

方案适用场景学习曲线包体积推荐度
useState + Props小型应用、简单状态0⭐⭐⭐
useReducer + Context中型应用、全局状态0⭐⭐⭐⭐
Zustand中小应用、轻量状态⭐⭐⭐⭐⭐
Jotai原子化状态、细粒度更新⭐⭐⭐⭐
Redux Toolkit大型应用、复杂状态⭐⭐⭐
TanStack Query服务端状态、数据缓存⭐⭐⭐⭐⭐

3.2 Zustand 最佳实践

TSX
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

interface Bear {
  id: string;
  name: string;
}

interface BearState {
  bears: Bear[];
  addBear: (name: string) => void;
  removeBear: (id: string) => void;
  clearBears: () => void;
}

const useBearStore = create<BearState>()(
  devtools(
    persist(
      (set) => ({
        bears: [],
        addBear: (name) =>
          set((state) => ({
            bears: [...state.bears, { id: Date.now().toString(), name }],
          })),
        removeBear: (id) =>
          set((state) => ({
            bears: state.bears.filter((bear) => bear.id !== id),
          })),
        clearBears: () => set({ bears: [] }),
      }),
      { name: 'bear-storage' }
    )
  )
);

// 使用
function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears.length} 只熊</h1>;
}

function BearControls() {
  const addBear = useBearStore((state) => state.addBear);
  const removeBear = useBearStore((state) => state.removeBear);
  
  return (
    <div>
      <button onClick={() => addBear('新熊')}>添加熊</button>
    </div>
  );
}

3.3 TanStack Query(React Query)最佳实践

TSX
import { 
  useQuery, 
  useMutation, 
  useQueryClient,
  type UseQueryResult 
} from '@tanstack/react-query';

interface User {
  id: string;
  name: string;
  email: string;
}

// 查询 Hook
function useUser(userId: string): UseQueryResult<User, Error> {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: async () => {
      const res = await fetch(`/api/users/${userId}`);
      if (!res.ok) throw new Error('获取用户失败');
      return res.json();
    },
    enabled: !!userId,
    staleTime: 5 * 60 * 1000, // 5 分钟内视为新鲜
  });
}

// 变更 Hook
function useUpdateUser() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: async ({ userId, data }: { userId: string; data: Partial<User> }) => {
      const res = await fetch(`/api/users/${userId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      if (!res.ok) throw new Error('更新失败');
      return res.json();
    },
    onSuccess: (data, variables) => {
      // 自动更新缓存
      queryClient.setQueryData(['user', variables.userId], data);
    },
  });
}

// 使用
function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useUser(userId);
  const updateUser = useUpdateUser();
  
  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>出错了</div>;
  if (!user) return <div>用户不存在</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button 
        onClick={() => updateUser.mutate({ userId, data: { name: '新名字' } })}
      >
        更新
      </button>
    </div>
  );
}

四、性能优化

4.1 渲染性能优化

memo 组件记忆化:

TSX
import { memo } from 'react';

interface TodoItemProps {
  todo: Todo;
  onToggle: (id: string) => void;
}

// 只有当 props 变化时才重新渲染
const TodoItem = memo(function TodoItem({ todo, onToggle }: TodoItemProps) {
  console.log('TodoItem 渲染');
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {todo.text}
    </div>
  );
});

useCallback 函数记忆化:

TSX
import { useCallback, useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');

  // 只有 todos 变化时才重新创建函数
  const handleToggle = useCallback((id: string) => {
    setTodos((prev) =>
      prev.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  }, []); // 空依赖,函数永远不变

  const filteredTodos = todos.filter((todo) => {
    if (filter === 'all') return true;
    if (filter === 'active') return !todo.completed;
    return todo.completed;
  });

  return (
    <div>
      {filteredTodos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
      ))}
    </div>
  );
}

useMemo 计算结果记忆化:

TSX
import { useMemo } from 'react';

function TodoStats({ todos }: { todos: Todo[] }) {
  // 只有 todos 变化时才重新计算
  const stats = useMemo(() => {
    console.log('计算统计数据');
    const total = todos.length;
    const completed = todos.filter((t) => t.completed).length;
    const active = total - completed;
    const completionRate = total > 0 ? (completed / total) * 100 : 0;
    
    return { total, completed, active, completionRate };
  }, [todos]);

  return (
    <div>
      <p>总计:{stats.total}</p>
      <p>已完成:{stats.completed}</p>
      <p>进行中:{stats.active}</p>
      <p>完成率:{stats.completionRate.toFixed(1)}%</p>
    </div>
  );
}

4.2 列表性能优化

key 的正确使用:

TSX
// ✅ 好:使用唯一 ID
{todos.map((todo) => (
  <TodoItem key={todo.id} todo={todo} />
))}

// ❌ 不好:使用索引(当列表顺序会变化时)
{todos.map((todo, index) => (
  <TodoItem key={index} todo={todo} />
))}

虚拟滚动(长列表):

TSX
import { useVirtualizer } from '@tanstack/react-virtual';

function LongList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 5,
  });

  return (
    <div
      ref={parentRef}
      style={{
        height: '600px',
        overflow: 'auto',
      }}
    >
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

4.3 代码分割与懒加载

TSX
import { lazy, Suspense } from 'react';

// 动态导入组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = lazy(() => import('./Dashboard'));

function App() {
  return (
    <div>
      <h1>我的应用</h1>
      <Suspense fallback={<Loading />}>
        <HeavyComponent />
      </Suspense>
      
      <Suspense fallback={<Loading />}>
        <Dashboard />
      </Suspense>
    </div>
  );
}

// 路由级代码分割
import { Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function AppRouter() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}

五、项目架构与代码规范

5.1 推荐目录结构

PLAINTEXT
src/
├── components/          # 通用组件
│   ├── ui/             # 基础 UI 组件(Button, Input...)
│   ├── layout/         # 布局组件(Header, Sidebar...)
│   └── features/       # 业务组件
├── pages/              # 页面组件
├── hooks/              # 自定义 Hook
├── stores/             # 状态管理(Zustand store)
├── services/           # API 服务
├── types/              # TypeScript 类型定义
├── utils/              # 工具函数
├── styles/             # 全局样式
├── assets/             # 静态资源
├── App.tsx
└── main.tsx

5.2 组件开发规范

组件文件结构:

PLAINTEXT
components/
  UserCard/
    UserCard.tsx        # 主组件
    UserCard.test.tsx   # 测试
    UserCard.stories.tsx # Storybook
    index.ts            # 导出

组件编写顺序:

TSX
// 1. 导入
import { useState, useCallback } from 'react';

// 2. 类型定义
interface UserCardProps {
  user: User;
  onEdit?: (user: User) => void;
}

// 3. 组件函数
export function UserCard({ user, onEdit }: UserCardProps) {
  // 4. State
  const [isEditing, setIsEditing] = useState(false);
  
  // 5. 计算属性
  const fullName = `${user.firstName} ${user.lastName}`;
  
  // 6. 事件处理
  const handleEdit = useCallback(() => {
    setIsEditing(true);
    onEdit?.(user);
  }, [user, onEdit]);
  
  // 7. 渲染
  return (
    <div className="user-card">
      <h3>{fullName}</h3>
      <p>{user.email}</p>
      <button onClick={handleEdit}>编辑</button>
    </div>
  );
}

// 8. 默认导出
export default UserCard;

5.3 类型定义规范

类型文件组织:

PLAINTEXT
types/
  api.ts           # API 相关类型
  models.ts        # 数据模型
  components.ts    # 组件 Props 类型
  utils.ts         # 工具类型
  index.ts         # 统一导出

类型命名规范:

TSX
// ✅ 好的命名
interface User {
  id: string;
  name: string;
}

type UserRole = 'admin' | 'editor' | 'viewer';

type ApiResponse<T> = {
  data: T;
  success: boolean;
  message: string;
};

// ❌ 不好的命名
interface IUser {}     // 不要加 I 前缀
type user = {};        // 用 PascalCase
type UserType = {};    // 避免 Type 后缀(除非必要)

5.4 ESLint + Prettier 配置

推荐的规则集:

JSON
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "prettier"
  ],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "warn",
    "react/prop-types": "off"
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

六、常见问题与解决方案

Q1:什么时候用 Server Component,什么时候用 Client Component?

快速判断:

PLAINTEXT
需要交互(点击、输入、状态)? → Client Component
需要访问浏览器 API(window, document)? → Client Component
需要获取数据、访问后端? → Server Component
纯展示内容? → Server Component

最佳实践:

Q2:any 类型怎么处理?

避免 any 的方法:

TSX
// 1. 使用 unknown 代替 any
function processValue(value: unknown) {
  if (typeof value === 'string') {
    // 这里 value 被收窄为 string
    console.log(value.toUpperCase());
  }
}

// 2. 使用类型守卫
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'name' in value
  );
}

// 3. 使用泛型
function identity<T>(value: T): T {
  return value;
}

Q3:状态更新不及时怎么办?

常见原因:

  1. 闭包陷阱
TSX
// ❌ 问题:count 总是初始值
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count); // 闭包捕获了初始值
  }, 1000);
  return () => clearInterval(timer);
}, []); // 空依赖

// ✅ 解决方案:使用函数式更新
useEffect(() => {
  const timer = setInterval(() => {
    setCount((prev) => prev + 1);
  }, 1000);
  return () => clearInterval(timer);
}, []);
  1. 状态更新是异步的
TSX
// ❌ 问题:刚设置完就读取不到新值
setCount(count + 1);
console.log(count); // 还是旧值

// ✅ 解决方案:使用 useEffect 监听变化
useEffect(() => {
  console.log('count 变化了:', count);
}, [count]);

Q4:useEffect 依赖警告怎么办?

常见场景:

TSX
// ❌ ESLint 警告:缺少依赖
useEffect(() => {
  fetchData(userId);
}, []); // 警告:缺少 userId

// ✅ 解决方案 1:添加依赖(推荐)
useEffect(() => {
  fetchData(userId);
}, [userId]);

// ✅ 解决方案 2:用 useCallback/useMemo 稳定函数
const fetchUserData = useCallback(() => {
  fetchData(userId);
}, [userId]);

useEffect(() => {
  fetchUserData();
}, [fetchUserData]);

Q5:表单处理有什么好方案?

推荐方案:

TSX
// React Hook Form + Zod 示例
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2, '名字至少 2 个字符'),
  email: z.string().email('请输入有效的邮箱'),
  age: z.number().min(18, '必须年满 18 岁').optional(),
});

type FormData = z.infer<typeof schema>;

function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormData) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input {...register('name')} placeholder="姓名" />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <input {...register('email')} placeholder="邮箱" />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <button type="submit">提交</button>
    </form>
  );
}

七、工具链与生态推荐

7.1 开发工具

工具用途推荐度
Vite构建工具⭐⭐⭐⭐⭐
TypeScript类型安全⭐⭐⭐⭐⭐
ESLint代码质量⭐⭐⭐⭐⭐
Prettier代码格式化⭐⭐⭐⭐⭐
Storybook组件开发⭐⭐⭐⭐
React DevTools调试工具⭐⭐⭐⭐⭐

7.2 UI 组件库

特点推荐度
Shadcn/ui可定制、无样式、复制粘贴⭐⭐⭐⭐⭐
MUI (Material UI)功能完整、Material Design⭐⭐⭐⭐
Ant Design企业级、中文文档好⭐⭐⭐⭐
Chakra UI简洁、可访问性好⭐⭐⭐⭐
Tailwind CSS原子化 CSS、灵活⭐⭐⭐⭐⭐
Radix UI无样式、可访问性好⭐⭐⭐⭐

7.3 测试工具

工具用途推荐度
Vitest单元测试⭐⭐⭐⭐⭐
React Testing Library组件测试⭐⭐⭐⭐⭐
PlaywrightE2E 测试⭐⭐⭐⭐⭐
Jest单元测试(老牌)⭐⭐⭐⭐

八、总结

React 19 + TypeScript 学习路径

PLAINTEXT
第 1 周:React 基础 + TypeScript 基础
  - JSX、组件、Props、State
  - TypeScript 基本类型、接口、泛型

第 2 周:Hooks 深入
  - useState、useEffect、useContext
  - useReducer、useCallback、useMemo
  - 自定义 Hook

第 3-4 周:React 19 新特性
  - Server Components
  - Actions、useActionState
  - useOptimistic、use Hook

第 2 个月:状态管理 + 性能优化
  - Zustand、TanStack Query
  - memo、useMemo、useCallback
  - 代码分割、虚拟滚动

第 3 个月:工程化 + 最佳实践
  - 项目架构、代码规范
  - 测试、CI/CD
  - 性能监控、错误边界

核心要点回顾

  1. 拥抱 Server Components:数据获取和展示用 Server Component,交互用 Client Component
  2. 类型安全第一:尽量避免 any,充分利用 TypeScript 的类型推断
  3. 合理选择状态方案:小项目用 useState,中项目用 Zustand,大项目用 Redux Toolkit
  4. 服务端状态用 TanStack Query:不要自己管理服务器数据
  5. 性能优化要适度:先测量再优化,不要过早优化
  6. 保持组件小而专注:每个组件只做一件事

【相关推荐】


React 19 + TypeScript 是 2026 年前端开发的黄金组合。掌握这些新特性和最佳实践,你的开发效率和代码质量都会有质的飞跃。祝你编码愉快!⚛️

版权声明

作者: 易邦

链接: https://blog.e8k.net/posts/react19-typescript-guide-2026/

许可证: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。