首页首页
前端
非前端
辅助
Github
前端
非前端
辅助
Github
  • 前端基础

    • HTML基础
    • CSS基础
    • JS基础
    • ES6基础
    • HTTP基础
    • 前端缓存
    • 页面性能
    • 数据结构基础
    • 我的文章
  • 前端进阶

    • Vue2基础
    • Vue2进阶
    • Vue3基础
    • Vue3进阶
    • React基础
    • React进阶
    • React18新特性
    • Vue和React对比
    • RN基础
    • RN环境搭建和打包发布
    • 打包工具
    • TS基础
    • Nuxt基础
    • 小程序基础
    • 微前端基础
    • uni-app基础
    • 业务相关
    • 低代码相关
  • 前端代码练习

    • CSS代码练习
    • JS代码练习
    • 算法代码练习
  • 前端代码技巧

    • 工具库
    • 工具函数
    • CSS动画库
    • CSS代码技巧
    • JS代码技巧
    • 项目技巧

1. React 介绍

React是UI库,专注于视图层。它用JSX将HTML和JavaScript结合,提供最大的灵活性。React的Hooks和函数组件让逻辑复用更方便,但需要自己选择路由、状态管理等生态。

一、什么是 JSX?

JSX (JavaScript XML) 是 JavaScript 的语法扩展,它允许开发者在 JavaScript 代码中编写类似 HTML 的结构。

class App extends React.Component {
  render() {
    return(
      <div>
        <h1>{'Welcome to React world!'}</h1>
      </div>
    )
  }
}

以上示例 render 方法中的 JSX 将会被转换为以下内容:

React.createElement("div", null, React.createElement(
  "h1", null, 'Welcome to React world!'));

二、React开发是否必须使用 JSX?

答案是不必须​​,但强烈推荐使用。以下是详细分析

不使用 JSX 的替代方案

使用纯 JavaScript 调用 React.createElement:

// 用 JSX 编写的组件
function HelloJSX() {
  return <div className="greeting">Hello</div>;
}

// 等效的不使用 JSX 写法
function HelloNoJSX() {
  return React.createElement(
    'div',
    { className: 'greeting' },
    'Hello'
  );
}

2.React 创建组件的方式⭐️

一、函数组件(Function Components)​

  • 简介​​:通过 JavaScript 函数定义的组件,是 React 推荐的主流方式(尤其是配合 Hooks 后)。
  • ​​特点​​:
    • 简洁,适合无状态或使用 Hooks 管理状态的组件。
    • 性能较好(无类组件的实例化开销)。
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
// 或使用箭头函数
const Welcome = (props) => <h1>Hello, {props.name}</h1>;

二、​类组件(Class Components)​

  • ​简介​​:通过 ES6 类定义的组件,是 React 早期的主要方式。
  • ​特点​​:
    • 可以维护自身状态(this.state)和生命周期方法。
    • 需要显式绑定事件处理函数的 this。
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

三. ​​React Hooks(函数组件的增强)​

  • 简介​​:Hooks 允许函数组件使用状态和其他 React 特性(如生命周期)。
  • 常用 Hooks​​:
    • useState:管理组件状态。
    • useEffect:处理副作用(替代生命周期)。
import { useState } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

四. 高阶组件(HOC, Higher-Order Component)​

  • ​简介​​:一种复用组件逻辑的模式(接收组件,返回增强后的组件)。
  • ​​用途​​:例如权限控制、日志记录等跨组件逻辑。
  • 核心特点
    • ​不修改原组件​​,而是通过组合的方式增强功能
    • ​​透传 props​​(需要手动处理 props 传递)
    • 通常以 with 开头命名(如 withRouter)
function withLoading(WrappedComponent) {
  return function(props) {
    const [loading, setLoading] = useState(true);
    useEffect(() => {
      // 模拟数据加载
      setTimeout(() => setLoading(false), 1000);
    }, []);
    return loading ? <div>Loading...</div> : <WrappedComponent {...props} />;
  };
}

