React面试葵花宝典之三

时间:2025-03-05 07:01:50

71、React组件返回值

、在 React 中,组件的返回值取决于组件的类型,通常有以下几种情况:

1. 函数组件的返回值

函数组件返回的是 JSX 或 null

const MyComponent = () => {
  return <div>Hello, React!</div>;
};

如果需要条件渲染,可以返回 null

const MyComponent = ({ show }) => {
  return show ? <div>Visible</div> : null;
};

2. 类组件的返回值

类组件的 render 方法返回 JSX:

class MyComponent extends React.Component {
  render() {
    return <div>Hello, Class Component!</div>;
  }
}

同样可以返回 null

class MyComponent extends React.Component {
  render() {
    return null;
  }
}

3. Fragment(片段)

React 允许返回多个元素而不需要额外的 DOM 包裹:

const MyComponent = () => {
  return (
    <>
      <h1>Title</h1>
      <p>Description</p>
    </>
  );
};

<></>React.Fragment 的简写。

4. 数组(Fragments 之外的多元素返回)

可以返回数组来渲染多个元素:

const MyComponent = () => {
  return [
    <li key="1">Item 1</li>,
    <li key="2">Item 2</li>,
    <li key="3">Item 3</li>,
  ];
};

5. React.createElement

JSX 最终会被转换成 React.createElement

const MyComponent = () => {
  return React.createElement('div', null, 'Hello, React!');
};

6. Portal

如果需要将组件渲染到 document.body 之外的 DOM 节点,可以返回 ReactDOM.createPortal

import ReactDOM from 'react-dom';

const MyComponent = () => {
  return ReactDOM.createPortal(
    <div>Portal Content</div>,
    document.getElementById('portal-root')
  );
};

7. Suspense

如果组件使用 React.Suspense 进行懒加载,它的返回值可能是 Suspense 组件:

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

const MyComponent = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
};

总结

