React 快速入门到精通教程:从零基础到能写项目
React 官方把它定义为:用 JavaScript 构建用户界面的库,核心思想是把页面拆成一个个组件,再用数据驱动页面变化。React 官方快速入门也强调,日常开发中最常用的能力包括组件、JSX、条件渲染、列表渲染、事件、状态和组件间数据共享。(React)
文章目录
- React 快速入门到精通教程:从零基础到能写项目
- 一、React 是什么?
- 1. 专业解释
- 2. 大白话理解
- 3. 生活案例类比
- 4. React 适合解决什么问题?
- 二、React 核心基础
- 1. JSX
- 概念解释
- 大白话
- 代码示例
- 逐行解释
- 常见错误
- 面试追问
- 2. 组件
- 概念解释
- 大白话
- 代码示例
- 逐行解释
- 常见错误
- 面试追问
- 3. Props
- 概念解释
- 大白话
- 代码示例
- 逐行解释
- 常见错误
- 面试追问
- 4. State
- 概念解释
- 大白话
- 代码示例
- 逐行解释
- 常见错误
- 面试追问
- 5. 事件处理
- 代码示例
- 解释
- 常见错误
- 面试追问
- 6. 条件渲染
- 常见场景
- 常见错误
- 7. 列表渲染
- 重点
- 常见错误
- 面试追问
- 8. 表单处理
- 大白话
- 面试追问
- 三、Hooks 重点讲解
- 1. useState
- 场景
- 常见错误
- 面试追问
- 2. useEffect
- 概念解释
- 逐行解释
- 常见错误
- 面试追问
- 3. useRef
- 场景
- 常见错误
- 面试追问
- 4. useMemo
- 大白话
- 场景
- 常见错误
- 5. useCallback
- 大白话
- 面试追问
- 6. 自定义 Hook
- 场景
- 面试追问
- 四、实战项目 1:TodoList
- 完整代码
- 核心讲解
- 面试追问
- 五、实战项目 2:搜索过滤列表
- 业务场景
- 常见错误
- 六、实战项目 3:可复用 Modal 组件
- 组件设计重点
- 面试追问
- 七、组件封装教学
- 1. 如何拆分组件?
- 2. 通用 Button 组件
- 使用
- 3. 通用 Card 组件
- 使用
- 八、进阶内容
- 1. 组件通信
- 2. 状态提升
- 大白话
- 3. Context
- 场景
- 4. React 性能优化
- 5. React Router
- 6. 常见项目目录结构
- 九、React 高频面试题
- 1. React 为什么需要虚拟 DOM?
- 2. key 的作用是什么?
- 3. useEffect 执行时机?
- 4. useMemo 和 useCallback 区别?
- 5. 受控组件和非受控组件?
- 6. React 组件通信方式?
- 7. React 性能优化方案?
- 十、React 学习路线图
- 最后总结
一、React 是什么?
1. 专业解释
React 是一个用于构建用户界面的 JavaScript 库。它通过组件化、声明式渲染、状态驱动 UI的方式,让开发者可以更高效地构建复杂前端应用。
2. 大白话理解
React 就像“搭积木”。
一个页面不是一次性写成一大坨 HTML,而是拆成:
页面 = Header + Sidebar + Content + Footer每一块都是组件,组件可以复用、组合、传数据。
3. 生活案例类比
做一套乐高房子:
门 = 一个组件 窗户 = 一个组件 屋顶 = 一个组件 房间 = 多个组件组合 整栋房子 = 整个 React 应用以后你想换窗户,不需要推倒整栋房子,只改窗户组件。
4. React 适合解决什么问题?
React 适合:
- 中后台管理系统
- 电商网站
- 社交应用
- 数据可视化平台
- 单页应用 SPA
- 组件复用很多的业务系统
React 官方“Thinking in React”也推荐先把 UI 拆成组件层级,再确定每个组件的状态与数据流。(React)
二、React 核心基础
1. JSX
概念解释
JSX 是 JavaScript 的语法扩展,看起来像 HTML,但本质上会被编译成 JavaScript。
大白话
JSX 就是“在 JS 里写页面结构”。
代码示例
function App() { const name = "小明"; return ( <div> <h1>Hello, {name}</h1> <p>欢迎学习 React</p> </div> ); } export default App;逐行解释
function App() {定义一个 React 函数组件。
const name = "小明";定义普通 JavaScript 变量。
return (组件返回 JSX。
<h1>Hello, {name}</h1>用{}在 JSX 中插入 JS 表达式。
export default App;导出组件。
常见错误
return ( <h1>标题</h1> <p>内容</p> );错误:JSX 必须有一个根节点。
正确:
return ( <> <h1>标题</h1> <p>内容</p> </> );面试追问
问:JSX 是 HTML 吗?
答:不是。JSX 是 JavaScript 的语法扩展,最终会被编译成 React 元素。
2. 组件
概念解释
组件是 React 应用的基本单位。React 应用就是由组件组成的树。
大白话
组件就是“页面零件”。
代码示例
function UserCard() { return ( <div> <h2>张三</h2> <p>前端工程师</p> </div> ); } function App() { return ( <div> <UserCard /> <UserCard /> </div> ); } export default App;逐行解释
function UserCard() {定义用户卡片组件。
<UserCard />在 App 组件中使用 UserCard。
常见错误
组件名小写:
function userCard() {}React 会把小写标签当成原生 HTML 标签。
正确:
function UserCard() {}面试追问
问:React 为什么推荐组件化?
答:为了复用、拆分复杂度、降低维护成本。
3. Props
概念解释
Props 是父组件传给子组件的数据。
大白话
Props 就像“外卖订单备注”:父组件告诉子组件该显示什么。
代码示例
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;逐行解释
function UserCard(props)子组件接收 props。
props.name读取父组件传入的 name。
<UserCard name="张三" job="前端工程师" />父组件传值。
常见错误
直接修改 props:
props.name = "王五";错误。Props 是只读的。
面试追问
问:Props 和 State 有什么区别?
答:Props 是外部传入的,State 是组件自己管理的。
4. State
概念解释
State 是组件内部状态。状态变化后,React 会重新渲染页面。
React 官方文档说明,State 可以让组件“记住”用户输入、选择等信息。(react.nodejs.cn)
大白话
State 就是组件自己的“小记事本”。
代码示例
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;逐行解释
import { useState } from "react";引入 useState。
const [count, setCount] = useState(0);定义状态 count,初始值是 0。
setCount(count + 1)更新状态。
常见错误
直接改 state:
count++;错误。页面不会可靠更新。
正确:
setCount(count + 1);面试追问
问:setState 是同步还是异步?
答:React 会批量处理状态更新,不应该依赖修改后的 state 立即同步可读。需要基于旧值更新时,用函数写法:
setCount(prev => prev + 1);5. 事件处理
代码示例
function App() { function handleClick() { alert("按钮被点击了"); } return <button onClick={handleClick}>点击我</button>; } export default App;解释
onClick={handleClick}React 中事件名使用驼峰命名。
常见错误
<button onClick={handleClick()}>点击</button>这样会在页面渲染时立即执行。
正确:
<button onClick={handleClick}>点击</button>面试追问
问:React 事件和原生 DOM 事件有什么区别?
答:React 使用合成事件,提供跨浏览器一致的事件行为。
6. 条件渲染
function App() { const isLogin = true; return ( <div> {isLogin ? <h1>欢迎回来</h1> : <h1>请先登录</h1>} </div> ); } export default App;常见场景
- 登录 / 未登录
- 有数据 / 无数据
- 加载中 / 加载完成
常见错误
if (isLogin) { return <h1>欢迎</h1>; }这可以用,但不能直接在 JSX 里面写 if 语句。
7. 列表渲染
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必须稳定、唯一。
常见错误
<li key={index}>{user.name}</li>如果列表会增删改,尽量不要用 index。
面试追问
问:key 的作用是什么?
答:帮助 React 判断哪些元素新增、删除、移动,从而提高更新效率并避免状态错乱。
8. 表单处理
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
import { useState } from "react"; function LikeButton() { const [liked, setLiked] = useState(false); return ( <button onClick={() => setLiked(!liked)}> {liked ? "已点赞" : "点赞"} </button> ); } export default LikeButton;场景
- 弹窗开关
- 表单输入
- Tab 切换
- 计数器
常见错误
setCount(count + 1); setCount(count + 1);可能只加一次。
正确:
setCount(prev => prev + 1); setCount(prev => prev + 1);面试追问
问:为什么 useState 返回数组?
答:方便开发者自定义变量名。
2. useEffect
概念解释
useEffect 用来处理副作用,比如请求接口、设置定时器、监听事件。
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;逐行解释
useEffect(() => {组件渲染后执行副作用。
fetch(...)请求接口。
}, []);空依赖数组表示组件首次挂载后执行一次。
常见错误
忘记依赖项:
useEffect(() => { console.log(keyword); }, []);如果 effect 依赖 keyword,应写:
useEffect(() => { console.log(keyword); }, [keyword]);面试追问
问:useEffect 执行时机?
答:
无依赖:每次渲染后执行 空数组:首次挂载后执行一次 有依赖:依赖变化后执行 return 函数:组件卸载或下次 effect 执行前清理3. useRef
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
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
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 区别?
答:
useMemo 缓存计算结果 useCallback 缓存函数本身6. 自定义 Hook
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
完整代码
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;核心讲解
const [text, setText] = useState("");保存输入框内容。
const [todos, setTodos] = useState([]);保存任务列表。
setTodos([...todos, newTodo]);不能直接 push,要创建新数组。
todos.map(...)用于更新某一项。
todos.filter(...)用于删除某一项。
面试追问
问:为什么不能直接 todos.push?
答:React 状态更新依赖引用变化。直接 push 会修改原数组,可能导致 React 无法正确感知变化。
五、实战项目 2:搜索过滤列表
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;业务场景
- 后台用户管理
- 商品搜索
- 订单筛选
- 城市列表过滤
常见错误
users.filter(...)如果数据量很大,每次渲染都计算,可能影响性能。可以用 useMemo 缓存。
六、实战项目 3:可复用 Modal 组件
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;组件设计重点
open控制弹窗显示隐藏。
title弹窗标题。
children弹窗内容插槽。
onClose关闭回调。
面试追问
问:children 是什么?
答:children 是 React 中特殊的 prop,用于接收组件标签内部的内容。
七、组件封装教学
1. 如何拆分组件?
拆分原则:
一个组件只做一件事 重复出现的 UI 抽成组件 复杂页面按业务模块拆分React 官方也建议构建 UI 时,先把界面拆成组件层级,再连接数据流。(React)
2. 通用 Button 组件
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;使用
<Button type="primary" onClick={() => alert("提交")}> 提交 </Button>3. 通用 Card 组件
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;使用
<Card title="用户信息"> <p>姓名:张三</p> <p>岗位:前端工程师</p> </Card>八、进阶内容
1. 组件通信
常见方式:
父传子:props 子传父:回调函数 兄弟通信:状态提升 跨层级通信:Context 复杂状态:状态管理库2. 状态提升
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
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 性能优化
常见方案:
React.memo:缓存组件 useMemo:缓存计算结果 useCallback:缓存函数 合理使用 key 列表虚拟滚动 避免不必要的状态提升 组件拆分 按需加载5. React Router
React Router 是 React 生态中常用的路由库。官方文档介绍 React Router v7 是非破坏性升级,并支持逐步衔接 React 19 相关能力。(reactrouter.com)
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. 常见项目目录结构
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 识别列表中每一项的身份。
错误:
list.map((item, index) => <li key={index}>{item.name}</li>)更推荐:
list.map(item => <li key={item.id}>{item.name}</li>)3. useEffect 执行时机?
useEffect(() => { console.log("每次渲染后执行"); }); useEffect(() => { console.log("只在挂载后执行一次"); }, []); useEffect(() => { console.log("keyword 变化时执行"); }, [keyword]);4. useMemo 和 useCallback 区别?
useMemo:缓存值 useCallback:缓存函数等价理解:
useCallback(fn, deps)约等于:
useMemo(() => fn, deps)5. 受控组件和非受控组件?
受控组件:
<input value={value} onChange={e => setValue(e.target.value)} />非受控组件:
<input ref={inputRef} />6. React 组件通信方式?
父传子:props 子传父:回调函数 兄弟通信:状态提升 跨层级通信:Context 复杂全局状态:Redux / Zustand / Jotai 等7. React 性能优化方案?
1. 使用 React.memo 减少组件重复渲染 2. 使用 useMemo 缓存复杂计算 3. 使用 useCallback 缓存函数 4. 避免无意义的 state 5. 大列表使用虚拟滚动 6. 路由懒加载 7. 合理拆分组件 8. 使用稳定 key十、React 学习路线图
第一阶段:JavaScript 基础 变量、函数、数组、对象、ES6、模块化、异步 第二阶段:React 基础 JSX、组件、Props、State、事件、列表、表单 第三阶段:Hooks useState、useEffect、useRef、useMemo、useCallback、自定义 Hook 第四阶段:项目实战 TodoList、搜索列表、Modal、后台管理系统 第五阶段:工程化 Vite、ESLint、Prettier、Git、接口请求、环境变量 第六阶段:进阶能力 Context、路由、权限、性能优化、组件封装 第七阶段:面试准备 虚拟 DOM、key、Hooks 原理、组件通信、性能优化最后总结
React 学习不要只背概念。正确路线是:
先学组件 再学状态 再学 Hooks 再做项目 最后补面试原理真正掌握 React 的标志不是“知道 useState 是什么”,而是你能独立完成:
一个可维护的页面 一套可复用组件 一次清晰的数据流设计 一个能解释给面试官听的项目