// 使用
const EnhancedComponent = withLoading(MyComponent);

五. Render Props​

  • ​​简介​​:组件通过一个名为 render(或其他名称)的 prop 接收渲染逻辑函数。
  • ​注意事项
    • ​可以使用任意名称​​,不一定是 render(如 children)
    • ​注意性能优化​​,避免不必要的重新渲染
    • ​与 React.memo 结合使用时需小心​
<DataProvider render={data => (
  <ChildComponent data={data} />
)}/>

实现示例

function MouseTracker(props) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove}>
      {props.render(position)}
    </div>
  );
}

// 使用
<MouseTracker render={({x, y}) => (
  <div>
    Mouse position: {x}, {y}
  </div>
)}/>

如何选择哪种方式创建组件?

  • 优先使用函数组件 + Hooks​​:现代 React 开发的主流方式,代码更简洁。
  • ​​类组件​​:仅在维护旧项目或需要 Error Boundaries(React 的错误捕获组件,生周周期的componentDidCatch) 时使用。
  • ​高阶组件/Render Props​​:复杂逻辑复用时考虑。
  • 随着 React Hooks 的普及,许多 HOC 和 Render Props 的场景可以用自定义 Hook 更简洁地实现:
// 自定义 Hook 替代 MouseTracker 的 Render Props
function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);
  return position;
}

// 使用
function App() {
  const { x, y } = useMousePosition();
  return <div>Mouse position: {x}, {y}</div>;
}

3.条件渲染⭐️

一、if/else 语句

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <UserGreeting />;
  } else {
    return <GuestGreeting />;
  }
}
  • 特点​​:
    • 适合较大的条件分支
    • 不能在 JSX 中直接使用

二、三元运算符

function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? <UserGreeting /> : <GuestGreeting />}
      <p>You are {isLoggedIn ? 'logged in' : 'a guest'}.</p>
    </div>
  );
}
  • 特点​​:
    • 可以直接嵌入 JSX
    • 嵌套过多会降低可读性

三、逻辑与运算符 (&&)

function Mailbox({ unreadMessages }) {
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 && (
        <h2>You have {unreadMessages.length} unread messages.</h2>
      )}
    </div>
  );
}
  • 特点​​:
    • 注意:左侧表达式必须为布尔值(避免 0 && <Component /> 的问题)
    • 嵌套过多会降低可读性

4.在哪个生命周期发起Ajax请求⭐️

一、类组件

1. componentDidMount​​ (最常用)

  • 组件挂载完成后立即调用
  • 适合初始数据加载
class MyComponent extends React.Component {
  componentDidMount() {
    fetch('/api/data')
      .then(res => res.json())
      .then(data => this.setState({ data }));
  }
}

2. componentDidUpdate​​ (用于props/state变化时)

  • 在更新发生后立即调用
  • 适合根据props变化重新获取数据
class MyComponent extends React.Component {
  componentDidUpdate(prevProps) {
    if (this.props.userId !== prevProps.userId) {
      fetch(`/api/users/${this.props.userId}`)
        .then(res => res.json())
        .then(user => this.setState({ user }));
    }
  }
}

二、函数组件,使用 useEffect Hook

1. ​​初始数据加载​​ (相当于componentDidMount)

function MyComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []); // 空依赖数组表示只在挂载时运行一次
}

2. props变化时重新获取数据​​ (相当于componentDidUpdate)

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser);
  }, [userId]); // userId变化时重新运行
}

5.最外层可以不包裹 div 标签

一、React Fragments(推荐方案)

React 16.2+ 提供了专门的 Fragment 语法:

// 简写语法(最常用)
const Component = () => (
  <>
    <ChildA />
    <ChildB />
    <ChildC />
  </>
);
// 完整写法
const Component = () => (
  <React.Fragment>
    <ChildA />
    <ChildB />
    <ChildC />
  </React.Fragment>
);

二、数组返回(React 16+)

const Component = () => [
  <ChildA key="a" />,
  <ChildB key="b" />,
  <ChildC key="c" />
];
  • 注意​​:
    • 必须为每个元素添加 key
    • 某些情况下样式处理可能更复杂