React 组件的返回值通常是:

  1. JSX 元素<div>...</div>
  2. React.Fragment<></>React.Fragment
  3. 数组(必须包含 key
  4. null(用于条件渲染)
  5. React.createElement(底层实现)
  6. Portal(使用 ReactDOM.createPortal
  7. Suspense 组件(用于懒加载)

这些返回值决定了组件如何在 React 组件树中渲染。

72.monorepo是什么用来做什么的

Monorepo是“monolithic repository”的缩写,也就是将多个项目或模块放在同一个代码仓库中。这和传统的每个项目一个仓库的方式不同。
为什么需要monorepo
大型项目、多包管理的库(如Babel、React)、需要跨团队协作的情况。
对比传统的多仓库方式,比如代码重复、依赖管理复杂、协作效率低等问题。Monorepo如何解决这些问题,比如共享代码、统一构建工具、简化依赖管理等。
Monorepo(单一代码仓库)是一种将多个项目或模块存储在同一个代码仓库中的代码管理策略。它的核心目标是简化跨项目协作、共享代码和统一工程化流程,尤其适合中大型团队或复杂项目。以下是其核心概念和用途的详细解析:


一、Monorepo 的核心特点

  1. 统一代码库

    • 所有相关项目(如前端、后端、工具库)存放在同一仓库中。

    • 示例目录结构:

      monorepo/
        ├── apps/
        │   ├── web-app/      # 前端应用
        │   └── mobile-app/   # 移动端应用
        ├── packages/
        │   ├── utils/        # 公共工具库
        │   └── ui-kit/       # 共享 UI 组件库
        └── package.json      # 根目录配置
      
  2. 共享依赖与代码

    • 模块间可直接引用本地代码,无需发布到包管理器(如 npm)。
    • 避免代码重复,例如多个项目共用工具函数或 UI 组件。
  3. 统一工具链

    • 集中管理构建、测试、代码规范等配置(如 ESLint、TypeScript、Jest)。
    • 一键执行跨项目操作(如同时构建所有应用)。
  4. 原子化提交(Atomic Commits)

    • 跨项目的代码变更可在一次提交中完成,确保版本一致性。
    • 便于追踪代码变更的影响范围。

二、Monorepo 的典型使用场景

  1. 多应用共享代码

    • 例如:Web 端、移动端、后台管理系统共用同一套业务逻辑或组件库。
  2. 微前端架构

    • 多个子应用(Micro Frontends)在同一个仓库中开发,便于协调和集成。
  3. 多包管理(Monorepo 的经典场景)

    • 开源项目如 BabelReactVue 3 使用 Monorepo 管理核心库、插件、文档等。
    • 示例:React 仓库包含 reactreact-domscheduler 等包。
  4. 全栈项目

    • 前后端代码同仓,方便接口联调和数据模型同步。

三、Monorepo 的优势

优势 说明
代码复用 跨项目共享代码,减少重复开发(如工具函数、组件、配置)。
依赖管理简化 统一管理第三方依赖版本,避免版本冲突。
跨项目重构 修改公共代码后,可立即验证所有依赖项目,降低破坏性变更风险。
统一 CI/CD 集中配置构建、测试、部署流程,提升自动化效率。
协作效率提升 开发者更容易理解项目全貌,减少跨仓库沟通成本。

四、Monorepo 的挑战与解决方案

挑战 解决方案
仓库体积膨胀 使用 Git 稀疏检出(Sparse Checkout)或按需克隆。
构建性能 增量构建(如 Turborepo)、缓存优化、任务并行化。
权限管理 通过目录级权限控制(如 Git Submodules 或自定义工具)。
工具链复杂度 使用成熟 Monorepo 工具(如 NxTurborepoRush)。

五、主流 Monorepo 工具

  1. Lerna

    • 传统 Monorepo 工具,适合多包发布(如 Babel 使用 Lerna)。
    • 需配合 Yarn Workspaces 或 npm Workspaces 使用。
  2. Nx

    • 功能强大,支持增量构建、依赖图可视化、分布式任务执行。
    • 适合大型企业级项目。
  3. Turborepo

    • 高性能构建缓存,无缝集成主流框架(Next.js、React Native)。
    • 配置简单,适合中小型项目。
  4. Yarn/PNPM Workspaces

    • 内置的轻量级 Monorepo 支持,管理依赖和跨包链接。

六、何时选择 Monorepo?

  • 适合场景

    • 项目间高度耦合,需要频繁共享代码。
    • 团队规模较大,需统一开发规范。
    • 希望简化依赖管理和跨项目协作。
  • 不适合场景

    • 小型独立项目,无需代码共享。
    • 团队技术栈差异大,难以统一工具链。

七、示例:React 的 Monorepo 结构

React 的仓库是经典案例,其结构如下:

react/
  ├── packages/
  │   ├── react/          # React 核心
  │   ├── react-dom/      # DOM 渲染器
  │   ├── scheduler/      # 调度器
  │   └── ...             # 其他子包
  ├── scripts/            # 构建脚本
  └── package.json        # 根目录配置

通过 Monorepo,React 团队可以同步更新核心库与相关模块,确保版本一致性。


总结

Monorepo 通过集中化管理代码共享,解决了多仓库协作的碎片化问题,尤其适合复杂项目或需要高度协作的团队。选择时需权衡项目规模、工具链支持与团队能力,合理使用工具(如 Turborepo 或 Nx)可大幅降低维护成本。

73.React中什么叫并发渲染具体表现是什么

并发渲染(Concurrent Rendering)是 React 18 引入的核心特性,它通过可中断的异步渲染机制,让 React 应用在处理复杂更新时保持高响应性。以下是其核心原理与具体表现:
并发渲染的本质是让 React 具备多任务协作能力
并发渲染通过时间切片,任务 优先级调度 ,可中断渲染,实现react的多任务协作能力,将多个任务实习切片,并按照优先级渲染,在处理高优先级人物时可以中断渲染


一、并发渲染的核心机制

  1. 时间切片(Time Slicing)

    • 将渲染任务拆分为多个可中断的微任务块(通常每块 5ms 左右)。
    • 浏览器主线程在每块任务之间可以处理更高优先级的操作(如用户点击、动画)。
  2. 任务优先级调度

    • 通过 Lane 模型 区分更新优先级(如用户输入 > 数据加载 > 非紧急渲染)。
    • 高优先级任务可“插队”低优先级任务,避免界面卡顿。
  3. 可中断的渲染流程

    • 渲染过程(Render Phase)可被中断,React 会暂存中间状态,后续恢复时继续处理。
    • 提交阶段(Commit Phase)保持同步,确保 DOM 更新原子性。

二、具体表现

1. 更流畅的交互体验
  • 输入响应优先
    当用户在输入框打字时,React 会优先处理输入事件,延迟其他低优先级渲染(如搜索结果列表的更新),确保输入不卡顿。

  • 示例

    function SearchBox() {
      const [query, setQuery] = useState('');
      const deferredQuery = useDeferredValue(query); // 延迟更新
    
      return (
        <>
          <input value={query} onChange={(e) => setQuery(e.target.value)} />
          <Results query={deferredQuery} /> // 低优先级渲染
        </>
      );
    }
    
2. 后台预渲染
  • 提前准备 UI
    在用户执行可能触发渲染的操作前(如悬停按钮),React 可提前在后台渲染内容,提升后续操作的响应速度。

  • 示例

    function App() {
      const [tab, setTab] = useState('home');
    
      return (
        <div>
          <button onMouseOver={() => startTransition(() => setTab('about'))}>
            About
          </button>
          <Suspense fallback={<Loader />}>
            <Content tab={tab} />
          </Suspense>
        </div>
      );
    }
    
3. 批量非紧急更新
  • 合并低优先级更新
    通过 startTransitionuseDeferredValue 将非紧急更新(如搜索建议、图表重绘)标记为“可延迟”,避免阻塞关键渲染。

  • 示例

    const [input, setInput] = useState('');
    const [search, setSearch] = useState('');
    
    const handleInput = (text) => {
      setInput(text); // 高优先级:立即更新输入框
      startTransition(() => {
        setSearch(text); // 低优先级:延迟更新搜索结果
      });
    };
    
4. 更智能的 Suspense 配合
  • 流式加载与错误边界
    结合 Suspense,并发渲染可实现逐步加载组件或数据,优先显示已就绪内容(如骨架屏),同时处理加载状态和错误。

三、与传统同步渲染的对比

场景 同步渲染 并发渲染
用户输入时触发渲染 输入卡顿,渲染阻塞事件循环 输入流畅,渲染任务被中断或延迟
同时处理多个更新 按顺序执行,可能产生界面冻结 按优先级调度,高优先级任务优先
复杂组件树渲染 长时间占用主线程,导致掉帧 分片执行,保持主线程可响应

四、开发者如何利用并发渲染

  1. 使用并发特性 API

    • startTransition:标记非紧急更新。
    • useTransition:获取过渡状态(如加载指示器)。
    • useDeferredValue:延迟派生值的更新。
  2. 优化组件性能

    • 通过 React.memouseMemo 减少不必要的渲染。
    • 拆分大型组件树,利用 Suspense 分块加载。
  3. 避免阻塞渲染

    • 避免在渲染阶段执行耗时操作(如复杂计算、同步网络请求)。

五、底层实现:Fiber 架构

  • 链表结构:Fiber 节点构成双向链表,支持渲染过程的暂停与恢复。
  • 工作循环:通过 requestIdleCallback(或 polyfill)在浏览器空闲时段执行任务。
  • 优先级标记:使用 31 位的 Lane 模型精细控制任务调度。

总结

并发渲染的本质是让 React 具备多任务协作能力,通过智能调度渲染任务,在保持界面流畅的同时处理复杂更新。其表现包括:

  • 用户交互无卡顿
  • 高优先级任务即时响应
  • 后台预加载与延迟渲染
  • 更平滑的过渡效果

74.React Reconciler 协调器

React Reconciler是React内部的一个核心部分,负责协调(reconciliation)过程。协调是React用来比较新旧虚拟DOM树,找出差异并高效更新实际DOM的算法。React的协调器不仅仅处理虚拟DOM,还涉及到组件的生命周期、状态更新等。
Reconciler的主要职责。可能包括:

  1. 虚拟DOM的创建和更新:当组件的状态或props变化时,Reconciler会生成新的虚拟DOM树,并与旧的进行比较,找出需要更新的部分。
  2. Diff算法:这是协调过程中的关键,用来高效地找出差异。React的Diff算法基于两个假设:不同类型的元素会产生不同的树,以及通过key属性来识别稳定的子元素。
  3. 调度更新:React可能会将更新分成多个小任务,使用时间分片(time slicing)和优先级调度(如并发模式中的Suspense)来避免阻塞主线程,保持应用的响应性。
  4. 副作用处理:比如处理组件的生命周期方法(componentDidMount, componentDidUpdate等),或者函数组件中的useEffect钩子。
  5. 当setState被调用时,Reconciler会创建一个新的虚拟DOM树,然后与旧的进行比较,找出需要更新的节点,生成一个副作用列表(effect list),然后提交(commit)这些变更到实际的DOM中。
  6. 调度器(Scheduler)和Reconciler。调度器负责安排任务的优先级和执行时机,而Reconciler负责处理组件的更新和协调。
  7. React Reconciler 是 React 的核心模块之一,负责协调(Reconciliation)过程,即高效更新 UI 的关键机制。以下是其核心要点:

1. 核心职责

  • 虚拟 DOM 的协调:当组件状态或 props 变化时,Reconciler 生成新的虚拟 DOM 树,并与旧树对比(Diff 算法),找出最小变更集。

  • Diff 算法优化:基于两个假设:

    • 类型差异:不同类型的元素(如 <div> 变为 <span>)会销毁旧子树并重建。
    • Key 稳定性:通过 key 标识子元素,减少不必要的节点复用错误。
  • 调度与优先级:在并发模式下,将更新拆分为可中断的“任务”,优先处理高优先级更新(如用户交互),避免阻塞主线程。

  • 副作用管理:处理生命周期(如 componentDidUpdate)和 Hook 副作用(如 useEffect),生成并提交变更到真实 DOM。


2. Fiber 架构

React 16 引入的 Fiber 重构了 Reconciler,使其支持:

  • 增量渲染:将渲染拆分为多个可中断/恢复的“工作单元”(Fiber 节点),提升大型应用响应速度。
  • 并发模式:通过时间分片(Time Slicing)和任务优先级(Lane 模型)实现异步可中断更新。
  • 双向链表结构:Fiber 节点构成链表,支持深度优先遍历的暂停与回溯,优化协调过程。

3. 协调流程

  1. Render 阶段(可中断):

    • 对比新旧 Fiber 树,生成副作用列表(如插入、更新、删除)。
    • 使用 Diff 算法标记变更,不直接操作 DOM。
  2. Commit 阶段(不可中断):

    • 遍历副作用列表,批量更新真实 DOM。
    • 触发生命周期和 Hook 的副作用(如 useLayoutEffect)。

4. 与渲染器解耦

  • Reconciler 是平台无关的抽象层,与具体渲染器(如 React DOM、React Native)分离。
  • 渲染器负责将 Reconciler 的指令转换为平台特定操作(如 DOM 修改或原生组件更新)。

5. 性能优化

  • Key 的合理使用:列表渲染时,唯一且稳定的 key 减少不必要的元素重建。
  • 批量更新:合并多次 setState,避免重复渲染。
  • 组件优化:通过 React.memoshouldComponentUpdate 跳过无关组件的协调。

示例:Key 的重要性

// 无 key:列表顺序变化可能导致状态错乱(如输入框内容)
{items.map(item => <div>{item.text}</div>)}

// 有 key:React 正确识别元素,优化复用
{items.map(item => <div key={item.id}>{item.text}</div>)}

总结

React Reconciler 通过虚拟 DOM 的智能对比、Fiber 的异步调度机制,以及跨平台渲染的抽象,实现了高效、灵活的 UI 更新。理解其原理有助于编写高性能 React 应用(如合理使用 key、优化渲染逻辑)。

75.React——Scheduler调度器

在 React 中,Scheduler 是一个用于协调任务调度的库,它管理不同优先级的任务,以确保 React 在主线程上的渲染不会阻塞关键用户交互。它是 React 并发模式(Concurrent Mode)的核心部分。


1. Scheduler 的作用

Scheduler 主要用于任务优先级管理,它可以:

  • 让高优先级任务(如用户输入)优先执行
  • 让低优先级任务(如页面渲染)延后执行
  • 让任务分批执行,防止长时间阻塞主线程

2. Scheduler API

React 通过 scheduler 包提供了一些 API 来控制任务的调度:

(1)scheduleCallback

用于调度一个任务,并根据优先级决定何时执行:

import { unstable_scheduleCallback, unstable_NormalPriority } from 'scheduler';

unstable_scheduleCallback(unstable_NormalPriority, () => {
  console.log('执行一个普通优先级的任务');
});

可选优先级:

  • unstable_ImmediatePriority(最高,立即执行)
  • unstable_UserBlockingPriority(次高,用户交互)
  • unstable_NormalPriority(默认)
  • unstable_LowPriority(低)
  • unstable_IdlePriority(最低,浏览器空闲时执行)

(2)shouldYield

用于检查是否应该让出控制权,避免长时间占用主线程:

import { unstable_shouldYield } from 'scheduler';

function heavyTask() {
  while (true) {
    if (unstable_shouldYield()) {
      console.log('主线程需要执行更高优先级任务,暂停当前任务');
      break;
    }
    // 执行计算任务...
  }
}

(3)getCurrentPriorityLevel

获取当前执行任务的优先级:

import { unstable_getCurrentPriorityLevel } from 'scheduler';

console.log(unstable_getCurrentPriorityLevel()); // 返回当前任务的优先级

3. Scheduler 在 React 中的应用

(1)React 并发模式(Concurrent Mode)

Scheduler 主要用于 React 并发模式,以支持时间切片(Time Slicing) ,使得 React 渲染不会长时间阻塞主线程。例如:

function handleClick() {
  React.startTransition(() => {
    setState(expensiveComputation());
  });
}

startTransition 内部,React 可能会使用 Scheduler 让任务分批执行。

(2)提高用户体验

Scheduler 可以让高优先级任务(如输入响应)优先执行,避免 UI 卡顿。例如:

import { unstable_scheduleCallback, unstable_UserBlockingPriority } from 'scheduler';

input.addEventListener('input', (event) => {
  unstable_scheduleCallback(unstable_UserBlockingPriority, () => {
    console.log('处理输入事件');
  });
});

4. 总结

  1. Scheduler 负责 React 任务调度,优化渲染任务的执行顺序。
  2. 它提供任务优先级管理,如 ImmediatePriorityUserBlockingPriority
  3. 它支持时间切片(Time Slicing) ,避免长时间阻塞主线程,提高 UI 流畅度。
  4. Scheduler 在 React 并发模式中发挥重要作用,比如 startTransition 内部使用了它。

如果面试官深入问,你可以补充 Scheduler 的工作原理、如何配合 requestIdleCallback 以及它在 React 18 并发渲染中的作用。

76.React18新特性——Concurrent Mode 并发模式 多个任务交替进行

React 18 的 Concurrent Mode(并发模式) 是 React 核心架构的重大升级,旨在通过可中断、优先级驱动的渲染机制,提升复杂应用的响应速度和用户体验。以下是其核心要点与面试回答方向:


一、核心目标

  • 解决阻塞渲染问题:传统同步渲染会阻塞主线程,导致用户交互卡顿(如输入延迟)。并发模式通过时间切片(Time Slicing) 将渲染任务拆分为可中断的微任务,优先响应用户操作。
  • 智能调度更新:根据任务优先级(如用户输入 > 数据加载 > 动画)动态调整渲染顺序,确保高优先级任务即时处理。
  • 支持后台预渲染:提前准备即将显示的 UI(如悬停时预加载组件),减少用户感知的等待时间。

二、关键特性与 API

1. 自动批处理(Automatic Batching)
  • 机制:React 18 默认将多个状态更新合并为单一渲染(包括 Promise、setTimeout 等异步操作),减少不必要的重复渲染。

  • 示例

    // React 17:setTimeout 内更新会触发两次渲染
    setTimeout(() => {
      setCount(1);
      setFlag(true);
    }, 1000);
    
    // React 18:自动批处理,仅一次渲染
    
2. 过渡更新(Transitions)
  • APIstartTransitionuseTransition

  • 作用:区分紧急更新(如输入反馈)与非紧急更新(如搜索结果),延迟后者以避免阻塞交互。

  • 示例

    const [isPending, startTransition] = useTransition();
    
    const handleSearch = (query) => {
      // 紧急:立即更新输入框
      setInput(query);
      // 非紧急:延迟更新搜索结果
      startTransition(() => {
        setSearchQuery(query);
      });
    };
    
    return <div>{isPending ? 'Loading...' : <Results />}</div>;
    
3. 延迟值(Deferred Values)
  • APIuseDeferredValue

  • 作用:派生值的“降级”更新,保持界面响应。适用于耗时计算或大型列表渲染。

  • 示例

    const deferredList = useDeferredValue(heavyList);
    return <List items={deferredList} />; // 延迟渲染,允许高优先级任务插队
    
4. Suspense 增强
  • 服务端渲染(SSR)支持:结合 lazySuspense 实现流式 HTML 传输,逐步加载组件并优先显示已就绪内容。

  • 示例

    <Suspense fallback={<Skeleton />}>
      <Comments /> // 异步加载的评论组件
    </Suspense>
    
5. 新的 Root API
  • createRoot:替代 ReactDOM.render,启用并发特性。

    // React 17
    ReactDOM.render(<App />, document.getElementById('root'));
    
    // React 18
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);
    

三、底层原理

  • Fiber 架构升级:利用双向链表结构(Fiber 节点)实现可中断渲染,支持回溯和任务恢复。
  • Lane 模型:通过 31 位二进制位表示任务优先级,精细控制调度逻辑。
  • 协作式多任务:在浏览器空闲时段执行渲染任务(类似 requestIdleCallback),避免主线程阻塞。

四、性能优化场景

  1. 输入防抖:用户连续输入时,优先渲染输入框内容,延迟处理搜索请求。
  2. 页面切换预加载:鼠标悬停在导航按钮时,后台预加载目标页面的组件或数据。
  3. 大型列表渲染:使用 useDeferredValue 分块渲染,避免界面冻结。

五、迁移与注意事项

  • 渐进式采用:并发特性可逐步集成,无需重写整个应用。
  • 副作用处理:注意 useEffect 和生命周期钩子可能因渲染中断而多次执行,需确保幂等性。
  • 严格模式:React 18 的严格模式会故意双渲染组件,帮助发现副作用问题。

六、与传统模式对比

场景 传统模式 并发模式
用户输入响应 可能被长任务阻塞 高优先级更新优先处理,输入流畅
多个更新竞争 顺序执行,可能导致卡顿 按优先级调度,动态插队
复杂组件树渲染 长时间占用主线程 分片渲染,保持界面可交互

总结

React 18 的 Concurrent Mode 通过可中断渲染智能调度,使应用具备“多任务处理”能力,显著提升用户体验。开发者应重点掌握:

  • 核心 APIstartTransitionuseDeferredValueSuspense
  • 优化场景:区分紧急/非紧急更新,预加载关键资源
  • 底层机制:Fiber 架构与 Lane 模型的作用

理解并发模式不仅有助于应对面试,更能为构建高性能 React 应用提供坚实基础。

77.React18新特性——自动批处理 Automatic Batchin

React 18 新特性 —— 自动批处理(Automatic Batching)

自动批处理(Automatic Batching) 是 React 18 引入的一项优化,它可以自动地将多个状态更新合并,减少渲染次数,提高性能。


1. 什么是批处理(Batching)?

在 React 18 之前,批处理只发生在React 事件处理函数内部:

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleClick = () => {
    setCount(count + 1);
    setFlag(!flag);
    // 在 React 17 及之前:只触发 1 次重新渲染(批处理)
  };

  console.log('组件渲染'); // 只会执行一次
  return <button onClick={handleClick}>{count.toString()}</button>;
}

