切换语言
切换主题

React Server Components 性能优化:数据获取与缓存实战

如果你的 RSC 页面 TTFB 还在 300-500ms,你可能只发挥了它 30% 的性能潜力。实测数据显示,正确的流式架构可以把 TTFB 降到 45ms——这不是魔法,是 React Server Components 的流式渲染真正激活后的结果。

说实话,我也踩过这个坑。去年帮一个电商团队优化商品详情页,他们用了 Next.js App Router,TTFB 却稳定在 380ms 左右。排查后发现,嵌套组件各自获取数据,形成了典型的”瀑布流”:商品信息等评论,评论等价格数据,价格又等库存校验。9 秒白屏。

这篇文章要聊的就是怎么解决这个问题。我会对比 4 种瀑布流处理方案,详解 5 种缓存 API 的使用场景,给你一套可以直接复制的配置模板。TTFB 从 450ms 到 45ms,中间差的可能只是几个 Suspense 边界的位置。


瀑布流问题:RSC 性能的最大杀手

先看个真实场景。你在电商网站打开一个商品详情页,页面先加载商品名称,然后等 3 秒价格出现,再等 5 秒评论区才显示。用户体验?灾难。

这就是瀑布流问题。嵌套组件各自获取数据,顺序执行而非并行。React Server Components 的数据获取默认是同步阻塞的——任何带 await 的请求都会阻止渲染,除非你用 Suspense 包裹。

瀑布流的两种形态

第一种:Server 内部瀑布流。同一页面内,父组件获取数据后才渲染子组件,子组件再获取自己的数据。典型代码:

// 瀑布流示例——这是问题代码
async function ProductPage({ id: string }) {
  // 第一个请求:1 秒
  const product = await db.getProduct(id);

  // 子组件渲染后才开始这些请求
  return (
    <div>
      <ProductDetails product={product} />
      <ProductPrice id={id} />      {/* 内部 await getPrice(id),3 秒 */}
      <ProductReviews id={id} />    {/* 内部 await getReviews(id),5 秒 */}
    </div>
  );
}

// ProductPrice.tsx
async function ProductPrice({ id }) {
  const price = await getPrice(id);  // 父组件渲染后才执行
  return <span>{price}</span>;
}

总耗时?9 秒。用户盯着空白页面等 9 秒。

第二种:Client-Server 瀑布流。客户端组件请求服务器,服务器再请求数据库。这种更隐蔽,React DevTools Profiler 才能抓到。N+1 问题的变种。

怎么识别瀑布流

打开 React DevTools Profiler,录制一次页面加载。如果时间轴上有明显的阶梯状请求分布——每个请求等待前一个完成——就是瀑布流。

还有一种更直观的方法。打开浏览器 Network 面板,看请求发起时间。如果数据请求是离散分布而非集中发起,问题基本确定了。

有意思的是,很多开发者以为用了 RSC 就自动获得性能收益。其实不然。根据 SitePoint 2026 年的报告,大多数团队只发挥了 RSC 约 30% 的性能潜力。原因就是瀑布流没处理。


四种解决方案对比:从粗暴到优雅

瀑布流问题有四种主流解决方案。从简单到复杂,从粗暴到优雅。

方案 1:Promise.all 并行获取

最直接的思路。所有请求一起发起,Promise.all 等待全部完成。

// 方案 1:Promise.all 并行获取
async function ProductPage({ id: string }) {
  // 同时发起所有请求
  const [product, price, reviews] = await Promise.all([
    getProduct(id),      // 1 秒
    getPrice(id),        // 3 秒
    getReviews(id),      // 5 秒
  ]);

  return (
    <div>
      <ProductDetails product={product} />
      <ProductPriceDisplay price={price} />
      <ProductReviewsList reviews={reviews} />
    </div>
  );
}

总耗时?5 秒。最慢的那个请求决定了整体时间。

优点:简单,改动小。

缺点:用户仍需等待最慢请求完成才能看到任何内容。还有个问题——数据耦合。父组件需要知道子组件需要什么数据,违反了组件独立性原则。

方案 2:Suspense 边界隔离

给数据依赖部分加 Suspense,让关键内容先显示。

