从零开始构建现代化 React 应用,涵盖环境搭建、核心概念、实战项目、性能优化全流程,适合完全新手快速入门。
一、前置准备:基础技能与工具
1. 必备基础
-
HTML/CSS :掌握标签语义化、Flex/Grid布局、响应式设计(如
@media查询)。 -
JavaScript :重点学习ES6+特性(箭头函数、解构赋值、
let/const、模块化import/export)、异步编程(Promise、async/await)、数组方法(map、filter、reduce)。 -
TypeScript (推荐):静态类型检查,提升代码质量(如接口
interface、类型推断)。
2. 开发工具
-
包管理器 :使用
pnpm(更快、更节省空间),替代npm/yarn。 -
代码编辑器 :VS Code(推荐插件:
ESLint、Prettier、React Developer Tools)。 -
版本控制 :Git(掌握
clone、add、commit、push、pull等基础命令)。
二、环境搭建:Vite + React + TypeScript
2025年主流脚手架,冷启动快、配置简单,适合新手。
1. 创建项目
# 使用pnpm(需先安装pnpm:npm install -g pnpm)
pnpm create vite@latest my-react-app -- --template react-ts
cd my-react-app
pnpm install
2. 启动项目
pnpm dev
打开http://localhost:5173,看到默认页面即搭建成功。
3. 项目结构说明
my-react-app/
├─ src/
│ ├─ app/ # 应用级入口(路由、状态管理)
│ ├─ components/ # 通用组件(如按钮、输入框)
│ ├─ features/ # 业务特性模块(如用户管理、商品列表)
│ ├─ pages/ # 页面组件(如首页、详情页)
│ ├─ hooks/ # 自定义Hooks(如数据请求、表单处理)
│ ├─ assets/ # 静态资源(图片、字体)
│ ├─ styles/ # 全局样式(如Tailwind配置)
│ ├─ lib/ # 工具库(如API请求封装)
│ ├─ test/ # 测试文件(如Jest)
│ ├─ main.tsx # 入口文件
│ └─ vite-env.d.ts # TypeScript类型声明
├─ .editorconfig # 编辑器配置(如缩进、换行)
├─ .eslintrc.cjs # ESLint代码规范(如禁止未使用变量)
├─ .prettierrc # Prettier代码格式化(如单引号、分号)
├─ index.html # 主页面
└─ package.json # 项目依赖与脚本
三、核心概念:React 基础
1. 组件(Component)
React 应用的核心,将 UI 拆分为可复用的模块(如按钮、表单)。
-
函数组件(推荐):用函数定义,返回 JSX(JavaScript + HTML)。
// src/components/Button.tsx import React from 'react'; // 定义Props接口(类型约束) interface ButtonProps { children: React.ReactNode; // 子元素(必选) onClick: () => void; // 点击事件(必选) variant?: 'primary' | 'secondary'; // 可选属性(按钮样式) } // 使用React.FC泛型定义组件 const Button: React.FC<ButtonProps> = ({ children, onClick, variant = 'primary' }) => { return ( <button className={`btn-${variant}`} // 动态类名(如btn-primary) onClick={onClick} > {children} // 子元素(如"提交") </button> ); }; export default Button;
2. JSX 语法
JavaScript 的扩展语法,用于描述 UI 结构(类似 HTML,但有区别)。
-
规则:
-
用
className替代 HTML 的class(避免与 JavaScript 关键字冲突)。 -
单标签必须闭合(如
<img />、<input />)。 -
表达式用
{}嵌入(如{count}、{new Date().toLocaleString()})。
-
-
示例:
// src/App.tsx import React, { useState } from 'react'; import Button from './components/Button'; const App: React.FC = () => { const [count, setCount] = useState<number>(0); // 状态管理(useState Hook) // 事件处理函数(驼峰式命名) const handleClick = () => { setCount(count + 1); // 更新状态(触发重新渲染) }; return ( <div className="app"> <h1>Hello, React!</h1> <p>当前计数:{count}</p> <Button onClick={handleClick}>点击增加</Button> {/* 使用自定义组件 */} </div> ); }; export default App;
3. 状态管理(State)
组件内部的数据,变化时触发重新渲染。
-
useState Hook(推荐):管理组件状态(如计数、输入框内容)。
const [count, setCount] = useState<number>(0); // 初始化状态(类型为number) -
useReducer Hook(复杂状态):用于多状态关联场景(如购物车、表单)。
// src/hooks/useCart.ts import { useReducer, Reducer } from 'react'; // 购物车状态类型 interface CartItem { id: number; name: string; price: number; quantity: number; } interface CartState { items: CartItem[]; } // 动作类型 type CartAction = | { type: 'add'; item: Omit<CartItem, 'quantity'> } | { type: 'remove'; id: number } | { type: 'updateQuantity'; id: number; quantity: number }; // Reducer函数(处理状态变化) const cartReducer: Reducer<CartState, CartAction> = (state, action) => { switch (action.type) { case 'add': // 检查是否已存在该商品 const existingItem = state.items.find(item => item.id === action.item.id); if (existingItem) { return { items: state.items.map(item => item.id === action.item.id ? { ...item, quantity: item.quantity + 1 } : item ) }; } else { return { items: [...state.items, { ...action.item, quantity: 1 }] }; } case 'remove': return { items: state.items.filter(item => item.id !== action.id) }; case 'updateQuantity': return { items: state.items.map(item => item.id === action.id ? { ...item, quantity: action.quantity } : item ) }; default: return state; } }; // 自定义Hook(封装购物车逻辑) const useCart = () => { const [state, dispatch] = useReducer(cartReducer, { items: [] }); // 添加商品 const addItem = (item: Omit<CartItem, 'quantity'>) => { dispatch({ type: 'add', item }); }; // 移除商品 const removeItem = (id: number) => { dispatch({ type: 'remove', id }); }; // 更新商品数量 const updateQuantity = (id: number, quantity: number) => { dispatch({ type: 'updateQuantity', id, quantity }); }; return { cart: state, addItem, removeItem, updateQuantity }; }; export default useCart;
4. 事件处理
React 中的事件采用驼峰式命名 (如onClick、onChange),事件处理函数需绑定this(函数组件无需考虑)。
-
示例:
// 输入框变化事件 const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); // 获取输入框值 }; // 按钮点击事件 const handleClick = () => { console.log('按钮被点击'); }; // 在JSX中使用 <input type="text" onChange={handleInputChange} /> <button onClick={handleClick}>点击</button>
5. 条件渲染与列表渲染
-
条件渲染 :用
if/else、三元表达式(condition ? true : false)、逻辑与(&&)控制 UI 显示。// 三元表达式 {isLoggedIn ? <div>欢迎回来!</div> : <div>请登录</div>} // 逻辑与(仅当条件为真时渲染) {count > 0 && <div>计数大于0</div>} -
列表渲染 :用
map遍历数组生成组件,必须添加key属性(唯一标识,提升性能)。// 商品列表 const products = [ { id: 1, name: '商品1', price: 100 }, { id: 2, name: '商品2', price: 200 }, { id: 3, name: '商品3', price: 300 } ]; // 渲染列表 <ul> {products.map(product => ( <li key={product.id}> {product.name} - ¥{product.price} </li> ))} </ul>
四、实战项目:Todo List(待办事项)
通过 Todo List 项目,巩固组件、状态管理、事件处理等核心概念。
1. 项目需求
-
添加待办事项(输入框 + 按钮)。
-
显示待办事项列表(可标记完成/未完成)。
-
删除待办事项。
-
统计未完成数量。
2. 项目结构
src/
├─ components/
│ ├─ TodoInput.tsx # 输入框组件
│ ├─ TodoList.tsx # 列表组件
│ └─ TodoItem.tsx # 列表项组件
├─ hooks/
│ └── useTodo.ts # Todo 状态管理 Hook
├─ App.tsx # 主组件
└─ main.tsx # 入口文件
3. 代码实现
(1)自定义 Hook:useTodo(管理 Todo 状态)
// src/hooks/useTodo.ts
import { useState, useCallback } from 'react';
// Todo 项类型
interface TodoItem {
id: number;
text: string;
completed: boolean;
}
// useTodo Hook
const useTodo = () => {
const [todos, setTodos] = useState<TodoItem[]>([]); // Todo 列表
const [inputValue, setInputValue] = useState<string>(''); // 输入框值
// 添加 Todo
const addTodo = useCallback(() => {
if (inputValue.trim()) {
const newTodo: TodoItem = {
id: Date.now(), // 用时间戳作为唯一 ID
text: inputValue.trim(),
completed: false
};
setTodos(prev => [...prev, newTodo]); // 添加到列表
setInputValue(''); // 清空输入框
}
}, [inputValue]);
// 切换 Todo 完成状态
const toggleTodo = useCallback((id: number) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
// 删除 Todo
const deleteTodo = useCallback((id: number) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
// 统计未完成数量
const uncompletedCount = todos.filter(todo => !todo.completed).length;
return {
todos,
inputValue,
setInputValue,
addTodo,
toggleTodo,
deleteTodo,
uncompletedCount
};
};
export default useTodo;
(2)输入框组件:TodoInput
// src/components/TodoInput.tsx
import React from 'react';
// Props 类型
interface TodoInputProps {
inputValue: string;
setInputValue: React.Dispatch<React.SetStateAction<string>>;
addTodo: () => void;
}
const TodoInput: React.FC<TodoInputProps> = ({ inputValue, setInputValue, addTodo }) => {
// 处理键盘事件(回车添加)
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
addTodo();
}
};
return (
<div className="todo-input">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="请输入待办事项"
/>
<button onClick={addTodo}>添加</button>
</div>
);
};
export default TodoInput;
(3)列表项组件:TodoItem
// src/components/TodoItem.tsx
import React from 'react';
// Props 类型
interface TodoItemProps {
todo: {
id: number;
text: string;
completed: boolean;
};
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
}
const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleTodo, deleteTodo }) => {
return (
<li className="todo-item">
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span
className={todo.completed ? 'completed' : ''}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
);
};
export default TodoItem;
(4)列表组件:TodoList
// src/components/TodoList.tsx
import React from 'react';
import TodoItem from './TodoItem';
// Props 类型
interface TodoListProps {
todos: {
id: number;
text: string;
completed: boolean;
}[];
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
uncompletedCount: number;
}
const TodoList: React.FC<TodoListProps> = ({ todos, toggleTodo, deleteTodo, uncompletedCount }) => {
return (
<div className="todo-list">
<h2>待办事项({uncompletedCount} 项未完成)</h2>
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
))}
</ul>
</div>
);
};
export default TodoList;
(5)主组件:App
// src/App.tsx
import React from 'react';
import useTodo from './hooks/useTodo';
import TodoInput from './components/TodoInput';
import TodoList from './components/TodoList';
import './App.css'; // 样式文件
const App: React.FC = () => {
const {
inputValue,
setInputValue,
addTodo,
todos,
toggleTodo,
deleteTodo,
uncompletedCount
} = useTodo();
return (
<div className="app">
<h1>Todo List</h1>
<TodoInput
inputValue={inputValue}
setInputValue={setInputValue}
addTodo={addTodo}
/>
<TodoList
todos={todos}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
uncompletedCount={uncompletedCount}
/>
</div>
);
};
export default App;
(6)样式文件:App.css
/* src/App.css */
.app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.todo-input {
display: flex;
margin-bottom: 20px;
}
.todo-input input {
flex: 1;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px 0 0 4px;
}
.todo-input button {
padding: 10px 20px;
font-size: 16px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.todo-list h2 {
margin-bottom: 10px;
}
.todo-list ul {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-item input[type="checkbox"] {
margin-right: 10px;
}
.todo-item span {
flex: 1;
font-size: 16px;
}
.todo-item button {
padding: 5px 10px;
font-size: 14px;
background-color: #dc3545;
color: #fff;
border: none;
border-radius: 4
[](@replace=0)