但在异步任务(如 setTimeoutPromise)中,React 17 及之前不会进行批处理:

setTimeout(() => {
  setCount(count + 1);
  setFlag(!flag);
  // 在 React 17 及之前:触发 2 次重新渲染
}, 1000);

2. React 18 自动批处理(Automatic Batching)

在 React 18 中,所有的状态更新都会自动批处理,无论它们在哪个上下文中触发!
包括异步代码setTimeoutPromisefetch 等)。

示例:React 18 自动批处理

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  setTimeout(() => {
    setCount((prev) => prev + 1);
    setFlag((prev) => !prev);
    // React 18:只会触发 1 次渲染
  }, 1000);

  console.log('组件渲染'); // React 18 只执行 1 次
  return <div>{count.toString()}</div>;
}

React 17 及之前:触发 2 次渲染
React 18:触发 1 次渲染


3. 取消自动批处理

如果在某些情况下你不想让 React 自动合并更新,可以使用 flushSync 强制立即更新:

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount((c) => c + 1);
  });
  flushSync(() => {
    setFlag((f) => !f);
  });
  // 这里会触发 2 次渲染
}

flushSync 适用于:

  • 需要在某个状态更新后立即获取最新 DOM 的情况
  • 需要精确控制渲染顺序的情况

4. 自动批处理的优势