// 方案 2:Suspense 边界隔离
async function ProductPage({ id: string }) {
  const product = await getProduct(id);  // 先等关键数据

  return (
    <div>
      <ProductDetails product={product} />  {/* 1 秒后显示 */}
      
      {/* 非关键部分用 Suspense 包裹 */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPrice id={id} />
      </Suspense>
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews id={id} />
      </Suspense>
    </div>
  );
}

用户体验:1 秒看到商品信息,3 秒价格出现,5 秒评论加载完成。

优点:关键内容优先显示,用户感知更好。

缺点:数据请求仍顺序发起。ProductPrice 和 ProductReviews 的请求是在父组件渲染后才开始的,不是真正的并行。

方案 3:Promise 作为 Props 传递

父组件启动所有请求,把 Promise 作为 props 传给子组件。子组件自己 await。

// 方案 3:Promise 传递模式
async function ProductPage({ id: string }) {
  // 立即启动所有请求,不 await
  const productPromise = getProduct(id);
  const pricePromise = getPrice(id);
  const reviewsPromise = getReviews(id);

  // 只 await 关键数据
  const product = await productPromise;

  return (
    <div>
      <ProductDetails product={product} />
      
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPrice pricePromise={pricePromise} />
      </Suspense>
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews reviewsPromise={reviewsPromise} />
      </Suspense>
    </div>
  );
}

// ProductPrice.tsx——接收 Promise
async function ProductPrice({ pricePromise }) {
  const price = await pricePromise;  // 复用父组件启动的 Promise
  return <span>{price}</span>;
}

三个请求在父组件同时启动。关键数据 1 秒显示,价格 3 秒,评论 5 秒。

优点:所有请求并行发起,关键内容优先显示,数据解耦(子组件只接收 Promise)。

缺点:需要修改组件接口,子组件从接收 id 变成接收 Promise。

方案 4:React cache() + preload(推荐)

React 19 引入了 cache() API。配合 preload 模式,是最优雅的解决方案。

// 方案 4:React cache() + preload
import { cache } from 'react';

// 用 cache 包装数据获取函数
const getComments = cache(async (postId: string) => {
  return db.getComments(postId);
});

// 导出 preload 函数,明确标识用途
export const preloadComments = (id: string) => {
  void getComments(id);  // 不 await,启动但不阻塞
};

// 父组件
async function PostPage({ postId: string }) {
  preloadComments(postId);  // 预加载评论
  
  const post = await getPost(postId);  // 只 await 关键数据
  
  return (
    <div>
      <PostContent post={post} />
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={postId} />  {/* 直接用 id,cache 自动复用 */}
      </Suspense>
    </div>
  );
}

// Comments.tsx——组件接口不变
async function Comments({ postId }) {
  const comments = await getComments(postId);  // 复用 preload 的 Promise
  return <CommentList comments={comments} />;
}

原理:cache() 函数在同一渲染周期内自动 memoized。preload 调用时触发请求但不等待,子组件 await 时复用同一个 Promise。

优点

  • 组件接口不变(仍然传 id)
  • 请求自动 memoized,无数据耦合
  • 子组件删除时 preload 成为无用代码,容易发现

缺点:需要理解 cache() 机制,注意隐藏耦合(删除 Comments 组件时要同步删除 preload)。

四种方案对比

方案总耗时关键内容可见时间数据耦合改动成本
顺序获取9s9s
Promise.all5s5s
Suspense5s1s
Promise 传递5s1s解耦
cache() + preload1s1s

实际选择看团队情况。快速迁移用方案 2,新项目推荐方案 4。


流式渲染架构:TTFB 45ms 的秘密

传统 SSR 的工作流程是这样的:等待所有数据获取完成,然后渲染完整 HTML,一次性发送给浏览器。TTFB(Time to First Byte)等于数据获取时间加渲染时间。

具体数字:数据库查询 400ms,渲染 50ms,TTFB 约 450ms。用户盯着空白页面等了近半秒。

450ms → 45ms
TTFB 优化效果

RSC Streaming 怎么改变这个流程

流式渲染的核心不是减少什么,而是改变内容到达用户的顺序。静态部分立即发送,动态部分流式补充。

// 流式架构示例
export default async function Dashboard() {
  return (
    <Layout>                          {/* 静态 shell,不包裹 Suspense */}
      <Nav />                         {/* 立即渲染 */}
      <Sidebar />                     {/* 立即渲染 */}
      
      <Suspense fallback={<ChartSkeleton />}>
        <DynamicChart />               {/* 动态数据,流式加载 */}
      </Suspense>
      
      <Suspense fallback={<TableSkeleton />}>
        <DataTable />                  {/* 动态数据,流式加载 */}
      </Suspense>
    </Layout>
  );
}

工作流程分解:

  1. T=0ms:静态 shell(Layout、Nav、Sidebar)从 CDN 边缘缓存立即发送
  2. T=30-50ms:浏览器开始渲染静态 shell,显示骨架屏
  3. T=200ms:DynamicChart 数据获取完成,对应 Suspense 边界内容流式发送
  4. T=400ms:DataTable 数据获取完成,对应内容流式发送

TTFB?约 45ms。静态 shell 发送的时间。

PPR(Partial Prerendering)的作用

PPR 是 Next.js 15 引入的特性,Next.js 16 将默认启用。它把静态部分预渲染到 CDN,动态部分保持流式。

配置方法:

// next.config.js——Next.js 15
module.exports = {
  experimental: {
    ppr: true,  // 启用 PPR
  },
};

// next.config.js——Next.js 16(预览)
module.exports = {
  experimental: {
    ppr: 'incremental',  // 渐进式启用
    cacheComponents: true,  // 新缓存模型
  },
};

启用 PPR 后,静态 shell(导航、布局、骨架屏)预渲染存储在 CDN。用户访问时,CDN 立即返回静态 HTML,动态部分由服务器流式补充。

Suspense 边界设计原则

关键原则:忘记用 Suspense 标记流式区块会导致 React 把整个应用视为一个巨大区块。

正确做法:

  • 静态部分不包裹 Suspense:导航、Layout、不依赖数据的骨架屏
  • 动态部分包裹 Suspense:依赖数据库/API 的组件
// 正确示例
export default async function Page() {
  return (
    <>
      <Header />                     {/* 静态,不包裹 */}
      <main>
        <Suspense fallback={<HeroSkeleton />}>
          <HeroSection />             {/* 动态,包裹 */}
        </Suspense>
        
        <Suspense fallback={<ContentSkeleton />}>
          <MainContent />             {/* 动态,包裹 */}
        </Suspense>
      </main>
      <Footer />                      {/* 静态,不包裹 */}
    </>
  );
}

错误示例(整个页面阻塞):

// 错误示例——忘记 Suspense
export default async function Page() {
  const data = await fetchDashboard();  // await 阻塞整个页面
  return (
    <>
      <Header />
      <Dashboard data={data} />
      <Footer />
    </>
  );
}

没有 Suspense 边界,整个页面被视为一个流式区块。TTFB 还是 450ms。

性能数据对比

渲染模式TTFBLCP说明
传统 SSR~450ms~500ms等待所有数据
RSC(无 Suspense)~450ms~500ms等同传统 SSR
RSC Streaming~45ms~200ms静态 shell 立即发送
RSC + PPR~30ms~150msCDN 缓存静态 shell

数据来源:SitePoint 2026 年报告,实测数据可能因数据源和 CDN 配置有所不同。


五种缓存 API 使用指南

Next.js 和 React 提供了五种缓存机制。选对了能事半功倍,选错了可能产生重复请求。

1. fetch cache(最常用)

fetch 请求在 Server Components 中自动 memoized。同一渲染周期内,相同 URL 和参数的请求只发起一次。

// fetch cache 示例
async function ProductCard({ id }) {
  // 自动缓存,相同 URL 不重复请求
  const res = await fetch(`https://api.example.com/products/${id}`, {
    cache: 'force-cache',      // 强制缓存(默认)
    next: {
      revalidate: 3600,         // 1 小时后重新验证
      tags: ['products'],       // 标记,用于 revalidateTag
    },
  });
  return <Card data={res.json()} />;
}

async function ProductList() {
  // 这里的请求复用上面的缓存
  const res = await fetch('https://api.example.com/products', {
    next: { tags: ['products'] },
  });
  return <List data={res.json()} />;
}

配置选项

  • cache: 'force-cache':优先使用缓存(默认)
  • cache: 'no-store':每次重新请求
  • next.revalidate:定时重新验证(秒)
  • next.tags:标记,配合 revalidateTag 手动刷新

2. React cache()(React 19 新增)

用于缓存函数调用结果。适用数据库查询、自定义数据获取函数。

import { cache } from 'react';

// 用 cache 包装数据库查询
export const getUser = cache(async (id: string) => {
  const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  return user;
});

// 在多个组件中使用,自动 memoized
async function UserProfile({ id }) {
  const user = await getUser(id);
  return <Profile user={user} />;
}

async function UserStats({ id }) {
  const user = await getUser(id);  // 复用上面的结果
  return <Stats user={user} />;
}

注意:cache() 只在同一渲染周期内生效。跨请求需要用 unstable_cache。

3. unstable_cache(Next.js 14-15)

持久化缓存,跨请求保持。适用昂贵计算、跨页面共享数据。

import { unstable_cache } from 'next/cache';

// 包装函数,添加持久化缓存
export const getPopularProducts = unstable_cache(
  async () => {
    const products = await db.getPopularProducts();
    return products;
  },
  ['popular-products'],           // 缓存键
  {
    revalidate: 3600,              // 1 小时重新验证
    tags: ['products', 'popular'], // 多标签
  }
);

// 使用
async function HomePage() {
  const products = await getPopularProducts();
  return <ProductGrid products={products} />;
}

手动刷新

import { revalidateTag } from 'next/cache';

// 在 Server Action 或 API Route 中
async function updateProduct() {
  await db.updateProduct();
  await revalidateTag('products');  // 刷新所有 products 标签的缓存
}

4. use cache(Next.js 16 新增)

组件级缓存指令。函数或组件顶部加 ‘use cache’,输出自动缓存。

// 函数级 'use cache'
'use cache';
export async function getRecommendations(userId: string) {
  return db.getRecommendations(userId);
}

// 组件级 'use cache'
'use cache';
export async function CachedFooter() {
  const links = await getFooterLinks();
  return <Footer links={links} />;
}

适用场景:频繁访问的组件,静态内容。实验性特性,Next.js 16 正式支持。

5. revalidatePath / revalidateTag

手动刷新缓存的方法。

import { revalidatePath, revalidateTag } from 'next/cache';

// 按路径刷新
await revalidatePath('/products');      // 刷新该路径的所有缓存
await revalidatePath('/products/[id]', 'page');  // 刷新特定页面

// 按标签刷新
await revalidateTag('products');        // 刷新所有 products 标签的缓存

选择建议

  • 精确控制用 revalidateTag(推荐)
  • 批量刷新用 revalidatePath

缓存 API 对比

API缓存范围持久化适用场景版本
fetch cache单请求可配置API 请求Next.js 13+
React cache()单渲染周期数据库查询、自定义函数React 19
unstable_cache跨请求昂贵计算、共享数据Next.js 14-15
use cache函数/组件级频繁访问组件Next.js 16
Cache Components组件输出配合 PPRNext.js 16

实战配置模板与踩坑提示

理论聊完了,直接给你可复制的配置。

next.config.js 完整配置

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    // Next.js 15:启用 PPR
    ppr: true,
    
    // Next.js 16:新缓存模型
    // cacheComponents: true,  // 正式版启用
  },
  
  // 性能相关
  images: {
    formats: ['image/avif', 'image/webp'],
  },
  
  // 输出优化
  output: 'standalone',  // Docker 部署用
};

