React 快速上手
概述
本章将带您快速创建第一个 React 应用,了解 React 的基本工作流程。我们将从创建项目开始,逐步了解 React 应用的核心概念,包括组件、JSX、状态管理等。
🚀 创建第一个 React 应用
项目初始化
bash
# 创建新项目
npx create-react-app my-first-react-app
# 进入项目目录
cd my-first-react-app
# 启动开发服务器
npm start项目结构分析
my-first-react-app/
├── public/ # 静态资源目录
│ ├── index.html # HTML 模板
│ ├── favicon.ico # 网站图标
│ └── manifest.json # PWA 配置
├── src/ # 源代码目录
│ ├── App.js # 主应用组件
│ ├── App.css # 应用样式
│ ├── index.js # 应用入口点
│ ├── index.css # 全局样式
│ └── App.test.js # 测试文件
├── package.json # 依赖配置
└── README.md # 项目说明📝 理解核心文件
index.html - HTML 模板
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>我的第一个 React 应用</title>
</head>
<body>
<noscript>您需要启用 JavaScript 才能运行此应用。</noscript>
<!-- React 应用将挂载到这个元素 -->
<div id="root"></div>
<!--
这个 HTML 文件是模板。
如果打开浏览器直接查看,会看到一个空页面。
可以添加字体、meta 标签或 analytics 到这个文件。
构建步骤会将打包后的脚本放入 <body> 标签。
-->
</body>
</html>index.js - 应用入口点
jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染应用到根元素
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// React.StrictMode 的作用:
// 1. 检测不安全的生命周期
// 2. 关于使用过时字符串 ref API 的警告
// 3. 关于使用废弃的 findDOMNode 方法的警告
// 4. 检测意外的副作用
// 5. 检测过时的 context APIApp.js - 主应用组件
jsx
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>欢迎来到 React 世界!</h1>
<p>这是您的第一个 React 应用</p>
<button onClick={() => alert('Hello React!')}>
点击我
</button>
</header>
</div>
);
}
export default App;🎯 第一个交互式组件
创建计数器组件
jsx
import React, { useState } from 'react';
import './App.css';
function Counter() {
// 使用 useState Hook 管理状态
const [count, setCount] = useState(0);
// 事件处理函数
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
const reset = () => {
setCount(0);
};
return (
<div className="counter">
<h2>计数器应用</h2>
<div className="counter-display">
当前计数: <span className="count-number">{count}</span>
</div>
<div className="counter-buttons">
<button onClick={decrement} className="btn btn-red">
-1
</button>
<button onClick={reset} className="btn btn-gray">
重置
</button>
<button onClick={increment} className="btn btn-green">
+1
</button>
</div>
</div>
);
}
function App() {
return (
<div className="App">
<header className="App-header">
<h1>我的第一个 React 应用</h1>
<Counter />
</header>
</div>
);
}
export default App;添加样式
css
/* App.css */
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
padding: 20px;
color: white;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.counter {
background-color: white;
color: #282c34;
padding: 30px;
border-radius: 10px;
margin: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.counter-display {
font-size: 24px;
margin: 20px 0;
}
.count-number {
font-weight: bold;
color: #61dafb;
font-size: 32px;
}
.counter-buttons {
display: flex;
gap: 10px;
justify-content: center;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.btn-red {
background-color: #ff6b6b;
color: white;
}
.btn-gray {
background-color: #6c757d;
color: white;
}
.btn-green {
background-color: #51cf66;
color: white;
}🔧 添加更多功能
待办事项组件
jsx
import React, { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
// 添加待办事项
const addTodo = () => {
if (inputValue.trim()) {
const newTodo = {
id: Date.now(),
text: inputValue,
completed: false
};
setTodos([...todos, newTodo]);
setInputValue('');
}
};
// 切换完成状态
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
// 删除待办事项
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// 处理键盘事件
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
addTodo();
}
};
return (
<div className="todo-app">
<h2>待办事项</h2>
{/* 输入区域 */}
<div className="todo-input">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="输入待办事项..."
className="todo-input-field"
/>
<button onClick={addTodo} className="btn btn-blue">
添加
</button>
</div>
{/* 待办事项列表 */}
<div className="todo-list">
{todos.length === 0 ? (
<p className="no-todos">暂无待办事项</p>
) : (
todos.map(todo => (
<div
key={todo.id}
className={`todo-item ${todo.completed ? 'completed' : ''}`}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span className="todo-text">{todo.text}</span>
<button
onClick={() => deleteTodo(todo.id)}
className="btn btn-small btn-red"
>
删除
</button>
</div>
))
)}
</div>
{/* 统计信息 */}
<div className="todo-stats">
<p>总计: {todos.length} | 已完成: {todos.filter(t => t.completed).length}</p>
</div>
</div>
);
}
// 更新 App 组件
function App() {
const [currentView, setCurrentView] = useState('counter');
return (
<div className="App">
<header className="App-header">
<h1>我的 React 应用集合</h1>
{/* 导航菜单 */}
<nav className="app-nav">
<button
onClick={() => setCurrentView('counter')}
className={`nav-btn ${currentView === 'counter' ? 'active' : ''}`}
>
计数器
</button>
<button
onClick={() => setCurrentView('todo')}
className={`nav-btn ${currentView === 'todo' ? 'active' : ''}`}
>
待办事项
</button>
</nav>
{/* 根据当前视图显示组件 */}
{currentView === 'counter' && <Counter />}
{currentView === 'todo' && <TodoApp />}
</header>
</div>
);
}
export default App;完整样式
css
/* 添加到 App.css */
/* 导航样式 */
.app-nav {
margin-bottom: 30px;
}
.nav-btn {
padding: 10px 20px;
margin: 0 10px;
border: 2px solid #61dafb;
background-color: transparent;
color: #61dafb;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
.nav-btn:hover,
.nav-btn.active {
background-color: #61dafb;
color: #282c34;
}
/* 待办事项样式 */
.todo-app {
background-color: white;
color: #282c34;
padding: 30px;
border-radius: 10px;
margin: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 500px;
}
.todo-input {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.todo-input-field {
flex: 1;
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
.todo-input-field:focus {
outline: none;
border-color: #61dafb;
}
.btn-blue {
background-color: #61dafb;
color: #282c34;
}
.todo-list {
min-height: 200px;
}
.todo-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
transition: background-color 0.2s;
}
.todo-item:hover {
background-color: #f8f9fa;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #6c757d;
}
.todo-text {
flex: 1;
text-align: left;
}
.btn-small {
padding: 5px 10px;
font-size: 12px;
}
.no-todos {
text-align: center;
color: #6c757d;
font-style: italic;
padding: 40px;
}
.todo-stats {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 14px;
color: #6c757d;
}🎨 理解 React 核心概念
1. 组件(Components)
jsx
// 函数组件 - 推荐方式
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// 类组件 - 传统方式
class GreetingClass extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}2. JSX 语法
jsx
function ExampleComponent() {
const user = { name: '张三', age: 25 };
const isLoggedIn = true;
return (
<div>
{/* JavaScript 表达式 */}
<h1>用户名: {user.name}</h1>
<p>年龄: {user.age}</p>
{/* 条件渲染 */}
{isLoggedIn ? (
<p>欢迎回来!</p>
) : (
<p>请先登录</p>
)}
{/* 列表渲染 */}
<ul>
{['苹果', '香蕉', '橙子'].map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
</div>
);
}3. 状态管理(State)
jsx
import { useState } from 'react';
function StateExample() {
// 简单状态
const [count, setCount] = useState(0);
// 对象状态
const [user, setUser] = useState({
name: '',
email: ''
});
// 数组状态
const [items, setItems] = useState([]);
const updateUserName = (newName) => {
setUser(prevUser => ({
...prevUser,
name: newName
}));
};
const addItem = (item) => {
setItems(prevItems => [...prevItems, item]);
};
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}4. 事件处理
jsx
function EventExample() {
const [inputValue, setInputValue] = useState('');
// 处理输入事件
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
// 处理表单提交
const handleSubmit = (event) => {
event.preventDefault();
console.log('提交的值:', inputValue);
};
// 处理键盘事件
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
console.log('按下了回车键');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="输入文本..."
/>
<button type="submit">提交</button>
</form>
);
}🔍 开发者工具使用
浏览器控制台调试
jsx
function DebugExample() {
const [data, setData] = useState(null);
const fetchData = async () => {
try {
console.log('开始获取数据...');
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const result = await response.json();
console.log('获取到的数据:', result);
setData(result);
} catch (error) {
console.error('获取数据失败:', error);
}
};
return (
<div>
<button onClick={fetchData}>获取数据</button>
{data && (
<div>
<h3>{data.title}</h3>
<p>{data.body}</p>
</div>
)}
</div>
);
}React Developer Tools
jsx
// 为组件添加 displayName 便于调试
function UserCard({ user }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
UserCard.displayName = 'UserCard';
// 使用 React.memo 优化性能
const OptimizedUserCard = React.memo(UserCard);🏃♂️ 实践练习
练习1:个人资料卡片
jsx
function ProfileCard() {
const [profile, setProfile] = useState({
name: '张三',
title: '前端开发工程师',
bio: '热爱编程,专注于React开发',
avatar: 'https://via.placeholder.com/150'
});
const [isEditing, setIsEditing] = useState(false);
return (
<div className="profile-card">
{isEditing ? (
<div>
<input
value={profile.name}
onChange={(e) => setProfile({...profile, name: e.target.value})}
/>
<input
value={profile.title}
onChange={(e) => setProfile({...profile, title: e.target.value})}
/>
<textarea
value={profile.bio}
onChange={(e) => setProfile({...profile, bio: e.target.value})}
/>
<button onClick={() => setIsEditing(false)}>保存</button>
</div>
) : (
<div>
<img src={profile.avatar} alt="Avatar" />
<h2>{profile.name}</h2>
<h3>{profile.title}</h3>
<p>{profile.bio}</p>
<button onClick={() => setIsEditing(true)}>编辑</button>
</div>
)}
</div>
);
}练习2:简单计算器
jsx
function Calculator() {
const [display, setDisplay] = useState('0');
const [previousValue, setPreviousValue] = useState(null);
const [operation, setOperation] = useState(null);
const [waitingForNewValue, setWaitingForNewValue] = useState(false);
const inputNumber = (num) => {
if (waitingForNewValue) {
setDisplay(String(num));
setWaitingForNewValue(false);
} else {
setDisplay(display === '0' ? String(num) : display + num);
}
};
const inputOperation = (nextOperation) => {
const inputValue = parseFloat(display);
if (previousValue === null) {
setPreviousValue(inputValue);
} else if (operation) {
const currentValue = previousValue || 0;
const newValue = calculate(currentValue, inputValue, operation);
setDisplay(String(newValue));
setPreviousValue(newValue);
}
setWaitingForNewValue(true);
setOperation(nextOperation);
};
const calculate = (firstValue, secondValue, operation) => {
switch (operation) {
case '+': return firstValue + secondValue;
case '-': return firstValue - secondValue;
case '×': return firstValue * secondValue;
case '÷': return firstValue / secondValue;
case '=': return secondValue;
default: return secondValue;
}
};
const performCalculation = () => {
inputOperation('=');
};
const clear = () => {
setDisplay('0');
setPreviousValue(null);
setOperation(null);
setWaitingForNewValue(false);
};
return (
<div className="calculator">
<div className="display">{display}</div>
<div className="buttons">
<button onClick={clear}>AC</button>
<button onClick={() => inputOperation('÷')}>÷</button>
<button onClick={() => inputOperation('×')}>×</button>
<button onClick={() => inputOperation('-')}>-</button>
<button onClick={() => inputNumber(7)}>7</button>
<button onClick={() => inputNumber(8)}>8</button>
<button onClick={() => inputNumber(9)}>9</button>
<button onClick={() => inputOperation('+')}>+</button>
<button onClick={() => inputNumber(4)}>4</button>
<button onClick={() => inputNumber(5)}>5</button>
<button onClick={() => inputNumber(6)}>6</button>
<button onClick={() => inputNumber(1)}>1</button>
<button onClick={() => inputNumber(2)}>2</button>
<button onClick={() => inputNumber(3)}>3</button>
<button onClick={() => inputNumber(0)}>0</button>
<button onClick={performCalculation}>=</button>
</div>
</div>
);
}📝 本章小结
通过本章的快速上手学习,你应该掌握了:
关键概念
- ✅ React 项目的基本结构
- ✅ 组件的创建和使用
- ✅ JSX 语法基础
- ✅ 状态管理基础
- ✅ 事件处理机制
- ✅ 基本的调试技巧
实践技能
- ✅ 创建交互式组件
- ✅ 处理用户输入
- ✅ 管理应用状态
- ✅ 样式和布局
- ✅ 条件渲染和列表渲染
开发流程
- 创建项目:使用 Create React App
- 编写组件:函数组件 + Hooks
- 添加样式:CSS 样式
- 测试功能:浏览器调试
- 迭代开发:持续改进
下一步学习
- 深入了解 React 项目结构
- 学习更多 Hooks 的使用
- 掌握组件间通信
- 了解路由和状态管理
继续学习:下一章 - React 项目说明