减少不必要的渲染,提高性能
提升开发体验,减少状态更新导致的 UI 闪烁
统一行为,在同步和异步任务中都适用


5. 总结

  • 批处理(Batching) :React 会合并多个状态更新,减少渲染次数。
  • React 18 以前:仅在 React 事件处理函数中批处理,异步任务不会批处理。
  • React 18 自动批处理:所有任务(同步 & 异步)都批处理,减少不必要的渲染。
  • flushSync:如果需要强制立即更新,可以使用 flushSync

这个特性让 React 代码更高效,避免不必要的重渲染,是 React 18 重要的优化之一!

78.React18新特性——流式 SSR

React 18 新特性 —— 流式 SSR(Streaming Server-Side Rendering)

流式 SSR(Streaming Server-Side Rendering) 是 React 18 提供的一种优化的服务端渲染(SSR)方式,它允许服务器在数据准备好后逐步发送 HTML,而不是等待所有数据加载完再返回完整页面。


1. 传统 SSR vs. 流式 SSR

(1)传统 SSR 的问题

在 React 18 之前,SSR 采用的是阻塞式渲染

  • 服务器需要等所有数据加载完成,然后一次性返回完整 HTML。
  • 如果某个组件的数据请求较慢,会导致整个页面的加载延迟。
  • 用户在收到 HTML 之前,会一直看到空白页面。