export default nextConfig;

preload 函数导出规范

preload 函数容易产生隐藏耦合。删除深层子组件时,preload 可能变成无用代码。

推荐做法:在 preload 函数上方加注释,标明用途。

// comments.ts
import { cache } from 'react';

const getComments = cache(async (postId: string) => {
  return db.getComments(postId);
});

/**
 * preloadComments:为 Comments 组件预加载评论数据
 * 注意:删除 Comments 组件时需同步删除此 preload 函数
 */
export const preloadComments = (id: string) => {
  void getComments(id);
};

export async function Comments({ postId }) {
  const comments = await getComments(postId);
  return <CommentList comments={comments} />;
}

常见踩坑案例

踩坑 1:忘记 Suspense 边界

症状:整个页面 TTFB 还是 450ms,没有流式效果。

原因:没有 Suspense 边界,React 把整个页面视为一个流式区块。

解决:给数据依赖组件加 Suspense。

// 修复前
async function Page() {
  const data = await getData();  // 阻塞整个页面
  return <Dashboard data={data} />;
}

// 修复后
async function Page() {
  return (
    <Suspense fallback={<DashboardSkeleton />}>
      <Dashboard />
    </Suspense>
  );
}

踩坑 2:preload 但未使用

症状:请求发起但数据没用到,浪费资源。

