一、组件(Component)
组件是 React 应用的构建块,每个组件代表 UI 的一部分,可以独立开发、测试和复用。
1. 函数组件(Function Component)
-
定义:使用 JavaScript 函数定义的组件。
-
特点:轻量、简洁,配合 Hooks 实现状态管理。
-
示例:
function Greeting(props) { return <h1>Hello, {props.name}</h1>; }
2. 类组件(Class Component)
-
定义:使用 ES6 类继承
React.Component
的组件。 -
特点:支持生命周期方法和
this.state
(已逐渐被函数组件 + Hooks 取代)。 -
示例:
class Greeting extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
3. 组件核心原则
-
可组合性:组件可以嵌套其他组件,形成组件树。
-
单一职责:每个组件专注于一个功能。
-
复用性:通过 Props 定制组件行为。
二、JSX(JavaScript XML)
JSX 是 React 的核心语法扩展,允许在 JavaScript 中编写类似 HTML 的结构。
1. JSX 基础
-
语法:类似 HTML,但本质是 JavaScript。
-
编译过程:Babel 将 JSX 转换为
React.createElement()
。// JSX 代码 const element = <div className="app">Hello React</div>; // 编译后的 JavaScript const element = React.createElement("div", { className: "app" }, "Hello React");
2. JSX 规则
-
必须闭合标签:如
<img />
。 -
使用
className
代替class
:避免与 JavaScript 关键字冲突。 -
表达式插值:用
{}
包裹 JavaScript 表达式。const name = "Alice"; const element = <p>{name} is learning React.</p>;
3. JSX 与组件结合
-
渲染组件:将组件当作标签使用。
function App() { return <Greeting name="Bob" />; }
三、状态(State)
状态是组件内部管理的可变数据,驱动 UI 更新。
1. 使用 useState
Hook(函数组件)
-
定义:通过
useState
定义状态变量和更新函数。 -
示例:
import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); // 初始值为 0 return ( <div> <button onClick={() => setCount(count + 1)}>+1</button> <p>Count: {count}</p> </div> ); }
2. 类组件的 State(传统方式)
-
初始化:通过
this.state
定义。 -
更新:通过
this.setState()
方法。class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <button onClick={() => this.setState({ count: this.state.count + 1 })}> +1 </button> <p>Count: {this.state.count}</p> </div> ); } }
3. State 注意事项
-
不可直接修改:必须通过
setState
或状态更新函数。 -
异步更新:多个
setState
调用可能被合并。 -
状态提升:如果多个组件需要共享状态,将状态提升到共同父组件。
4. 常见问题与解决方案
1. 为什么组件不更新?
-
原因:可能直接修改了 State(如
state.count = 5
)。 -
解决:始终使用
setState
或状态更新函数。
2. 如何避免不必要的渲染?
-
方法:使用
React.memo
(函数组件)或shouldComponentUpdate
(类组件)。
3. Props 如何传递复杂对象?
-
示例:
<UserProfile user={{ name: "Alice", age: 30 }} />
四、核心 Hooks
1. useState
:状态管理
-
作用:为函数组件添加局部状态。
-
语法:
const [state, setState] = useState(initialState);
-
示例:
function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Clicked {count} times </button> ); }
-
注意:
-
状态更新是异步的,且会触发组件重新渲染。
-
若新状态依赖旧状态,使用函数式更新:
setCount(prev => prev + 1)
。 -
复杂对象状态应确保不可变性(如使用扩展运算符或
immer
库)。
-
-
闭包陷阱(异步更新问题)
// 直接使用 count 值 const fn1 = () => { setCount(count + 1); setCount(count + 1); // 两次更新都基于同一个旧值 } // 使用函数式更新 const fn2 = () => { setCount(prev => prev + 1); setCount(prev => prev + 1); // 每次更新都基于最新状态 }
-
直接更新:同步代码中连续调用时,所有更新都基于同一个旧的
count
值(闭包陷阱) -
函数式更新:确保每次更新都基于最新的状态值
2. useEffect
:处理副作用
useEffect
是专门用于处理副作用(Side Effects)的 Hook。它通过将副作用逻辑与渲染逻辑分离,保证组件的纯函数特性。
①为什么不能在渲染函数中直接执行副作用?
纯函数(Pure Function)
定义:纯函数是满足以下两个条件的函数:
-
相同的输入永远返回相同的输出(确定性)
-
不会修改外部状态或产生副作用(无污染)
核心特征:
-
不依赖外部变量(只使用自己的参数)
-
不改变外部变量(如全局变量、参数对象等)
-
不执行任何 I/O 操作(如网络请求、文件读写)
// ✅ 纯函数示例
function add(a, b) {
return a + b; // 只依赖参数,不修改外部状态
}
const result = add(2, 3); // 永远是 5
// ✅ 另一个纯函数
function toUpperCase(str) {
return str.toUpperCase(); // 原始字符串未被修改
}
const text = toUpperCase("hello"); // 永远是 "HELLO"
// 非纯函数反例
// ❌ 非纯函数:依赖外部变量
let base = 10;
function impureAdd(a) {
return base + a; // 结果受外部变量 base 影响
}
// ❌ 非纯函数:修改外部状态
function changeColor(element) {
element.style.color = 'red'; // 修改了 DOM(外部状态)
}
// ❌ 非纯函数:产生副作用(控制台输出)
function logSum(a, b) {
console.log(a + b); // 产生 I/O 操作
return a + b;
}
副作用(Side Effect)
定义:任何与函数外部世界发生交互的操作,包括:
-
修改全局变量
-
操作 DOM
-
发送 HTTP 请求
-
读写文件或数据库
-
调用有副作用的函数(如
console.log
)
React 组件的渲染函数(如函数组件的函数体)必须是纯函数。如果在渲染函数中直接执行副作用:
function Component() {
// ❌ 直接在渲染函数中触发副作用
fetchData(); // 网络请求
document.title = "Updated"; // 修改 DOM
return <div>...</div>;
}
会导致:
-
无限循环:如果副作用触发了状态更新(如
setState
),组件会重新渲染 → 再次执行副作用 → 再次更新状态 → 循环。 -
不可预测的 UI:副作用可能破坏 React 的渲染时序(如异步操作未完成时 UI 已渲染)。
②useEffect
的核心作用
useEffect
将副作用逻辑延迟到渲染完成后执行,确保渲染过程的纯粹性。它的基本结构为:
useEffect(() => {
// 副作用逻辑
return () => { /* 清理逻辑 */ }; // 可选
}, [dependencies]); // 依赖项数组(可选)
执行时机:
依赖项数组 | 执行时机 |
---|---|
useEffect(() => {}) | 每次渲染后都执行(类似 componentDidMount + componentDidUpdate ) |
useEffect(() => {}, []) | 仅首次渲染后执行(类似 componentDidMount ) |
useEffect(() => {}, [dep]) | 依赖项变化时执行(类似 componentDidUpdate ) |
③常见副作用场景
-
修改 DOM
// React 组件中的副作用(通常在 useEffect 中处理) function Button() { const handleClick = () => { document.title = "Clicked"; // 副作用:修改页面标题 }; return <button onClick={handleClick}>Click</button>; }
-
数据请求
// 在 useEffect 中发起网络请求 useEffect(() => { fetch('/api/data') // 副作用:网络请求 .then(res => res.json()); }, []);
-
定时器操作
useEffect(() => { const timer = setInterval(() => { console.log("Running..."); // 副作用:持续输出 }, 1000); return () => clearInterval(timer); // 清理副作用 }, []);
④useEffect
的最佳实践
1. 始终处理清理逻辑
如果副作用需要清理(如订阅、计时器、DOM 事件),在 return
函数中执行:
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer); // 清理计时器
}, []);
2. 精确控制依赖项
-
依赖项数组决定了何时重新执行副作用。
-
如果省略依赖项,可能导致访问过时的闭包变量:
const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { // ❌ 由于依赖项为空,count 始终是初始值 0 setCount(count + 1) }, 1000); return () => clearInterval(timer); }, []); // 错误写法 // ✅ 正确写法:将 count 加入依赖项 useEffect(() => { const timer = setInterval(() => { setCount(cnt => cnt + 1) // 使用函数式更新 }, 1000); return () => clearInterval(timer); }, [count]);
3. 避免无限循环
如果副作用中更新了依赖项,确保依赖项变化是可控的:
const [data, setData] = useState([]);
// ❌ 无限循环:data 变化 → 触发 useEffect → 更新 data → 再次触发...
useEffect(() => {
setData([...data, newItem]);
}, [data]);
// ✅ 使用函数式更新避免依赖 data
useEffect(() => {
setData((prevData) => [...prevData, newItem]);
}, []);
⑤总结
useEffect
的用途可以归纳为:
-
隔离副作用:保证渲染函数的纯粹性。
-
生命周期管理:替代类组件的
componentDidMount
、componentDidUpdate
、componentWillUnmount
。 -
依赖驱动执行:通过依赖项数组精确控制副作用触发时机。
React 中的核心规则:
-
渲染阶段必须保持纯净
React 要求组件在渲染时:-
不直接修改 DOM(渲染函数应该是纯的)
-
不执行网络请求
-
不访问全局变量
-
-
副作用必须隔离
所有副作用应放在:-
useEffect
钩子中 -
事件处理函数中(如
onClick
)
-
3. useContext
:访问上下文
-
作用:读取 React Context 的值,避免逐层传递 Props。
-
示例:
const ThemeContext = createContext('light'); function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { const theme = useContext(ThemeContext); return <button>Theme: {theme}</button>; }
五、性能优化
1. memo
(高阶组件)
作用:
-
防止子组件不必要的重新渲染。当父组件重新渲染时,若子组件的
props
没有变化,memo
会跳过子组件的渲染。 -
通过浅比较(shallow compare)
props
是否变化来决定是否重新渲染。
使用场景:
-
子组件渲染成本高(如复杂计算、大量 DOM 操作)。
-
父组件频繁重新渲染,但子组件的
props
不变。 -
传递的
props
是基本类型或引用稳定的对象/函数。
代码示例:
import { memo } from 'react';
// 用 memo 包裹子组件
const ChildComponent = memo(function ChildComponent({ data }) {
console.log('子组件渲染');
return <div>{data}</div>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>点击 {count}</button>
{/* data 是固定值,父组件更新时子组件不会重新渲染 */}
<ChildComponent data="静态数据" />
</div>
);
}
2. useMemo
(钩子)
作用:
-
缓存计算结果。当依赖项不变时,直接返回缓存值,避免重复计算。
-
适用于需要复杂计算的场景(如过滤列表、数学运算)。
使用场景:
-
计算代价高昂的衍生数据。
-
依赖项变化时才需要重新计算。
-
避免因父组件无关状态变化导致子组件重复计算。
代码示例
import { useMemo } from 'react';
function App({ items, filter }) {
// 依赖项 [items, filter] 变化时,重新计算 filteredItems
const filteredItems = useMemo(() => {
console.log('重新计算列表');
return items.filter(item => item.includes(filter));
}, [items, filter]);
return <List items={filteredItems} />;
}
3. useCallback
(钩子)
作用:
-
缓存函数实例。当依赖项不变时,返回同一个函数引用。
-
避免因父组件重新渲染导致传递给子组件的函数引用变化,触发子组件不必要的渲染。
使用场景:
-
将函数作为
props
传递给子组件,且子组件被memo
优化。 -
函数作为其他 Hook 的依赖项(如
useEffect
)。 -
事件处理函数需要保持引用稳定。
代码示例:
import { useCallback, memo } from 'react';
const ChildButton = memo(function ChildButton({ onClick }) {
console.log('子按钮渲染');
return <button onClick={onClick}>点击</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖项:函数引用始终不变
return (
<div>
<div>计数:{count}</div>
<ChildButton onClick={handleClick} />
</div>
);
}
三者的核心区别
工具 | 解决的问题 | 输入 | 输出 | 适用对象 |
---|---|---|---|---|
memo | 子组件不必要的渲染 | 组件 | 优化后的组件 | 函数组件 |
useMemo | 避免重复计算高开销操作 | 计算函数 + 依赖项 | 缓存值 | 计算结果 |
useCallback | 保持函数引用稳定 | 函数 + 依赖项 | 缓存函数 | 函数引用 |
使用注意事项
-
避免滥用:
-
优先考虑代码可读性,仅在性能瓶颈明确时优化。
-
过度使用可能导致内存开销增加。
-
-
与
memo
配合使用:-
如果子组件用
memo
包裹,父组件传递的props
(包括函数)需保持引用稳定,此时useCallback
和useMemo
是必要的。
-
实战场景总结
-
场景 1:父组件频繁更新,但子组件
props
不变 →memo
。 -
场景 2:复杂计算(如数据过滤、排序) →
useMemo
。 -
场景 3:函数作为
props
传递且子组件用memo
优化 →useCallback
。
六、高级 Hooks
6. useReducer
:复杂状态逻辑
-
作用:类似 Redux 的状态管理,适用于多操作或嵌套状态。
-
语法:
const [state, dispatch] = useReducer(reducer, initialState);
-
示例:
import { useReducer } from 'react' const initState = { count: 0 } function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } case 'update': return { count: action.payload } default: return state.count } } function App() { const [state, dispatch] = useReducer(reducer, initState) return ( <> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <span> {state.count} </span> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <br /> <button onClick={() => dispatch({ type: 'update', payload: 100 })}>update</button> </> ) } export default App
7. useRef
:持久化引用
-
作用:访问 DOM 节点或保存可变值(不触发重新渲染)。
-
语法:
const ref = useRef(initialValue);
-
示例:
import { useRef } from 'react' function Input({ placeholder, ref }) { return <input type='text' placeholder={placeholder} ref={ref}></input> } function App() { const inputRef = useRef(null) const focusInput = () => inputRef.current.focus() return ( <> <Input ref={inputRef} placeholder="请输入内容" /> <button onClick={focusInput}>focus</button> </> ) } export default App
七、自定义 Hooks
封装可复用逻辑
-
规则:函数名以
use
开头,内部可调用其他 Hooks。
-
将重复的组件逻辑(如表单处理、数据请求)封装成可复用的函数。
// 自定义 Hook:表单输入管理 function useFormInput(initialValue) { const [value, setValue] = useState(initialValue); const handleChange = (e) => setValue(e.target.value); return { value, onChange: handleChange }; } // 使用 function LoginForm() { const username = useFormInput(''); const password = useFormInput(''); return ( <form> <input {...username} placeholder="Username" /> <input {...password} placeholder="Password" /> </form> ); }
八、总结
-
useState
:管理组件内部状态。 -
useEffect
:处理副作用与生命周期逻辑。 -
useContext
:简化跨组件数据传递。 -
useMemo
/useCallback
:优化计算与渲染性能。 -
useReducer
:处理复杂状态逻辑。 -
useRef
:访问 DOM 或保存可变值。