示例(React 17 SSR):

import { renderToString } from 'react-dom/server';
import App from './App';

const html = renderToString(<App />);
res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
  • renderToString 阻塞执行,直到 App 及其所有子组件的内容完全准备好。

(2)React 18 的流式 SSR

React 18 提供了非阻塞的流式渲染

  • HTML 按块(chunks)发送,优先展示可用的 UI。
  • 可以提前渲染静态内容,等数据准备好再填充动态内容。
  • 提高首次内容可见(FCP) ,减少白屏时间。

示例(React 18 流式 SSR):

import { renderToPipeableStream } from 'react-dom/server';
import App from './App';

export default function handler(req, res) {
  const { pipe } = renderToPipeableStream(<App />, {
    onShellReady() {
      res.setHeader('Content-Type', 'text/html');
      pipe(res); // 立即开始流式传输 HTML
    },
    onError(err) {
      console.error(err);
      res.status(500).send('Internal Server Error');
    }
  });
}
  • renderToPipeableStream 替代了 renderToString,支持逐步发送 HTML
  • onShellReady 事件触发时,就可以开始向客户端流式发送内容。

2. 关键优化点

(1)更快的 FCP(First Contentful Paint)

  • 静态内容可以立即渲染,减少白屏时间。
  • 服务器端可优先返回非阻塞组件,提高用户体验。

