一、Hooks 进阶:脱离 Class 组件的核心能力
React Hooks 是现代 React 的基石,除了useState/useEffect基础用法,这些进阶 Hooks 是处理复杂逻辑的关键:
1. useCallback & useMemo:性能优化(避免不必要的重渲染/计算)
核心场景:组件重渲染时,避免函数/计算结果重复创建,导致子组件无效重渲染。
useCallback:缓存函数引用(解决“每次渲染函数重新创建”问题);useMemo:缓存计算结果(解决“每次渲染重复计算”问题)。
示例:
import { useState, useCallback, useMemo } from "react"; const Parent = () => { const [count, setCount] = useState(0); const [name, setName] = useState("React"); // 缓存函数:只有依赖(空数组)变化时,函数引用才会更新 const handleClick = useCallback(() => { console.log("点击了"); }, []); // 缓存计算结果:只有 count 变化时,才重新计算 const doubleCount = useMemo(() => { console.log("计算 doubleCount"); return count * 2; }, [count]); return ( <div> <p>count: {count}, double: {doubleCount}</p> <button onClick={() => setCount(count + 1)}>加1</button> <button onClick={() => setName("Vue")}>改名字</button> {/* 子组件接收缓存的函数,避免因函数重新创建导致子组件重渲染 */} <Child onClick={handleClick} /> </div> ); }; // 子组件:用 React.memo 配合 useCallback,实现“浅比较props才重渲染” const Child = React.memo(({ onClick }) => { console.log("子组件渲染"); return <button onClick={onClick}>子组件按钮</button>; });关键:
useCallback依赖数组为空 → 函数永久缓存;依赖变化 → 函数重新创建;useMemo不要用于“无意义的简单计算”(比如count + 1),仅用于“重计算成本高”的场景(比如大数据过滤、复杂公式);- 必须配合
React.memo(组件)/useMemo(计算)才能生效,否则单独用无意义。
2. useRef:跨渲染周期保存数据 + 操作 DOM
核心场景:
- 保存跨渲染周期的变量(不会因 setState 触发重渲染);
- 直接操作 DOM 节点(替代 Class 组件的
this.refs); - 保存上一次的状态/属性。
示例:
import { useState, useRef, useEffect } from "react"; const RefDemo = () => { const [count, setCount] = useState(0); // 1. 保存跨渲染周期的变量(不会触发重渲染) const countRef = useRef(0); // 2. 操作 DOM const inputRef = useRef(null); // 3. 保存上一次的 count const prevCountRef = useRef(0); useEffect(() => { // 每次渲染后,更新 prevCountRef prevCountRef.current = count; // 操作 DOM:聚焦输入框 inputRef.current.focus(); }, [count]); const handleAdd = () => { countRef.current += 1; // 不会触发重渲染 setCount(count + 1); }; return ( <div> <input ref={inputRef} type="text" /> <p>当前 count: {count},上一次 count: {prevCountRef.current}</p> <p>ref 保存的 count: {countRef.current}</p> <button onClick={handleAdd}>加1</button> </div> ); };3. useContext:跨组件通信(替代 props 层层传递)
核心场景:祖孙组件、非相邻组件之间共享状态(比如主题、用户信息),避免“props 钻取”。
示例:
// 1. 创建 Context const ThemeContext = React.createContext(); // 父组件:提供 Context 数据 const Parent = () => { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Child /> </ThemeContext.Provider> ); }; // 子组件:无需接收 props,直接消费 Context const Child = () => { // 2. 消费 Context const { theme, setTheme } = useContext(ThemeContext); return ( <div style={{ background: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "#fff" }}> <p>当前主题:{theme}</p> <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>切换主题</button> </div> ); };4. useReducer:复杂状态逻辑管理(替代多个 useState)
核心场景:状态逻辑复杂(比如多个状态互相关联、状态更新规则多),替代 Redux 做轻量级状态管理。
示例:
import { useReducer } from "react"; // 1. 定义 reducer 函数(纯函数:state + action → 新 state) const countReducer = (state, action) => { switch (action.type) { case "ADD": return { ...state, count: state.count + 1 }; case "SUB": return { ...state, count: state.count - 1 }; case "RESET": return { ...state, count: 0 }; default: return state; } }; const ReducerDemo = () => { // 2. 初始化 useReducer:(reducer, 初始state) const [state, dispatch] = useReducer(countReducer, { count: 0 }); return ( <div> <p>count: {state.count}</p> {/* 3. 分发 action,触发 state 更新 */} <button onClick={() => dispatch({ type: "ADD" })}>加1</button> <button onClick={() => dispatch({ type: "SUB" })}>减1</button> <button onClick={() => dispatch({ type: "RESET" })}>重置</button> </div> ); };5. useLayoutEffect:同步执行 DOM 操作(比 useEffect 早)
核心场景:需要在 DOM 更新后、浏览器绘制前执行的操作(比如测量 DOM 尺寸、避免页面闪烁)。
- 执行时机:
useLayoutEffect→ DOM 更新 → 浏览器绘制 →useEffect; - 注意:内部代码会阻塞浏览器绘制,避免做耗时操作。
示例:
import { useState, useLayoutEffect, useRef } from "react"; const LayoutEffectDemo = () => { const [width, setWidth] = useState(0); const divRef = useRef(null); useLayoutEffect(() => { // DOM 更新后立即执行:测量 div 宽度(无闪烁) setWidth(divRef.current.offsetWidth); }, []); return <div ref={divRef}>该元素宽度:{width}px</div>; };二、自定义 Hooks:状态逻辑复用(React 高级用法的核心)
核心思想:将组件中可复用的逻辑抽离成自定义 Hooks,实现“逻辑复用,代码解耦”,替代 Class 组件的 HOC/Render Props。
示例1:封装“防抖”Hook
// hooks/useDebounce.js import { useState, useEffect } from "react"; export const useDebounce = (value, delay = 500) => { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { // 延迟更新 debouncedValue const timer = setTimeout(() => { setDebouncedValue(value); }, delay); // 清除定时器(依赖变化时) return () => clearTimeout(timer); }, [value, delay]); return debouncedValue; }; // 使用: const Search = () => { const [input, setInput] = useState(""); // 防抖后的输入值(500ms 无变化才更新) const debouncedInput = useDebounce(input, 500); // 仅在防抖后的值变化时,请求接口 useEffect(() => { if (debouncedInput) { console.log("请求搜索:", debouncedInput); } }, [debouncedInput]); return <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="搜索..." />; };示例2:封装“请求数据”Hook
// hooks/useRequest.js import { useState, useEffect } from "react"; export const useRequest = (apiFn, dependencies = []) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const res = await apiFn(); setData(res); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); }, dependencies); return { data, loading, error }; }; // 使用: const UserList = () => { // 传入接口函数,依赖为空 → 仅初始化请求 const { data, loading, error } = useRequest(() => fetch("/api/users").then(res => res.json()), []); if (loading) return <div>加载中...</div>; if (error) return <div>出错了:{error.message}</div>; return ( <ul> {data?.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); };三、性能优化进阶
1. React.memo:组件浅比较(避免无意义重渲染)
- 作用:对函数组件做“浅比较 props”,只有 props 变化时才重渲染;
- 注意:仅对比 props 的“浅值”(基本类型、引用类型的地址),深对象变化需配合
useCallback/useMemo。
2. shouldComponentUpdate(Class 组件):手动控制重渲染
- 作用:Class 组件中,返回
true则重渲染,false则跳过; - 替代:函数组件用
React.memo+useCallback/useMemo。
3. React.lazy + Suspense:代码分割(按需加载组件)
核心场景:减小首屏加载体积,只加载当前页面需要的组件(比如路由懒加载)。
示例:
import { lazy, Suspense } from "react"; import { BrowserRouter, Routes, Route } from "react-router-dom"; // 1. 懒加载组件(代码分割) const Home = lazy(() => import("./components/Home")); const About = lazy(() => import("./components/About")); const App = () => { return ( <BrowserRouter> {/* 2. Suspense:指定加载中的 fallback */} <Suspense fallback={<div>加载中...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> </BrowserRouter> ); };4. 不可变数据:避免意外的状态修改
React 状态更新依赖“不可变数据”(直接修改原对象不会触发重渲染),推荐用immer简化不可变操作:
import { useState } from "react"; import { produce } from "immer"; const ImmerDemo = () => { const [user, setUser] = useState({ name: "React", age: 18 }); const handleUpdate = () => { // 传统方式:手动解构,易出错 // setUser({ ...user, age: user.age + 1 }); // immer 方式:直接“修改”草稿,自动生成不可变新对象 setUser(produce(draft => { draft.age += 1; })); }; return ( <div> <p>name: {user.name}, age: {user.age}</p> <button onClick={handleUpdate}>加1岁</button> </div> ); };四、高级模式:跨场景解决方案
1. 错误边界(Error Boundary):捕获组件内的错误
核心场景:避免单个组件报错导致整个应用崩溃,仅支持 Class 组件(React 暂未提供 Hooks 版本)。
示例:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } // 捕获子组件的错误 static getDerivedStateFromError(error) { return { hasError: true, error }; } // 记录错误日志 componentDidCatch(error, info) { console.error("错误信息:", error, info); } render() { if (this.state.hasError) { // 自定义错误提示 return <div>出错了:{this.state.error?.message}</div>; } return this.props.children; } } // 使用:包裹可能出错的组件 const App = () => { return ( <ErrorBoundary> <PotentiallyErrorComponent /> </ErrorBoundary> ); };2. 端口转发(Portals):将组件渲染到 DOM 树外
核心场景:弹窗、模态框、提示框等,避免被父组件的样式(比如overflow: hidden)遮挡。
示例:
import { createPortal } from "react-dom"; const Modal = ({ children, visible }) => { if (!visible) return null; // 渲染到 body 下的 div(而非当前组件的 DOM 层级) return createPortal( <div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "rgba(0,0,0,0.5)", display: "flex", alignItems: "center", justifyContent: "center" }}> <div style={{ background: "#fff", padding: 20 }}>{children}</div> </div>, document.body // 目标 DOM 节点 ); }; // 使用: const PortalDemo = () => { const [visible, setVisible] = useState(false); return ( <div style={{ height: 200, overflow: "hidden", border: "1px solid #eee" }}> <button onClick={() => setVisible(true)}>打开弹窗</button> <Modal visible={visible}>{/* 弹窗内容不会被父组件 overflow 遮挡 */}</Modal> </div> ); };3. 服务器组件(RSC):React 18+ 新特性
核心场景:服务端渲染组件,减少客户端 JS 体积,提升首屏加载速度(Next.js 已深度集成)。
- 特点:组件在服务端运行,不依赖浏览器 API,可直接访问数据库;
- 注意:需配合 React 18+ 服务端渲染框架(Next.js、Remix)使用。
五、总结:高级用法的核心价值
React 高级用法的本质是:
- 复用:自定义 Hooks 实现逻辑复用,替代传统的 HOC/Render Props;
- 性能:
useCallback/useMemo/React.memo减少无效渲染,React.lazy分割代码; - 解耦:
useContext/useReducer简化状态管理,避免 props 钻取和冗余 state; - 兼容:错误边界、Portals 解决特殊场景的 UI/交互问题;
学习建议:
- 先掌握基础 Hooks(
useState/useEffect),再逐步学习进阶 Hooks; - 自定义 Hooks 是核心,从封装小逻辑(防抖、请求)开始,逐步抽象复杂逻辑;
- 性能优化要“按需使用”,不要过度优化(比如简单组件无需
useCallback); - 结合实际场景(比如表单、列表、弹窗)练习,理解“为什么用”比“怎么用”更重要。