一、Hooks 进阶:脱离 Class 组件的核心能力
React Hooks 是现代 React 的基石,除了 useState/useEffect 基础用法,这些进阶 Hooks 是处理复杂逻辑的关键:
1. useCallback & useMemo:性能优化(避免不必要的重渲染/计算)
核心场景:组件重渲染时,避免函数/计算结果重复创建,导致子组件无效重渲染。
useCallback:缓存函数引用(解决"每次渲染函数重新创建"问题);useMemo:缓存计算结果(解决"每次渲染重复计算"问题)。
示例:
jsx
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); - 保存上一次的状态/属性。
示例:
jsx
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 钻取"。
示例:
jsx
// 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 做轻量级状态管理。
示例:
jsx
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; - 注意:内部代码会阻塞浏览器绘制,避免做耗时操作。
示例:
jsx
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
jsx
// 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
jsx
// 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:代码分割(按需加载组件)
核心场景:减小首屏加载体积,只加载当前页面需要的组件(比如路由懒加载)。
示例:
jsx
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 简化不可变操作:
jsx
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 版本)。
示例:
jsx
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)遮挡。
示例:
jsx
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); - 结合实际场景(比如表单、列表、弹窗)练习,理解"为什么用"比"怎么用"更重要。