(2)Suspense 支持流式 SSR

  • React 18 允许在 SSR 中使用 <Suspense>,未加载的数据部分可以稍后填充,不会影响整个页面的渲染。

示例:

import { Suspense } from 'react';

function SlowComponent() {
  const data = fetchData(); // 假设这里是一个耗时请求
  return <div>{data}</div>;
}

export default function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

在 React 18 SSR:

  1. 服务器会先返回 <h1>My App</h1><p>Loading...</p>
  2. SlowComponent 数据加载完,再填充真实内容。

3. 流式 SSR vs. 传统 SSR vs. CSR

方式 传统 SSR 流式 SSR CSR(客户端渲染)
页面渲染时机 数据加载完成后一次性返回 逐步返回,先渲染可用部分 HTML 先返回,JS 再渲染
性能 可能导致服务器阻塞 更快响应用户,减少白屏 依赖 JS 解析,SEO 不佳
SEO 友好
适用场景 适用于小型 SSR 项目 适用于高并发、内容较多的 SSR 项目 适用于动态交互强的 SPA

4. 总结

流式 SSR 允许服务器逐步发送 HTML,提升页面加载速度。
支持 <Suspense> 进行流式渲染,未加载的数据不会阻塞整个页面。
减少 TTFB(首字节时间) ,提高用户体验。
适用于高并发和需要快速响应的应用,如新闻站点、电商页面等。

React 18 的流式 SSR 使得服务端渲染更加高效,结合 Suspense 还能提供更好的用户体验,是前端优化的重要特性之一!

79.React18新特性——Server Component

React 18 的 Server Components(服务端组件) 是 React 生态中的一项革新,旨在通过服务端与客户端的协同渲染,优化性能并提升开发体验。以下是其核心要点与面试回答方向:


一、核心概念

  • 服务端专属:Server Components 仅在服务器端运行,不参与客户端交互,代码不会发送到浏览器,减少客户端包体积
  • 动态与静态结合:允许在服务端动态生成组件树,并与客户端组件(Client Components)无缝集成,形成混合渲染模式。
  • 直接访问后端资源:可直接连接数据库、读取文件或调用内部 API,避免客户端到服务端的额外请求

二、核心特性

1. 减少客户端代码
  • 零客户端代码:Server Components 的代码(包括依赖)不打包到客户端,显著降低首屏加载体积。

  • 示例

    // ServerComponent.server.js (服务端组件)
    import db from 'server-db'; // 仅在服务端运行
    
    export default function ProductDetails({ id }) {
      const product = db.query('SELECT * FROM products WHERE id = ?', id);
      return <div>{product.name}</div>; // 静态内容
    }
    
2. 流式渲染(Streaming SSR)
  • 分块传输:服务端将渲染结果拆分为多个数据块,通过 HTTP 流(Stream)逐步发送到客户端,加速首屏显示

  • 结合 Suspense:与 Suspense 配合,优先渲染关键内容,延迟非关键部分。

    // 客户端组件
    import ProductDetails from './ProductDetails.server';
    
    function App() {
      return (
        <Suspense fallback={<Loading />}>
          <ProductDetails id={1} />
        </Suspense>
      );
    }
    
3. 数据获取优化
  • 无客户端数据请求:服务端组件直接访问数据源,避免客户端通过 fetch 二次请求,减少网络延迟
  • 自动序列化:服务端组件向客户端传递的 props 会自动序列化为 JSON,支持基础类型、JSX 元素及客户端组件引用。
4. 与客户端组件协作
  • 混合渲染:通过文件后缀(如 .server.js.client.js)区分组件类型,服务端组件可嵌套客户端组件,反之不行。

    // ProductPage.server.js
    import AddToCart from './AddToCart.client'; // 导入客户端组件
    
    export default function ProductPage() {
      return (
        <div>
          <ProductDetails id={1} />
          <AddToCart productId={1} /> // 交互逻辑由客户端处理
        </div>
      );
    }
    

三、与传统 SSR 的区别

特性 传统 SSR Server Components
代码体积 服务端生成 HTML,但客户端仍需加载完整 JS 包 服务端组件代码不发送到客户端
数据获取 需在客户端通过 getServerSideProps 等获取 直接访问服务端资源,无额外请求
交互性 需水合(Hydration)后交互 需结合客户端组件处理交互
渲染粒度 整页渲染 按组件级动态流式渲染

四、适用场景

  1. 内容型页面:博客、文档、商品详情页等以静态内容为主的场景。
  2. 数据敏感操作:直接访问数据库或内部 API,避免暴露敏感逻辑到客户端。
  3. 性能敏感应用:需减少客户端代码体积,提升低端设备或慢网络下的体验。

五、限制与注意事项

  • 无交互能力:不能使用 useStateuseEffect 或浏览器 API(如 localStorage)。
  • 序列化约束:传递的 props 需可序列化,避免包含函数或复杂对象。
  • 框架依赖:需配合支持 Server Components 的框架(如 Next.js)或自定义服务端环境。