原因:删除了子组件但保留 preload 函数。

解决:删除组件时同步删除 preload,或用注释标记关联关系。

踩坑 3:缓存 tags 冲突

症状:revalidateTag 刷新范围过大,不该刷新的数据也被刷新。

原因:多个不相关的缓存使用了相同 tag。

解决:给不同业务的数据用不同 tag。

// 错误示例
await fetch(url, { next: { tags: ['data'] } });  // 所有数据都用 'data' tag
await revalidateTag('data');  // 刷新所有数据缓存

// 正确示例
await fetch(productsUrl, { next: { tags: ['products'] } });
await fetch(usersUrl, { next: { tags: ['users'] } });
await revalidateTag('products');  // 只刷新 products

踩坑 4:fetch cache 与 React cache 混用

症状:相同数据发起两次请求。

原因:fetch 用了 ‘no-store’,React cache() 复用不到。

解决:fetch 用 force-cache 或默认配置,让 React cache 能复用。

// 错误示例
const data1 = await fetch(url, { cache: 'no-store' });  // 不缓存
const data2 = await getData();  // React cache 包装,但无法复用

// 正确示例
const data1 = await fetch(url);  // 默认 force-cache
const data2 = await getData();  // 可以复用

调试工具

  1. React DevTools Profiler:录制渲染过程,看瀑布流分布
  2. Next.js 分析工具next build --experimental-debug 输出构建分析
  3. Chrome DevTools:Network 面板看请求时序,Performance 面板看渲染时机