6.setState 传参的方式⭐️

一. 类组件中的 setState

1. 基本用法 - 直接传入新状态对象

this.setState({ count: 1 });

2. 基于前一个状态的更新 - 传入函数

this.setState((prevState, props) => {
  // prevState 是之前的状态
  // props 是当前的 props
  return { count: prevState.count + 1 };
});
  • 使用场景​​:
    • 当新状态依赖于前一个状态时
    • 避免状态更新合并导致的错误

3. 第二个参数 - 回调函数

this.setState(
  { count: 1 },
  () => {
    // 状态更新完成后的回调
    console.log('状态已更新', this.state.count);
  }
);

二. 函数组件中的 useState

1. 基本用法 - 直接传入新值

const [count, setCount] = useState(0);
// 直接设置新值
setCount(1);

2. 基于前一个状态的更新 - 传入函数

setCount(prevCount => prevCount + 1);
  • 使用场景​​:
    • 当新状态依赖于前一个状态时
    • 在闭包中获取最新状态值

三. 对象状态更新的注意事项

1. 合并更新(类组件)

state = { name: 'John', age: 25 };
// 只更新 age,name 保持不变
this.setState({ age: 26 });

2. 展开运算符更新对象(函数组件)

const [user, setUser] = useState({ name: 'John', age: 25 });
// 更新 age,保留其他属性
setUser(prevUser => ({ ...prevUser, age: 26 }));

五. 常见问题解决方案

1:连续多次 setState 不生效

// 函数组件中
const [count, setCount] = useState(0);
const handleClick = () => {
  setCount(count + 1);
  setCount(count + 1); // 不会累加
};

正确解决方案​​:

const handleClick = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1); // 现在会正确累加
};

7.setState同步还是异步⭐️

正常情况下:setState 是异步的​

在 ​​React 的​生命周期或合成事件​(如 onClick、onChange)​​ 中调用 setState 时,React 会​​批量处理更新​​​,表现为​​异步​​行为:

class Example extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    console.log("Before setState:", this.state.count); // 0
    this.setState({ count: this.state.count + 1 });
    console.log("After setState:", this.state.count); // 0(仍未更新)
  };

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}
  • 原因​​:React 会合并多个 setState 调用,并在稍后的某个时间点统一更新状态(​​批量更新​​)。
  • React 为什么设计为异步?​​
    • ​性能优化​​:避免频繁渲染,合并多个 setState 减少不必要的 re-render。
    • ​​保证一致性​​:在事件处理中,确保所有 setState 调用完成后才触发渲染,避免中间状态不一致。

特殊情况:setState 是同步的​

在某些情况下,setState 会​​立即执行​​,表现为同步行为:

  • 在React 16+的Fiber架构中:
    • 即使同步更新也不会立即刷新DOM
    • 但state的值会立即更新(可同步获取)
    • DOM更新仍然会被合理调度

​(1) 在 setTimeout、Promise、原生DOM事件中调用​

class Example extends React.Component {
  state = { count: 0 };

  componentDidMount() {
    // 原生事件 - 同步更新
    document.getElementById('btn').addEventListener('click', () => {
      this.setState({ count: this.state.count + 1 });
      console.log(this.state.count); // 输出新值
    });
  }

  handleClick = () => {
    setTimeout(() => {
      console.log("Before setState:", this.state.count); // 0
      this.setState({ count: this.state.count + 1 });
      console.log("After setState:", this.state.count); // 1(同步更新)
    }, 0);
  };

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}
  • ​原因​​:setTimeout 脱离了 React 的批处理机制,导致 setState 立即执行。

(2) 使用 ReactDOM.flushSync 强制同步更新

import { flushSync } from "react-dom";

class Example extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    flushSync(() => {
      this.setState({ count: 1 });
    });
    console.log("After flushSync:", this.state.count); // 1(同步)
  };

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}
  • 用途​​:在需要立即获取最新状态时使用(如动画、测量布局)。

