React 入门教程:构建现代用户界面
15 min read
#React#JavaScript#前端框架#Hooks#组件
React 入门教程:构建现代用户界面
React 是由 Facebook(现 Meta)开发的用于构建用户界面的 JavaScript 库。它采用组件化开发模式,让构建复杂的交互式 UI 变得简单高效。
为什么选择 React?
- 组件化:将 UI 拆分为独立、可复用的组件
- 声明式:描述 UI 应该是什么样子,React 负责更新
- 虚拟 DOM:高效的 DOM 更新机制,提升性能
- 单向数据流:数据流向清晰,易于调试
- 丰富的生态系统:大量第三方库和工具
- 强大的社区:活跃的开发者社区,资源丰富
- 跨平台:React Native 支持移动端开发
环境准备
安装 Node.js
React 开发需要 Node.js 环境(推荐 18.0+)。
# 检查 Node.js 版本
node --version
# 检查 npm 版本
npm --version
创建 React 应用
使用 Create React App(CRA)快速创建项目:
# 使用 npx(推荐)
npx create-react-app my-app
# 使用 TypeScript 模板
npx create-react-app my-app --template typescript
# 进入项目目录
cd my-app
# 启动开发服务器
npm start
使用 Vite(更快的构建工具):
# 创建 Vite + React 项目
npm create vite@latest my-app -- --template react
# 或使用 TypeScript
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
JSX 基础
JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的代码。
// 基本 JSX
const element = <h1>Hello, World!</h1>;
// JSX 中嵌入表达式
const name = "Alice";
const element2 = <h1>Hello, {name}!</h1>;
// JSX 中使用 JavaScript 表达式
const element3 = <h1>2 + 2 = {2 + 2}</h1>;
// JSX 属性
const element4 = <img src="logo.png" alt="Logo" />;
// 使用驼峰命名
const element5 = <div className="container" onClick={handleClick}></div>;
// JSX 子元素
const element6 = (
<div>
<h1>标题</h1>
<p>段落内容</p>
</div>
);
// 条件渲染
const isLoggedIn = true;
const element7 = (
<div>
{isLoggedIn ? <p>欢迎回来!</p> : <p>请登录</p>}
</div>
);
// 列表渲染
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => (
<li key={number}>{number}</li>
));
// 注释
const element8 = (
<div>
{/* 这是注释 */}
<h1>标题</h1>
</div>
);
组件
1. 函数组件
函数组件是最简单的组件形式(推荐使用):
// 基本函数组件
function Welcome() {
return <h1>Hello, World!</h1>;
}
// 箭头函数组件
const Greeting = () => {
return <h1>Hello, React!</h1>;
};
// 带 props 的组件
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用解构
function Welcome({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
</div>
);
}
// 使用组件
function App() {
return (
<div>
<Welcome name="Alice" age={25} />
<Welcome name="Bob" age={30} />
</div>
);
}
2. 类组件(旧式,了解即可)
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
// 带状态的类组件
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
React Hooks
Hooks 是 React 16.8 引入的新特性,让函数组件也能使用状态和其他 React 特性。
1. useState - 状态管理
import { useState } from 'react';
function Counter() {
// 声明状态变量
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// 多个状态
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
return (
<form>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
placeholder="Age"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
</form>
);
}
// 对象状态
function UserProfile() {
const [user, setUser] = useState({
name: 'Alice',
age: 25,
email: 'alice@example.com'
});
const updateName = (newName) => {
setUser({ ...user, name: newName });
};
return (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>Email: {user.email}</p>
<button onClick={() => updateName('Bob')}>Change Name</button>
</div>
);
}
// 数组状态
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input }]);
setInput('');
}
};
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
2. useEffect - 副作用处理
import { useState, useEffect } from 'react';
// 基本用法
function Example() {
const [count, setCount] = useState(0);
// 每次渲染后执行
useEffect(() => {
document.title = `Count: ${count}`;
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// 仅在挂载时执行(空依赖数组)
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []); // 空数组表示仅在挂载时执行
if (loading) return <p>Loading...</p>;
return <div>{JSON.stringify(data)}</div>;
}
// 依赖特定值
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
if (query) {
fetch(`https://api.example.com/search?q=${query}`)
.then(response => response.json())
.then(data => setResults(data));
}
}, [query]); // 当 query 变化时执行
return (
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
);
}
// 清理副作用
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// 清理函数
return () => {
clearInterval(interval);
};
}, []);
return <p>Seconds: {seconds}</p>;
}
// 订阅和取消订阅
function ChatRoom({ roomId }) {
useEffect(() => {
// 订阅
const subscription = subscribeToRoom(roomId);
// 清理:取消订阅
return () => {
subscription.unsubscribe();
};
}, [roomId]);
return <div>Chat Room: {roomId}</div>;
}
3. useContext - 上下文
import { createContext, useContext, useState } from 'react';
// 创建上下文
const ThemeContext = createContext();
// 提供者组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 使用上下文
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff'
}}
>
Toggle Theme (Current: {theme})
</button>
);
}
// 应用
function App() {
return (
<ThemeProvider>
<div>
<h1>My App</h1>
<ThemedButton />
</div>
</ThemeProvider>
);
}
4. useRef - 引用
import { useRef, useEffect } from 'react';
// 访问 DOM 元素
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
// 保存可变值(不触发重新渲染)
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
5. useMemo 和 useCallback - 性能优化
import { useState, useMemo, useCallback } from 'react';
// useMemo - 缓存计算结果
function ExpensiveComponent({ numbers }) {
const sum = useMemo(() => {
console.log('Calculating sum...');
return numbers.reduce((a, b) => a + b, 0);
}, [numbers]); // 仅当 numbers 变化时重新计算
return <p>Sum: {sum}</p>;
}
// useCallback - 缓存函数
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
// 缓存函数,避免子组件不必要的重新渲染
const addTodo = useCallback(() => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input }]);
setInput('');
}
}, [input, todos]);
const removeTodo = useCallback((id) => {
setTodos(todos => todos.filter(todo => todo.id !== id));
}, []);
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button onClick={addTodo}>Add</button>
<TodoItems todos={todos} onRemove={removeTodo} />
</div>
);
}
6. 自定义 Hook
import { useState, useEffect } from 'react';
// 自定义 Hook:获取窗口尺寸
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 使用自定义 Hook
function App() {
const { width, height } = useWindowSize();
return (
<div>
<p>Window size: {width} x {height}</p>
</div>
);
}
// 自定义 Hook:数据获取
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// 使用
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <div>{data.name}</div>;
}
事件处理
// 基本事件处理
function Button() {
const handleClick = () => {
alert('Button clicked!');
};
return <button onClick={handleClick}>Click Me</button>;
}
// 传递参数
function ItemList() {
const handleClick = (id) => {
console.log(`Item ${id} clicked`);
};
return (
<div>
<button onClick={() => handleClick(1)}>Item 1</button>
<button onClick={() => handleClick(2)}>Item 2</button>
</div>
);
}
// 事件对象
function Form() {
const handleSubmit = (e) => {
e.preventDefault(); // 阻止默认行为
console.log('Form submitted');
};
const handleChange = (e) => {
console.log('Input value:', e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
}
// 常见事件
function EventExamples() {
return (
<div>
<button onClick={() => console.log('Click')}>Click</button>
<button onDoubleClick={() => console.log('Double Click')}>
Double Click
</button>
<input onFocus={() => console.log('Focus')} />
<input onBlur={() => console.log('Blur')} />
<input onChange={(e) => console.log(e.target.value)} />
<div onMouseEnter={() => console.log('Mouse Enter')}>
Hover me
</div>
<form onSubmit={(e) => e.preventDefault()}>
<button type="submit">Submit</button>
</form>
</div>
);
}
条件渲染
// if-else
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in.</h1>;
}
// 三元运算符
function LoginButton({ isLoggedIn }) {
return (
<button>
{isLoggedIn ? 'Logout' : 'Login'}
</button>
);
}
// 逻辑与运算符
function Mailbox({ unreadMessages }) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && (
<h2>You have {unreadMessages.length} unread messages.</h2>
)}
</div>
);
}
// 阻止渲染
function WarningBanner({ warn }) {
if (!warn) {
return null;
}
return <div className="warning">Warning!</div>;
}
列表渲染
// 基本列表
function NumberList() {
const numbers = [1, 2, 3, 4, 5];
return (
<ul>
{numbers.map((number) => (
<li key={number}>{number}</li>
))}
</ul>
);
}
// 对象列表
function UserList() {
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
];
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.age} years old
</li>
))}
</ul>
);
}
// 提取组件
function User({ user }) {
return (
<li>
<h3>{user.name}</h3>
<p>Age: {user.age}</p>
</li>
);
}
function UserList2() {
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 }
];
return (
<ul>
{users.map((user) => (
<User key={user.id} user={user} />
))}
</ul>
);
}
表单处理
import { useState } from 'react';
// 受控组件
function ControlledForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
gender: 'male',
agree: false
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form data:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Username"
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
/>
<select name="gender" value={formData.gender} onChange={handleChange}>
<option value="male">Male</option>
<option value="female">Female</option>
</select>
<label>
<input
type="checkbox"
name="agree"
checked={formData.agree}
onChange={handleChange}
/>
I agree to terms
</label>
<button type="submit">Submit</button>
</form>
);
}
实战示例:Todo 应用
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const [filter, setFilter] = useState('all'); // all, active, completed
const addTodo = () => {
if (input.trim()) {
setTodos([
...todos,
{
id: Date.now(),
text: input,
completed: false
}
]);
setInput('');
}
};
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 clearCompleted = () => {
setTodos(todos.filter((todo) => !todo.completed));
};
const filteredTodos = todos.filter((todo) => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
const activeCount = todos.filter((todo) => !todo.completed).length;
return (
<div className="todo-app">
<h1>Todo List</h1>
<div className="input-section">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="What needs to be done?"
/>
<button onClick={addTodo}>Add</button>
</div>
<ul className="todo-list">
{filteredTodos.map((todo) => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
<div className="footer">
<span>{activeCount} items left</span>
<div className="filters">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
All
</button>
<button
className={filter === 'active' ? 'active' : ''}
onClick={() => setFilter('active')}
>
Active
</button>
<button
className={filter === 'completed' ? 'active' : ''}
onClick={() => setFilter('completed')}
>
Completed
</button>
</div>
<button onClick={clearCompleted}>Clear completed</button>
</div>
</div>
);
}
export default TodoApp;
最佳实践
- 使用函数组件和 Hooks:现代 React 推荐方式
- 保持组件小而专注:单一职责原则
- 提取可复用组件:DRY 原则
- 使用 PropTypes 或 TypeScript:类型检查
- 避免直接修改状态:使用不可变更新
- 合理使用 useEffect:避免无限循环
- 使用 key 属性:列表渲染时必须
- 性能优化:使用 useMemo、useCallback、React.memo
- 代码分割:使用 React.lazy 和 Suspense
- 遵循命名约定:组件大写,事件处理器 handle 前缀
下一步学习
- React Router:单页应用路由
- 状态管理:Redux、Zustand、Jotai
- 样式方案:CSS Modules、Styled Components、Tailwind CSS
- 表单库:React Hook Form、Formik
- 数据获取:React Query、SWR
- 测试:Jest、React Testing Library
- Next.js:React 全栈框架
- React Native:移动端开发
总结
通过本教程,你已经掌握了 React 的核心知识:
- ✅ JSX 语法
- ✅ 组件化开发
- ✅ React Hooks(useState、useEffect、useContext 等)
- ✅ 事件处理
- ✅ 条件渲染和列表渲染
- ✅ 表单处理
- ✅ 实战项目开发
继续实践和探索,你将能够构建出强大的 React 应用!