React高级用法

一、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 高级用法的本质是:

  1. 复用:自定义 Hooks 实现逻辑复用,替代传统的 HOC/Render Props;
  2. 性能useCallback/useMemo/React.memo 减少无效渲染,React.lazy 分割代码;
  3. 解耦useContext/useReducer 简化状态管理,避免 props 钻取和冗余 state;
  4. 兼容:错误边界、Portals 解决特殊场景的 UI/交互问题;

学习建议

  • 先掌握基础 Hooks(useState/useEffect),再逐步学习进阶 Hooks;
  • 自定义 Hooks 是核心,从封装小逻辑(防抖、请求)开始,逐步抽象复杂逻辑;
  • 性能优化要"按需使用",不要过度优化(比如简单组件无需 useCallback);
  • 结合实际场景(比如表单、列表、弹窗)练习,理解"为什么用"比"怎么用"更重要。
相关推荐
How_doyou_do2 小时前
模态框与DOM,及React和Vue中的优化
前端·vue.js·react.js
....4922 小时前
el-select 下拉框支持线上 SVG + 本地图片图标 展示
前端·javascript·vue.js
L、2182 小时前
Flutter + OpenHarmony 分布式能力融合:实现跨设备 UI 共享与协同控制(终极篇)
javascript·分布式·flutter·ui·智能手机·harmonyos
fruge3 小时前
深入理解 JavaScript 事件循环:宏任务与微任务的执行机制
开发语言·javascript·ecmascript
Youyzq3 小时前
css样式用flex 布局的时候元素尺寸展示不对
前端·javascript·css
大雨倾城3 小时前
网页端和桌面端的electron通信Webview
javascript·vue.js·react.js·electron
亚洲小炫风3 小时前
React 分页轻量化封装
前端·react.js·前端框架
yilan_n3 小时前
【UniApp实战】手撸面包屑导航与路由管理 (拒绝页面闪烁)
前端·javascript·vue.js·uni-app·gitcode
Highcharts.js3 小时前
官方文档|Vue 集成 Highcharts Dashboards
前端·javascript·vue.js·技术文档·highcharts·看板·dashboards