React 快速入门到精通教程:从零基础到能写项目
React 官方把它定义为:用 JavaScript 构建用户界面的库 ,核心思想是把页面拆成一个个组件,再用数据驱动页面变化。React 官方快速入门也强调,日常开发中最常用的能力包括组件、JSX、条件渲染、列表渲染、事件、状态和组件间数据共享。(React)


文章目录
- [React 快速入门到精通教程:从零基础到能写项目](#React 快速入门到精通教程:从零基础到能写项目)
-
- [一、React 是什么?](#一、React 是什么?)
-
- [1. 专业解释](#1. 专业解释)
- [2. 大白话理解](#2. 大白话理解)
- [3. 生活案例类比](#3. 生活案例类比)
- [4. React 适合解决什么问题?](#4. React 适合解决什么问题?)
- [二、React 核心基础](#二、React 核心基础)
-
- [1. JSX](#1. JSX)
- [2. 组件](#2. 组件)
- [3. Props](#3. Props)
- [4. State](#4. State)
- [5. 事件处理](#5. 事件处理)
- [6. 条件渲染](#6. 条件渲染)
- [7. 列表渲染](#7. 列表渲染)
- [8. 表单处理](#8. 表单处理)
- [三、Hooks 重点讲解](#三、Hooks 重点讲解)
- [四、实战项目 1:TodoList](#四、实战项目 1:TodoList)
- [五、实战项目 2:搜索过滤列表](#五、实战项目 2:搜索过滤列表)
- [六、实战项目 3:可复用 Modal 组件](#六、实战项目 3:可复用 Modal 组件)
- 七、组件封装教学
- 八、进阶内容
- [九、React 高频面试题](#九、React 高频面试题)
-
- [1. React 为什么需要虚拟 DOM?](#1. React 为什么需要虚拟 DOM?)
- [2. key 的作用是什么?](#2. key 的作用是什么?)
- [3. useEffect 执行时机?](#3. useEffect 执行时机?)
- [4. useMemo 和 useCallback 区别?](#4. useMemo 和 useCallback 区别?)
- [5. 受控组件和非受控组件?](#5. 受控组件和非受控组件?)
- [6. React 组件通信方式?](#6. React 组件通信方式?)
- [7. React 性能优化方案?](#7. React 性能优化方案?)
- [十、React 学习路线图](#十、React 学习路线图)
一、React 是什么?
1. 专业解释
React 是一个用于构建用户界面的 JavaScript 库。它通过 组件化、声明式渲染、状态驱动 UI 的方式,让开发者可以更高效地构建复杂前端应用。
2. 大白话理解
React 就像"搭积木"。
一个页面不是一次性写成一大坨 HTML,而是拆成:
txt
页面 = Header + Sidebar + Content + Footer
每一块都是组件,组件可以复用、组合、传数据。
3. 生活案例类比
做一套乐高房子:
txt
门 = 一个组件
窗户 = 一个组件
屋顶 = 一个组件
房间 = 多个组件组合
整栋房子 = 整个 React 应用
以后你想换窗户,不需要推倒整栋房子,只改窗户组件。
4. React 适合解决什么问题?
React 适合:
- 中后台管理系统
- 电商网站
- 社交应用
- 数据可视化平台
- 单页应用 SPA
- 组件复用很多的业务系统
React 官方"Thinking in React"也推荐先把 UI 拆成组件层级,再确定每个组件的状态与数据流。(React)
二、React 核心基础
1. JSX
概念解释
JSX 是 JavaScript 的语法扩展,看起来像 HTML,但本质上会被编译成 JavaScript。
大白话
JSX 就是"在 JS 里写页面结构"。
代码示例
jsx
function App() {
const name = "小明";
return (
<div>
<h1>Hello, {name}</h1>
<p>欢迎学习 React</p>
</div>
);
}
export default App;
逐行解释
jsx
function App() {
定义一个 React 函数组件。
jsx
const name = "小明";
定义普通 JavaScript 变量。
jsx
return (
组件返回 JSX。
jsx
<h1>Hello, {name}</h1>
用 {} 在 JSX 中插入 JS 表达式。
jsx
export default App;
导出组件。
常见错误
jsx
return (
<h1>标题</h1>
<p>内容</p>
);
错误:JSX 必须有一个根节点。
正确:
jsx
return (
<>
<h1>标题</h1>
<p>内容</p>
</>
);
面试追问
问:JSX 是 HTML 吗?
答:不是。JSX 是 JavaScript 的语法扩展,最终会被编译成 React 元素。
2. 组件
概念解释
组件是 React 应用的基本单位。React 应用就是由组件组成的树。
大白话
组件就是"页面零件"。
代码示例
jsx
function UserCard() {
return (
<div>
<h2>张三</h2>
<p>前端工程师</p>
</div>
);
}
function App() {
return (
<div>
<UserCard />
<UserCard />
</div>
);
}
export default App;
逐行解释
jsx
function UserCard() {
定义用户卡片组件。
jsx
<UserCard />
在 App 组件中使用 UserCard。
常见错误
组件名小写:
jsx
function userCard() {}
React 会把小写标签当成原生 HTML 标签。
正确:
jsx
function UserCard() {}
面试追问
问:React 为什么推荐组件化?
答:为了复用、拆分复杂度、降低维护成本。
3. Props
概念解释
Props 是父组件传给子组件的数据。
大白话
Props 就像"外卖订单备注":父组件告诉子组件该显示什么。
代码示例
jsx
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.job}</p>
</div>
);
}
function App() {
return (
<div>
<UserCard name="张三" job="前端工程师" />
<UserCard name="李四" job="后端工程师" />
</div>
);
}
export default App;
逐行解释
jsx
function UserCard(props)
子组件接收 props。
jsx
props.name
读取父组件传入的 name。
jsx
<UserCard name="张三" job="前端工程师" />
父组件传值。
常见错误
直接修改 props:
jsx
props.name = "王五";
错误。Props 是只读的。
面试追问
问:Props 和 State 有什么区别?
答:Props 是外部传入的,State 是组件自己管理的。
4. State
概念解释
State 是组件内部状态。状态变化后,React 会重新渲染页面。
React 官方文档说明,State 可以让组件"记住"用户输入、选择等信息。(react.nodejs.cn)
大白话
State 就是组件自己的"小记事本"。
代码示例
jsx
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前数量:{count}</p>
<button onClick={() => setCount(count + 1)}>加 1</button>
</div>
);
}
export default Counter;
逐行解释
jsx
import { useState } from "react";
引入 useState。
jsx
const [count, setCount] = useState(0);
定义状态 count,初始值是 0。
jsx
setCount(count + 1)
更新状态。
常见错误
直接改 state:
jsx
count++;
错误。页面不会可靠更新。
正确:
jsx
setCount(count + 1);
面试追问
问:setState 是同步还是异步?
答:React 会批量处理状态更新,不应该依赖修改后的 state 立即同步可读。需要基于旧值更新时,用函数写法:
jsx
setCount(prev => prev + 1);
5. 事件处理
代码示例
jsx
function App() {
function handleClick() {
alert("按钮被点击了");
}
return <button onClick={handleClick}>点击我</button>;
}
export default App;
解释
jsx
onClick={handleClick}
React 中事件名使用驼峰命名。
常见错误
jsx
<button onClick={handleClick()}>点击</button>
这样会在页面渲染时立即执行。
正确:
jsx
<button onClick={handleClick}>点击</button>
面试追问
问:React 事件和原生 DOM 事件有什么区别?
答:React 使用合成事件,提供跨浏览器一致的事件行为。
6. 条件渲染
jsx
function App() {
const isLogin = true;
return (
<div>
{isLogin ? <h1>欢迎回来</h1> : <h1>请先登录</h1>}
</div>
);
}
export default App;
常见场景
- 登录 / 未登录
- 有数据 / 无数据
- 加载中 / 加载完成
常见错误
jsx
if (isLogin) {
return <h1>欢迎</h1>;
}
这可以用,但不能直接在 JSX 里面写 if 语句。
7. 列表渲染
jsx
function App() {
const users = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
];
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default App;
重点
key 必须稳定、唯一。
常见错误
jsx
<li key={index}>{user.name}</li>
如果列表会增删改,尽量不要用 index。
面试追问
问:key 的作用是什么?
答:帮助 React 判断哪些元素新增、删除、移动,从而提高更新效率并避免状态错乱。
8. 表单处理
jsx
import { useState } from "react";
function LoginForm() {
const [username, setUsername] = useState("");
function handleSubmit(e) {
e.preventDefault();
alert(`提交用户名:${username}`);
}
return (
<form onSubmit={handleSubmit}>
<input
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="请输入用户名"
/>
<button type="submit">提交</button>
</form>
);
}
export default LoginForm;
大白话
输入框的值交给 React 管,这叫受控组件。
面试追问
问:受控组件和非受控组件区别?
答:受控组件由 state 控制表单值;非受控组件通过 DOM 或 ref 获取值。
三、Hooks 重点讲解
Hooks 是 React 函数组件中使用状态、副作用、引用、缓存等能力的方式。React 官方文档也强调,Hooks 可以组合使用,并可以封装成自定义 Hook。(react.nodejs.cn)
1. useState
jsx
import { useState } from "react";
function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "已点赞" : "点赞"}
</button>
);
}
export default LikeButton;
场景
- 弹窗开关
- 表单输入
- Tab 切换
- 计数器
常见错误
jsx
setCount(count + 1);
setCount(count + 1);
可能只加一次。
正确:
jsx
setCount(prev => prev + 1);
setCount(prev => prev + 1);
面试追问
问:为什么 useState 返回数组?
答:方便开发者自定义变量名。
2. useEffect
概念解释
useEffect 用来处理副作用,比如请求接口、设置定时器、监听事件。
jsx
import { useEffect, useState } from "react";
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserList;
逐行解释
jsx
useEffect(() => {
组件渲染后执行副作用。
jsx
fetch(...)
请求接口。
jsx
}, []);
空依赖数组表示组件首次挂载后执行一次。
常见错误
忘记依赖项:
jsx
useEffect(() => {
console.log(keyword);
}, []);
如果 effect 依赖 keyword,应写:
jsx
useEffect(() => {
console.log(keyword);
}, [keyword]);
面试追问
问:useEffect 执行时机?
答:
txt
无依赖:每次渲染后执行
空数组:首次挂载后执行一次
有依赖:依赖变化后执行
return 函数:组件卸载或下次 effect 执行前清理
3. useRef
jsx
import { useRef } from "react";
function FocusInput() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} placeholder="请输入内容" />
<button onClick={handleFocus}>聚焦输入框</button>
</div>
);
}
export default FocusInput;
场景
- 获取 DOM
- 保存不会触发渲染的数据
- 定时器 ID
常见错误
以为 ref 改变会触发页面更新。不会。
面试追问
问:useRef 和 useState 区别?
答:useState 更新会触发渲染;useRef 更新不会触发渲染。
4. useMemo
jsx
import { useMemo, useState } from "react";
function ProductList() {
const [keyword, setKeyword] = useState("");
const products = ["苹果", "香蕉", "橙子", "西瓜"];
const filteredProducts = useMemo(() => {
return products.filter(item => item.includes(keyword));
}, [keyword]);
return (
<div>
<input value={keyword} onChange={e => setKeyword(e.target.value)} />
<ul>
{filteredProducts.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
export default ProductList;
大白话
useMemo 是"缓存计算结果"。
场景
- 大列表过滤
- 复杂计算
- 避免重复计算
常见错误
滥用 useMemo。简单计算不需要缓存。
5. useCallback
jsx
import { useCallback, useState } from "react";
function Child({ onClick }) {
return <button onClick={onClick}>子组件按钮</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("点击子组件");
}, []);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>加 1</button>
<Child onClick={handleClick} />
</div>
);
}
export default App;
大白话
useCallback 是"缓存函数"。
面试追问
问:useMemo 和 useCallback 区别?
答:
txt
useMemo 缓存计算结果
useCallback 缓存函数本身
6. 自定义 Hook
jsx
import { useEffect, useState } from "react";
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return width;
}
function App() {
const width = useWindowWidth();
return <h1>当前窗口宽度:{width}</h1>;
}
export default App;
场景
- 封装请求逻辑
- 封装权限逻辑
- 封装窗口监听
- 封装表单逻辑
面试追问
问:自定义 Hook 为什么必须以 use 开头?
答:方便 React 识别 Hook 调用规则,也方便 ESLint 检查。
四、实战项目 1:TodoList
完整代码
jsx
import { useState } from "react";
function TodoList() {
const [text, setText] = useState("");
const [todos, setTodos] = useState([]);
function addTodo() {
if (!text.trim()) return;
const newTodo = {
id: Date.now(),
title: text,
done: false,
};
setTodos([...todos, newTodo]);
setText("");
}
function toggleTodo(id) {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}
function deleteTodo(id) {
setTodos(todos.filter(todo => todo.id !== id));
}
return (
<div>
<h1>TodoList</h1>
<input
value={text}
onChange={e => setText(e.target.value)}
placeholder="请输入任务"
/>
<button onClick={addTodo}>添加</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
onClick={() => toggleTodo(todo.id)}
style={{
textDecoration: todo.done ? "line-through" : "none",
cursor: "pointer",
}}
>
{todo.title}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
export default TodoList;
核心讲解
jsx
const [text, setText] = useState("");
保存输入框内容。
jsx
const [todos, setTodos] = useState([]);
保存任务列表。
jsx
setTodos([...todos, newTodo]);
不能直接 push,要创建新数组。
jsx
todos.map(...)
用于更新某一项。
jsx
todos.filter(...)
用于删除某一项。
面试追问
问:为什么不能直接 todos.push?
答:React 状态更新依赖引用变化。直接 push 会修改原数组,可能导致 React 无法正确感知变化。
五、实战项目 2:搜索过滤列表
jsx
import { useMemo, useState } from "react";
function SearchList() {
const [keyword, setKeyword] = useState("");
const users = [
{ id: 1, name: "张三", role: "前端" },
{ id: 2, name: "李四", role: "后端" },
{ id: 3, name: "王五", role: "测试" },
];
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.includes(keyword) || user.role.includes(keyword)
);
}, [keyword]);
return (
<div>
<h1>用户搜索</h1>
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="请输入姓名或岗位"
/>
<ul>
{filteredUsers.map(user => (
<li key={user.id}>
{user.name} - {user.role}
</li>
))}
</ul>
</div>
);
}
export default SearchList;
业务场景
- 后台用户管理
- 商品搜索
- 订单筛选
- 城市列表过滤
常见错误
jsx
users.filter(...)
如果数据量很大,每次渲染都计算,可能影响性能。可以用 useMemo 缓存。
六、实战项目 3:可复用 Modal 组件
jsx
import { useState } from "react";
function Modal({ open, title, children, onClose }) {
if (!open) return null;
return (
<div style={styles.mask}>
<div style={styles.modal}>
<h2>{title}</h2>
<div>{children}</div>
<button onClick={onClose}>关闭</button>
</div>
</div>
);
}
const styles = {
mask: {
position: "fixed",
inset: 0,
background: "rgba(0,0,0,0.4)",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
modal: {
width: 400,
padding: 24,
background: "#fff",
borderRadius: 8,
},
};
function App() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>打开弹窗</button>
<Modal
open={open}
title="提示"
onClose={() => setOpen(false)}
>
<p>这是一个可复用 Modal 组件。</p>
</Modal>
</div>
);
}
export default App;
组件设计重点
jsx
open
控制弹窗显示隐藏。
jsx
title
弹窗标题。
jsx
children
弹窗内容插槽。
jsx
onClose
关闭回调。
面试追问
问:children 是什么?
答:children 是 React 中特殊的 prop,用于接收组件标签内部的内容。
七、组件封装教学
1. 如何拆分组件?
拆分原则:
txt
一个组件只做一件事
重复出现的 UI 抽成组件
复杂页面按业务模块拆分
React 官方也建议构建 UI 时,先把界面拆成组件层级,再连接数据流。(React)
2. 通用 Button 组件
jsx
function Button({ type = "primary", children, onClick, disabled = false }) {
const style = {
padding: "8px 16px",
border: "none",
borderRadius: 4,
cursor: disabled ? "not-allowed" : "pointer",
background: type === "primary" ? "#1677ff" : "#ddd",
color: type === "primary" ? "#fff" : "#333",
};
return (
<button style={style} onClick={onClick} disabled={disabled}>
{children}
</button>
);
}
export default Button;
使用
jsx
<Button type="primary" onClick={() => alert("提交")}>
提交
</Button>
3. 通用 Card 组件
jsx
function Card({ title, children }) {
return (
<div
style={{
border: "1px solid #eee",
borderRadius: 8,
padding: 16,
marginBottom: 16,
}}
>
<h3>{title}</h3>
<div>{children}</div>
</div>
);
}
export default Card;
使用
jsx
<Card title="用户信息">
<p>姓名:张三</p>
<p>岗位:前端工程师</p>
</Card>
八、进阶内容
1. 组件通信
常见方式:
txt
父传子:props
子传父:回调函数
兄弟通信:状态提升
跨层级通信:Context
复杂状态:状态管理库
2. 状态提升
jsx
import { useState } from "react";
function InputBox({ value, onChange }) {
return (
<input value={value} onChange={e => onChange(e.target.value)} />
);
}
function Preview({ value }) {
return <p>预览:{value}</p>;
}
function App() {
const [text, setText] = useState("");
return (
<div>
<InputBox value={text} onChange={setText} />
<Preview value={text} />
</div>
);
}
export default App;
大白话
两个组件都需要同一份数据,就把数据放到它们共同的父组件。
3. Context
jsx
import { createContext, useContext } from "react";
const ThemeContext = createContext("light");
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>当前主题:{theme}</div>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
export default App;
场景
- 主题
- 登录用户信息
- 多语言
- 权限信息
4. React 性能优化
常见方案:
txt
React.memo:缓存组件
useMemo:缓存计算结果
useCallback:缓存函数
合理使用 key
列表虚拟滚动
避免不必要的状态提升
组件拆分
按需加载
5. React Router
React Router 是 React 生态中常用的路由库。官方文档介绍 React Router v7 是非破坏性升级,并支持逐步衔接 React 19 相关能力。(reactrouter.com)
jsx
import {
createBrowserRouter,
RouterProvider,
Link,
} from "react-router-dom";
function Home() {
return <h1>首页</h1>;
}
function About() {
return <h1>关于我们</h1>;
}
function Layout() {
return (
<div>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</div>
);
}
const router = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
6. 常见项目目录结构
txt
src
├── assets 静态资源
├── components 通用组件
├── pages 页面组件
├── hooks 自定义 Hook
├── utils 工具函数
├── services 接口请求
├── router 路由配置
├── store 状态管理
├── App.jsx
└── main.jsx
九、React 高频面试题
1. React 为什么需要虚拟 DOM?
虚拟 DOM 是 JS 对象形式的 UI 描述。React 通过比较前后虚拟 DOM,尽量减少真实 DOM 操作。
大白话:
真实 DOM 很贵,虚拟 DOM 像"草稿纸",先在草稿纸上算清楚哪里变了,再去改真实页面。
2. key 的作用是什么?
key 帮助 React 识别列表中每一项的身份。
错误:
jsx
list.map((item, index) => <li key={index}>{item.name}</li>)
更推荐:
jsx
list.map(item => <li key={item.id}>{item.name}</li>)
3. useEffect 执行时机?
jsx
useEffect(() => {
console.log("每次渲染后执行");
});
useEffect(() => {
console.log("只在挂载后执行一次");
}, []);
useEffect(() => {
console.log("keyword 变化时执行");
}, [keyword]);
4. useMemo 和 useCallback 区别?
txt
useMemo:缓存值
useCallback:缓存函数
等价理解:
jsx
useCallback(fn, deps)
约等于:
jsx
useMemo(() => fn, deps)
5. 受控组件和非受控组件?
受控组件:
jsx
<input value={value} onChange={e => setValue(e.target.value)} />
非受控组件:
jsx
<input ref={inputRef} />
6. React 组件通信方式?
txt
父传子:props
子传父:回调函数
兄弟通信:状态提升
跨层级通信:Context
复杂全局状态:Redux / Zustand / Jotai 等
7. React 性能优化方案?
txt
1. 使用 React.memo 减少组件重复渲染
2. 使用 useMemo 缓存复杂计算
3. 使用 useCallback 缓存函数
4. 避免无意义的 state
5. 大列表使用虚拟滚动
6. 路由懒加载
7. 合理拆分组件
8. 使用稳定 key
十、React 学习路线图
txt
第一阶段:JavaScript 基础
变量、函数、数组、对象、ES6、模块化、异步
第二阶段:React 基础
JSX、组件、Props、State、事件、列表、表单
第三阶段:Hooks
useState、useEffect、useRef、useMemo、useCallback、自定义 Hook
第四阶段:项目实战
TodoList、搜索列表、Modal、后台管理系统
第五阶段:工程化
Vite、ESLint、Prettier、Git、接口请求、环境变量
第六阶段:进阶能力
Context、路由、权限、性能优化、组件封装
第七阶段:面试准备
虚拟 DOM、key、Hooks 原理、组件通信、性能优化
最后总结
React 学习不要只背概念。正确路线是:
txt
先学组件
再学状态
再学 Hooks
再做项目
最后补面试原理
真正掌握 React 的标志不是"知道 useState 是什么",而是你能独立完成:
txt
一个可维护的页面
一套可复用组件
一次清晰的数据流设计
一个能解释给面试官听的项目