关键指标:

  • TTFB:首字节时间,目标 < 100ms
  • LCP:最大内容绘制时间,目标 < 2.5s
  • CLS:累积布局偏移,目标 < 0.1

迁移建议

从现有 SSR 页面迁移到 RSC 流式:

  1. 先识别瀑布流:用 Profiler 录制,找出顺序数据获取
  2. 添加 Suspense 边界:给数据依赖组件加 Suspense,关键路径优先
  3. 添加 preload:对深层组件使用 cache() + preload
  4. 配置缓存:给 fetch 添加 tags,实现精确刷新控制

逐步迁移,不要一次性重构。先处理最慢的页面,测量收益后再推广。


总结

说了这么多,核心就三步:识别瀑布流,选择解决方案,配置流式架构。

瀑布流问题很容易识别——嵌套组件各自 await 数据,时间轴上呈阶梯状分布。四种解决方案各有适用场景:快速迁移用 Suspense 边界,新项目用 React cache() + preload。

流式架构的关键是 Suspense 边界的位置。静态部分(导航、Layout)不包裹,动态部分(数据依赖组件)必须包裹。忘记这个边界,整个页面还是阻塞渲染。

性能收益可以量化:TTFB 从 450ms 降到 45ms,10 倍差距。中间差的只是几个 Suspense 边界和一个 preload 函数。

现在打开你的 Next.js 项目,检查有没有嵌套组件各自获取数据的情况。如果 TTFB 还在 300ms 以上,试试把关键内容用 Suspense 包裹。你的用户会立刻感受到差异。

React Server Components 性能优化流程

从瀑布流识别到流式架构配置的完整优化步骤