8.获取 setState 更新后的值

一、class组件

1. setState 回调函数​(推荐)

this.setState(
  { count: this.state.count + 1 },
  () => {
    // 在这里可以获取更新后的state
    console.log('更新后的值:', this.state.count);
    // 可以在这里执行依赖于新state的操作
  }
);

2. 使用componentDidUpdate生命周期

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    console.log('count已更新:', this.state.count);
    // 可以在这里执行副作用操作
  }
}

3. 使用函数式setState(确保基于最新状态)

this.setState(prevState => {
  const newCount = prevState.count + 1;
  // 这里可以立即使用新值进行计算
  console.log('计算中的新值:', newCount);
  return { count: newCount };
});

二、函数组件

1. 使用useEffect Hook

import { useState, useEffect } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('count已更新:', count);
    // 这里可以执行副作用操作
  }, [count]); // 依赖count变化
  return (
    <button onClick={() => setCount(c => c + 1)}>
      点击我: {count}
    </button>
  );
}

2. 使用useState的函数式更新

const [count, setCount] = useState(0);

const increment = () => {
  setCount(prevCount => {
    const newCount = prevCount + 1;
    console.log('新值:', newCount); // 立即获取新值
    return newCount;
  });
};

三、强制同步获取更新后的值(不推荐)

1. 使用ReactDOM.flushSync(React 18+)

import { flushSync } from 'react-dom';

// 强制同步更新
flushSync(() => {
  this.setState({ count: 42 });
});
console.log(this.state.count); // 立即获取新值

2. 在setTimeout/Promise/原生事件中

setTimeout(() => {
  this.setState({ count: 100 });
  console.log(this.state.count); // 同步获取
}, 0);

9.React Router(V6)

一、核心概念​

1. 路由类型

生命周期阶段选项式 API
BrowserRouter基于 HTML5 History API(推荐)
HashRouter基于 URL Hash(兼容旧浏览器)
MemoryRouter测试或非浏览器环境(如 React Native)

2. 基础组件​

组件​​ ​作用​
<Routes>路由容器(v6 新增,替代 <Switch>)
<Route>定义路由规则
<Link> / <NavLink>导航链接(NavLink 支持高亮)
<Outlet>嵌套路由的占位符
useNavigate编程式导航(替代 useHistory)
useParams获取动态路由参数

二、基本示例​

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="*" element={<NotFound />} /> {/* 404 页面 */}
      </Routes>
    </BrowserRouter>
  );
}

三、嵌套路由​

1. 动态路由参数​

<Route path="/users/:id" element={<UserDetail />} />
// 在组件中获取参数
function UserDetail() {
  const { id } = useParams();
  return <div>User ID: {id}</div>;
}

2. 配置嵌套路由​

<Route path="/dashboard" element={<Dashboard />}>
  <Route path="profile" element={<Profile />} />
  <Route path="settings" element={<Settings />} />
</Route>
// Dashboard.jsx
function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <nav>
        <Link to="profile">Profile</Link>
        <Link to="settings">Settings</Link>
      </nav>
      <Outlet /> {/* 子路由将渲染在这里 */}
    </div>
  );
}

3. 默认嵌套路由​

<Route path="/dashboard" element={<Dashboard />}>
  <Route index element={<DashboardHome />} /> {/* 默认子路由 */}
  <Route path="profile" element={<Profile />} />
</Route>

四、编程式导航

1. 使用 useNavigate

import { useNavigate } from "react-router-dom";
function Login() {
  const navigate = useNavigate();
  const handleLogin = () => {
    navigate("/dashboard", { replace: true }); // replace 替换当前历史记录
  };
  return <button onClick={handleLogin}>Login</button>;
}

2. 导航传参​

// 传递 state
navigate("/user", { state: { from: "/login" } });
// 接收 state
const location = useLocation();
console.log(location.state?.from); // "/login"

五、路由守卫(权限控制)​

1. 封装 ProtectedRoute​