六、示例:混合渲染流程

  1. 服务端渲染

    • 运行 ProductPage.server.js,获取数据库数据。
    • 将静态内容序列化为 HTML,动态部分标记为占位符。
  2. 流式传输

    • 客户端逐步接收 HTML 和客户端组件代码。
    • AddToCart.client.js 被加载并水合,启用交互。
  3. 最终结果

    • 用户立即看到商品信息,按钮交互由客户端处理。

七、总结

React Server Components 通过服务端与客户端的职责分离,解决了传统 SSR 的代码冗余和数据请求问题,尤其适合内容优先的场景。其核心优势包括:

  • 更小的客户端包体积
  • 直接服务端数据访问
  • 流式渲染加速首屏

开发者需注意其限制,合理划分服务端与客户端组件,结合框架能力(如 Next.js)最大化性能收益。这一特性标志着 React 向全栈开发迈出了重要一步。

80.React18新特性——OffScreen

大白话:支持保持组件的状态,而删除组件的UI 部分,很方面的实现与渲染,或者keep-alive

React 18 的 Offscreen(或实验性 API unstable_Offscreen)是一个旨在优化渲染性能的特性,主要用于管理不可见组件的渲染行为,减少不必要的计算和 DOM 操作。以下是其核心要点与回答方向:


一、核心目标

  • 保留组件状态与 DOM:当组件暂时不可见时(如标签页切换、弹窗关闭),保持其 DOM 结构和状态,避免重复挂载/卸载带来的性能损耗。
  • 后台预渲染:结合并发模式,在后台静默渲染隐藏的组件,为后续显示提前准备。
  • 资源优化:暂停不可见组件的副作用(如动画、数据订阅),减少 CPU 和内存占用。

二、核心特性

1. 状态保留与快速恢复
  • 避免重新挂载:组件隐藏时保留其状态(如滚动位置、表单输入),再次显示时无需重新初始化。

  • 示例

    import { unstable_Offscreen as Offscreen } from 'react';
    
    function App() {
      const [show, setShow] = useState(true);
      return (
        <div>
          <button onClick={() => setShow(!show)}>Toggle</button>
          <Offscreen mode={show ? "visible" : "hidden"}>
            <ExpensiveComponent /> // 隐藏时保留 DOM 和状态
          </Offscreen>
        </div>
      );
    }
    
2. 与并发模式协同
  • 后台渲染:在组件隐藏期间,React 可异步完成其未完成的渲染任务(如数据加载),待显示时直接提交结果。
  • 优先级控制:隐藏组件的更新被标记为低优先级,避免阻塞用户交互。
3. 副作用管理
  • 自动暂停/恢复:隐藏时暂停 useEffect、动画等副作用,显示时自动恢复。
  • 资源释放:若组件长期隐藏,可选择彻底卸载以释放资源(需开发者配置)。

三、与传统处理方式的对比

场景 传统方式 Offscreen
组件隐藏 卸载组件,丢失状态 保留 DOM 和状态,快速恢复
复杂组件渲染 每次显示需重新渲染,可能卡顿 后台预渲染,显示时无延迟
副作用管理 需手动取消订阅或清理 自动暂停/恢复,减少内存泄漏风险

四、适用场景

  1. 标签页/路由切换:保留非活动标签页的状态,提升切换流畅度。
  2. 弹窗/抽屉组件:避免重复渲染弹窗内容,快速响应用户操作。
  3. 虚拟列表优化:对屏幕外列表项使用 Offscreen,减少渲染压力。

五、实现原理

  • DOM 隐藏而非卸载:通过 CSS display: nonevisibility: hidden 隐藏组件,保持 DOM 树结构。
  • Fiber 节点标记:在协调阶段跳过隐藏组件的更新检查,除非强制刷新。
  • 副作用调度:通过 React 调度器暂停隐藏组件的副作用执行。

六、注意事项

  • 实验性 API:截至 React 18.2,unstable_Offscreen 仍为实验性功能,API 可能变更。
  • 内存占用:长期保留大量隐藏组件可能增加内存消耗,需权衡使用。
  • 兼容性:需配合 Concurrent Mode 使用,传统同步渲染不生效。

七、示例:路由切换优化

import { unstable_Offscreen as Offscreen } from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';

function App() {
  const location = useLocation();
  return (
    <Routes location={location}>
      <Route path="/" element={<Home />} />
      <Route path="/dashboard" element={
        <Offscreen mode="visible">
          <Dashboard /> // 显示时优先渲染
        </Offscreen>
      } />
      <Route path="/settings" element={
        <Offscreen mode="hidden">
          <Settings /> // 隐藏时保留状态
        </Offscreen>
      } />
    </Routes>
  );
}

总结

React 18 的 Offscreen 特性通过智能管理不可见组件的生命周期,显著提升了复杂应用的渲染效率与用户体验。其核心价值在于:

  • 状态保留:避免重复初始化带来的性能损耗。
  • 后台渲染:利用空闲资源预计算,减少用户等待时间。
  • 副作用控制:自动化资源管理,降低开发复杂度。

尽管目前仍处于实验阶段,Offscreen 展现了 React 在性能优化方向的持续探索,值得开发者关注其未来正式发布后的应用场景。

81.React18新特性——卸载组件时的更新状态警告

React 18 新特性 —— 卸载组件时的更新状态警告(Updating a component while unmounting)