⏱️ 预计耗时: 60 分钟

  1. 1

    步骤1: 识别瀑布流问题

    使用 React DevTools Profiler 录制页面加载:

    • 打开 Chrome DevTools,切换到 Profiler 标签
    • 点击录制,刷新页面,等待加载完成
    • 查看时间轴上的请求分布
    • 阶梯状分布 = 瀑布流问题
    • 关注 TTFB 是否超过 300ms
  2. 2

    步骤2: 选择解决方案

    根据团队情况选择方案:

    • 快速迁移:方案 2(Suspense 边界隔离)
    • 新项目:方案 4(React cache() + preload)
    • 数据耦合容忍:方案 1(Promise.all)
    • 组件接口不变:方案 4(推荐)
  3. 3

    步骤3: 添加 Suspense 边界

    为数据依赖组件添加 Suspense:

    • 静态部分不包裹(导航、Layout)
    • 动态部分必须包裹(数据依赖组件)
    • 提供合适的 fallback 骨架屏
    • 关键路径优先处理
  4. 4

    步骤4: 配置 React cache() + preload

    使用 React 19 cache() API:

    • 用 cache 包装数据获取函数
    • 导出 preload 函数,不 await
    • 添加注释标记关联关系
    • 删除组件时同步删除 preload
  5. 5

    步骤5: 配置缓存策略

    选择合适的缓存 API:

    • fetch cache:API 请求(最常用)
    • React cache():数据库查询
    • unstable_cache:跨请求共享
    • 给缓存添加 tags,实现精确刷新
  6. 6

    步骤6: 测量性能收益

    验证优化效果:

    • TTFB 目标:&lt; 100ms
    • LCP 目标:&lt; 2.5s
    • CLS 目标:&lt; 0.1
    • 对比优化前后的性能数据

常见问题

React Server Components 的瀑布流问题是什么?
瀑布流问题指嵌套组件顺序获取数据,导致页面加载时间累加。例如父组件获取数据后渲染子组件,子组件再获取数据,总耗时等于所有请求时间之和。使用 React DevTools Profiler 可以识别阶梯状请求分布。
四种瀑布流解决方案如何选择?
根据团队情况选择:

• Promise.all:简单直接,适合快速修复,但仍有数据耦合
• Suspense 边界:关键内容优先显示,适合快速迁移
• Promise 传递:所有请求并行,适合组件接口可修改场景
• React cache() + preload:最优雅方案,适合新项目(推荐)
Suspense 边界应该放在哪里?
关键原则:静态部分不包裹 Suspense,动态部分必须包裹。静态部分包括导航、Layout、不依赖数据的骨架屏;动态部分包括依赖数据库或 API 的组件。忘记包裹会导致整个页面阻塞。
五种缓存 API 有什么区别?
适用场景不同:

• fetch cache:API 请求,自动 memoized(Next.js 13+)
• React cache():数据库查询,单渲染周期缓存(React 19)
• unstable_cache:跨请求持久化,昂贵计算(Next.js 14-15)
• use cache:函数/组件级缓存(Next.js 16)
• revalidatePath/Tag:手动刷新缓存
如何测量 RSC 性能优化效果?
使用 React DevTools Profiler 录制渲染过程,查看瀑布流分布。关键指标:TTFB 目标小于 100ms,LCP 目标小于 2.5s,CLS 目标小于 0.1。优化后 TTFB 可从 450ms 降至 45ms。
PPR(Partial Prerendering)是什么?
PPR 是 Next.js 15 引入的特性,Next.js 16 默认启用。它将静态部分预渲染到 CDN,动态部分保持流式。启用后静态 shell(导航、布局)从 CDN 立即返回,TTFB 可降至 30ms。配置:experimental.ppr = true。
常见的缓存配置踩坑有哪些?
四个常见问题:

• 忘记 Suspense 边界:整个页面阻塞,TTFB 无改善
• preload 但未使用:删除组件但保留 preload,浪费请求
• 缓存 tags 冲突:revalidateTag 刷新范围过大
• fetch cache 与 React cache 混用:使用 no-store 导致无法复用

参考资料

13 分钟阅读 · 发布于: 2026年5月13日 · 修改于: 2026年5月13日

当前属于系列阅读 第 47 / 47 篇

Next.js 完全指南

如果你是从搜索进入这篇文章,建议顺手补上上一篇或继续下一篇,这样更容易把同一主题读完整。

查看系列总览

相关文章

BetterLink

想持续收到这个主题的更新?

你可以直接关注作者更新、订阅 RSS,或者继续沿着系列入口往下读,避免下次又回到搜索结果重新找。

关注公众号

评论

使用 GitHub 账号登录后即可评论