function ProtectedRoute({ children }) {
  const { user } = useAuth(); // 假设有权限钩子
  const location = useLocation();
  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return children;
}
// 使用
<Route
  path="/admin"
  element={
    <ProtectedRoute>
      <AdminPanel />
    </ProtectedRoute>
  }
/>

2. 全局路由拦截​

function RouterWrapper() {
  const location = useLocation();
  const { isAuthenticated } = useAuth();
  if (!isAuthenticated && location.pathname !== "/login") {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return <Outlet />;
}

// 在根路由中使用
<Route element={<RouterWrapper />}>
  <Route path="/" element={<Home />} />
  <Route path="/admin" element={<AdminPanel />} />
</Route>

常见问题​

1. 如何实现路由懒加载?​

const LazyComponent = React.lazy(() => import("./Component"));

<Route
  path="/lazy"
  element={
    <React.Suspense fallback={<Spinner />}>
      <LazyComponent />
    </React.Suspense>
  }
/>

2. 如何监听路由变化?​

useEffect(() => {
  const unlisten = navigation.listen((location) => {
    console.log("Route changed to:", location.pathname);
  });
  return () => unlisten(); // 清理监听
}, [navigation]);

10.高阶组件(HOC)

高阶组件(Higher-Order Component,HOC)是 React 中用于​​复用组件逻辑​​的高级技术,它本质上是一个函数,接收一个组件并返回一个新的增强组件。

一、核心概念

1. 基本结构

const EnhancedComponent = higherOrderComponent(WrappedComponent);

2. 类比高阶函数

就像高阶函数接收/返回函数一样,HOC 接收/返回组件:

// 高阶函数示例
const twice = f => x => f(f(x));
// 高阶组件示例
const withLogging = Component => props => {
  console.log('Rendered:', Component.name);
  return <Component {...props} />;
};

二、实现方式

1. 属性代理(Props Proxy)

// 最常见的 HOC 形式:
function withExtraProps(WrappedComponent) {
  return function EnhancedComponent(props) {
    const extraProps = { user: currentUser };
    return <WrappedComponent {...props} {...extraProps} />;
  };
}

2. 继承反转(Inheritance Inversion)

// 通过继承操纵渲染:
function withTheme(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      return (
        <div style={{ color: this.props.themeColor }}>
          {super.render()}
        </div>
      );
    }
  };
}

三、常见应用场景

1. 属性增强

const withUser = Component => props => (
  <Component {...props} user={getCurrentUser()} />
);

2. 逻辑复用

const withLoading = Component => props => 
props.isLoading ? <Spinner /> : <Component {...props} />;

3. 状态抽象

const withToggle = Component => {
  return class extends React.Component {
    state = { isOn: false };
    toggle = () => this.setState({ isOn: !this.state.isOn });
    
    render() {
      return (
        <Component 
          {...this.props} 
          isOn={this.state.isOn} 
          toggle={this.toggle}
        />
      );
    }
  };
};

11.React.memo

React.memo 是一个用于优化功能组件性能的高阶组件。如果一个组件被包裹在 React.memo 中,当该组件的 props 不变时,React 会跳过对该组件及其子组件的渲染。

memo(Component, arePropsEqual?) 
  • Component:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件。

  • 可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true。否则返回 false。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop。(浅比较)

简单的使用例子

import React, { useState } from 'react';

// 定义一个计算开销比较大的组件
const ExpensiveComponent = React.memo(({ value }) => {
    console.log('ExpensiveComponent rendered');
    // 模拟一些昂贵的计算
    const result = value * 2; // 这是一个简单的计算,实际上可以是更复杂的渲染逻辑
    return <div>Result: {result}</div>;
});