在 React 18 之前,如果组件在卸载(unmount)时仍然尝试更新状态(setStateuseState),React 会抛出**“Can’t perform a React state update on an unmounted component”** 警告。这通常是由于异步操作(如 fetchsetTimeoutuseEffect 副作用)未在组件卸载时正确清理导致的。


React 18 的改进

在 React 18 之后,React 变得更聪明了

  1. 自动检测并忽略卸载组件的更新,不会再抛出警告。
  2. 提高应用稳定性,避免无效的状态更新影响其他组件。

???? 示例:React 18 之前

function Example() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(result => setData(result));

    // ???? 组件卸载时,没有清理请求,可能导致警告
  }, []);

  return <div>{data}</div>;
}

如果用户在数据请求完成之前离开当前页面卸载组件,组件仍然尝试 setData(result),这会触发 React 17 及之前版本的状态更新警告

???? React 18 之后

function Example() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;

    fetch('/api/data')
      .then(res => res.json())
      .then(result => {
        if (isMounted) setData(result); // ✅ React 18 处理得更好
      });

    return () => {
      isMounted = false; // ???? 组件卸载时标记
    };
  }, []);

  return <div>{data}</div>;
}

在 React 18,即使不手动清理副作用,React 也会自动忽略组件卸载后的更新,从而避免警告。


为什么要优化这个问题?

  • 避免开发者手动清理副作用(比如 isMounted 变量)。
  • 解决了异步更新导致的潜在 Bug
  • 提升应用稳定性,避免不必要的错误提示。

总结

React 18 自动忽略卸载组件的更新,避免不必要的警告
减少开发者手动处理的负担,代码更简洁
增强应用稳定性,避免潜在的内存泄漏和状态错误

虽然 React 18 进行了优化,但良好的副作用清理习惯仍然是最佳实践,比如在 useEffectreturn 语句中清理定时器、取消请求等。

82.什么是服务端渲染什么是客户端渲染

服务端渲染(SSR) vs. 客户端渲染(CSR)

在 Web 开发中,页面的渲染方式主要有两种:

  • 服务端渲染(SSR - Server-Side Rendering)
  • 客户端渲染(CSR - Client-Side Rendering)

它们的区别在于HTML 生成的时机

  • SSR:HTML 在服务器端生成,然后返回完整的页面给浏览器。
  • CSR:HTML 在客户端(浏览器)生成,服务器只返回 JavaScript 代码,由浏览器执行后动态渲染页面。

1. 什么是服务端渲染(SSR)?

概念

服务端渲染(SSR)是指服务器在接收到请求后,直接返回完整的 HTML 页面
浏览器接收到 HTML 后,可以立即渲染页面,无需等待 JavaScript 加载和执行。

工作流程

  1. 浏览器发送请求给服务器(如 example.com)。
  2. 服务器执行 React/Vue/Next.js 代码,渲染出完整的 HTML 页面。
  3. 返回完整 HTML 页面给浏览器,用户可以立刻看到页面内容
  4. 客户端加载 JavaScript,让页面变得可交互(称为水合(Hydration) )。

示例(React SSR)

import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';

const app = express();

app.get('*', (req, res) => {
  const html = renderToString(<App />);
  res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});

app.listen(3000, () => console.log('Server is running on port 3000'));

这里 renderToString() 会在服务器端生成 HTML 并返回给浏览器。


SSR 的优点

SEO 友好:搜索引擎可以直接解析 HTML,提高排名。
首屏加载快:页面内容在服务器上渲染好后直接返回,减少白屏时间。
适合内容型网站(如博客、新闻站点、电商网站)。

SSR 的缺点

服务器压力大:每个请求都需要服务器计算,可能影响并发性能。
交互需要水合(Hydration) :页面渲染后,仍需 JavaScript 绑定事件,使页面可交互。


2. 什么是客户端渲染(CSR)?

概念

客户端渲染(CSR)是指服务器只返回一个基本的 HTML 结构,页面内容由 JavaScript 在浏览器中动态渲染

工作流程

  1. 服务器返回一个基本 HTML(通常只包含一个 div#root)。
  2. 浏览器下载 JavaScript,并在浏览器中执行 React/Vue 代码。
  3. 前端框架(如 React、Vue)动态渲染页面
  4. 用户可以看到页面内容并进行交互

示例(React CSR)

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = document.getElementById('root');
ReactDOM.createRoot(root).render(<App />);

这里 ReactDOM.createRoot().render() 会在浏览器端动态生成页面内容。


CSR 的优点

前后端分离,适合 SPA(单页面应用)
用户交互体验更流畅(页面切换更快)。
服务器压力小,因为页面渲染由客户端完成。

CSR 的缺点

首屏加载慢,需要先下载 JavaScript,再渲染页面。
SEO 不友好(搜索引擎爬取的是空 HTML)。
依赖 JavaScript,如果禁用 JavaScript,页面可能无法正常加载。


3. SSR vs. CSR 对比

特性 SSR(服务端渲染) CSR(客户端渲染)
首屏渲染速度 快(服务器返回完整 HTML) 慢(等待 JS 解析并渲染)
SEO 友好 好(搜索引擎可直接爬取 HTML) 差(搜索引擎可能爬不到内容)
服务器压力 高(每次请求都要计算) 低(服务器只提供 API 数据)
用户交互体验 需要 Hydration,使页面可交互 交互流畅
适用场景 适用于 SEO 重要的网站,如博客、新闻、电商 适用于交互性强的应用,如管理后台、社交平台

4. SSR 和 CSR 的结合

方案 1:同构渲染(Universal Rendering)

  • 服务器先进行 SSR,返回 HTML 让