const App = () => {
    const [count, setCount] = useState(0);
    const [value, setValue] = useState(1);
    return (
        <div>
            <h1>Counter: {count}</h1>
            <button onClick={() => setCount(count + 1)}>Increment Counter</button>

            <h2>Value: {value}</h2>
            <button onClick={() => setValue(value + 1)}>Increment Value</button>

            {/* 只有当 value 改变时,ExpensiveComponent 才会重新渲染 */}
            <ExpensiveComponent value={value} />
        </div>
    );
};
export default App;
  • 当点击 “Increment Counter” 按钮时,count 发生变化,组件 App 将重新渲染,但 ExpensiveComponent 因为 value 没有变化而不会重新渲染。这归功于 React.memo 的性能优化。

  • 只有当点击 “Increment Value” 按钮时,value 改变,ExpensiveComponent 才会重新渲染。

memo可能无效?

import React, { memo, useState } from 'react';
// 子组件 - 使用 memo
const Child = memo(({ onClick }) => {
  console.log('🔵 Child 重新渲染了!');
});

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);
  // ❌ 不使用 useCallback
  const handleClick = () => {
    console.log('按钮被点击');
  };
  console.log('🟡 Parent 重新渲染了!');
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        增加计数: {count}
      </button>
      <Child onClick={handleClick} />
    </div>
  );
};

结果:每次点击"增加计数"按钮,控制台都会输出:
🟡 Parent 重新渲染了!
🔵 Child 重新渲染了!

⚡ 根本原因:函数引用每次都是新的

// 每次 Parent 重新渲染时
const Parent = () => {
  // 这行代码会执行
  const handleClick = () => {  // <- 每次都是全新的函数!
    console.log('按钮被点击');
  };
  
  // 然后传递给 Child
  return <Child onClick={handleClick} />;
};
  • 关键理解:
    • 函数是引用类型(对象)
    • 即使函数体完全一样,每次重新创建的函数都是不同的引用
    • React.memo 进行的是浅比较(===比较)

🔧 解决方案

  • 方案1:将函数移到组件外部
// ✅ 将函数移出组件
const handleClick = () => {
  console.log('点击');
};
const Parent = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>计数: {count}</button>
      <Child onClick={handleClick} />  {/* 引用稳定 */}
    </div>
  );
};
  • 方案2:使用 useCallback(推荐)
// ✅ 使用 useCallback
import React, { memo, useState, useCallback } from 'react';
const Parent = () => {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => {
    console.log('点击');
  }, []);  // 依赖为空,函数引用稳定
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>计数: {count}</button>
      <Child onClick={handleClick} />  {/* 不会重新渲染 */}
    </div>
  );
};

使用 React.memo 的场景

  • 场景1:昂贵的渲染组件,如图表、长列表等
// ✅ 计算密集型组件
const ExpensiveChart = memo(({ data }) => {
  console.log('📊 渲染复杂图表...');
  // 模拟复杂计算
  const result = useMemo(() => {
    return calculateChartData(data); // 耗时计算
  }, [data]);
  
  return <ComplexChart data={result} />;
});

// 父组件
const Dashboard = () => {
  const [filters, setFilters] = useState({});
  const [refreshKey, setRefreshKey] = useState(0);
  
  return (
    <div>
      <Filters onChange={setFilters} />
      <button onClick={() => setRefreshKey(k => k + 1)}>
        刷新界面
      </button>
      {/* 只有 data 变化时才重新渲染图表 */}
      <ExpensiveChart data={filters.chartData} />
    </div>
  );
};
  • 场景2:纯展示组件
// ✅ 纯展示组件,无状态
const UserProfile = memo(({ user, theme }) => {
  console.log('👤 渲染用户资料');
  return (
    <div className={`profile ${theme}`}>
      <Avatar src={user.avatar} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
      <Stats stats={user.stats} />
    </div>
  );
});

// 使用
const App = () => {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(initialUser);
  return (
    <>
      <ThemeToggle onChange={setTheme} />
      {/* 切换主题不会导致 UserProfile 重新渲染 */}
      <UserProfile user={user} theme={theme} />
    </>
  );
};

React.memo 是性能优化的工具,不是默认行为。只有在确实存在性能问题,且 memo 能解决时才使用。过早优化可能会导致代码复杂度增加,而收益甚微。

最后更新: 2026/1/8 20:58
Prev
Vue3进阶
Next
React进阶