news 2026/9/10 23:03:00

Next.js App Router 模式完全指南:agents 仓库 frontend-mobile-development 插件的 8 大核心模式与缓存策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Next.js App Router 模式完全指南:agents 仓库 frontend-mobile-development 插件的 8 大核心模式与缓存策略

Next.js App Router 模式完全指南:agents 仓库 frontend-mobile-development 插件的 8 大核心模式与缓存策略

【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents

本文基于 agents 多 harness Agent 插件市场中frontend-mobile-development插件的nextjs-app-router-patternsSkill 参考文档 references/details.md,系统讲解 Next.js App Router 的 8 大生产级模式——Server Components 数据获取、Client Components、Server Actions、Parallel Routes、Intercepting Routes、Suspense 流式渲染、Route Handlers 与 Metadata/SEO,以及配套的数据缓存策略。读完本文,你将掌握从组件边界划分、异步数据流到缓存失效控制的完整 App Router 实战方案,并能理解这套知识在 Agent 插件体系中的组织方式。

文档定位:Agent Skill 中的渐进式知识披露

nextjs-app-router-patternsfrontend-mobile-development插件(版本 1.2.3,类别 development,见 plugin.json 与 .claude-plugin/marketplace.json 中的登记条目)内置的 4 个 Skill 之一,遵循 Anthropic Agent Skills 规范的渐进式披露(progressive disclosure)结构:

plugins/frontend-mobile-development/skills/nextjs-app-router-patterns/ ├── SKILL.md # 导航层:渲染模式表、文件约定、Quick Start、最佳实践 └── references/ └── details.md # 详细层:8 大模式 + 缓存策略的完整代码示例

SKILL.md 明确说明了两层分工:"Detailed pattern documentation lives inreferences/details.md. Read that file when the navigation tier above is insufficient."——即 Agent 先加载轻量导航层,只有当需要深入实现时才读取本文档。这正是该文档作为"详细参考层"的定位:导航层负责"何时用",本文档负责"怎么写"。

安装该 Skill 的方式(依据 README.md 的 Quick start 章节):

# Claude Code:安装整个插件 /plugin marketplace add wshobson/agents /plugin install frontend-mobile-development # 或仅安装单个 Skill(无需克隆仓库、无需生成步骤) gh skill install wshobson/agents npx skills add wshobson/agents --skill nextjs-app-router-patterns

该插件配套的 frontend-developer agent 声明覆盖 "Next.js 15 App Router with Server Components and Client Components"、"React Server Components (RSC) and streaming patterns"、"Advanced routing with parallel routes, intercepting routes, and route handlers",本文档即为该能力声明对应的知识实现。

适用前提:文档声明面向 Next.js 14+(SKILL.md frontmatter 描述),而其中所有页面组件均采用params: Promise<...>/searchParams: Promise<...>的异步参数风格,即 Next.js 15 引入的 Promise 化路由参数写法。在 14 及以下版本中使用这些示例时,paramssearchParams是同步对象,需要去掉await

核心概念:渲染模式与文件约定

在进入具体模式之前,先继承 SKILL.md 导航层的两张核心表,它们是理解后文 8 个模式的坐标系。

渲染模式选择

模式运行位置适用场景
Server Components仅服务端数据获取、重计算、密钥访问
Client Components浏览器交互、hooks、浏览器 API
Static构建时很少变更的内容
Dynamic请求时个性化或实时数据
Streaming渐进式大页面、慢数据源

App 目录文件约定

app/ ├── layout.tsx # Shared UI wrapper ├── page.tsx # Route UI ├── loading.tsx # Loading UI (Suspense) ├── error.tsx # Error boundary ├── not-found.tsx # 404 UI ├── route.ts # API endpoint ├── template.tsx # Re-mounted layout ├── default.tsx # Parallel route fallback └── opengraph-image.tsx # OG image generation

后文的模式 4 用到default.tsxloading.tsx,模式 7 用到route.ts,与本约定表一一对应。

Pattern 1:Server Components 数据获取

原文档的第一个模式展示了电商商品列表页的完整数据流:页面组件作为 Server Component 读取查询参数,用Suspense包裹慢数据区,子组件直接await fetch取数。

// app/products/page.tsx import { Suspense } from 'react' import { ProductList, ProductListSkeleton } from '@/components/products' import { FilterSidebar } from '@/components/filters' interface SearchParams { category?: string sort?: 'price' | 'name' | 'date' page?: string } export default async function ProductsPage({ searchParams, }: { searchParams: Promise<SearchParams> }) { const params = await searchParams return ( <div className="flex gap-8"> <FilterSidebar /> <Suspense key={JSON.stringify(params)} fallback={<ProductListSkeleton />} > <ProductList category={params.category} sort={params.sort} page={Number(params.page) || 1} /> </Suspense> </div> ) } // components/products/ProductList.tsx - Server Component async function getProducts(filters: ProductFilters) { const res = await fetch( `${process.env.API_URL}/products?${new URLSearchParams(filters)}`, { next: { tags: ['products'] } } ) if (!res.ok) throw new Error('Failed to fetch products') return res.json() } export async function ProductList({ category, sort, page }: ProductFilters) { const { products, totalPages } = await getProducts({ category, sort, page }) return ( <div> <div className="grid grid-cols-3 gap-4"> {products.map((product) => ( <ProductCard key={product.id} product={product} /> ))} </div> <Pagination currentPage={page} totalPages={totalPages} /> </div> ) }

(见 details.md)

三个值得注意的要点:

  • key={JSON.stringify(params)}技巧:给Suspense边界设置随查询参数变化的 key,使得 URL 查询串变化(切换分类、排序、页码)时边界内容整体重建并重新触发 fallback。若不设 key,客户端导航改参数时 React 会尝试复用边界内的组件树,可能跳过重新挂起/流式过程。
  • 数据获取就近原则getProducts定义在使用数据的地方,与 SKILL.md 最佳实践 "Colocate data fetching - Fetch data where it's used" 对应。
  • 缓存标签预埋fetchnext: { tags: ['products'] }为该响应打上标签,是后文"缓存策略"一节中revalidateTag('products')精确失效的前提。

Pattern 2:Client Components 与 'use client'

交互型组件在文件顶部声明'use client',成为客户端组件子树的根:

// components/products/AddToCartButton.tsx 'use client' import { useState, useTransition } from 'react' import { addToCart } from '@/app/actions/cart' export function AddToCartButton({ productId }: { productId: string }) { const [isPending, startTransition] = useTransition() const [error, setError] = useState<string | null>(null) const handleClick = () => { setError(null) startTransition(async () => { const result = await addToCart(productId) if (result.error) { setError(result.error) } }) } return ( <div> <button onClick={handleClick} disabled={isPending} className="btn-primary" > {isPending ? 'Adding...' : 'Add to Cart'} </button> {error && <p className="text-red-500 text-sm">{error}</p>} </div> ) }

(见 details.md)

该示例与 Pattern 3 的 Server Action 形成完整闭环:addToCartapp/actions/cart.ts导出的 server action,客户端组件通过startTransition包裹调用,isPending自动反映请求进行状态(按钮禁用 + 文案切换),错误则通过返回值{ error }回传并在本地 state 中渲染。这正是 Next.js 推荐的最小客户端边界——只有按钮这一叶子节点带'use client',其余部分留在服务端。注意客户端组件只能接收可序列化 props(此处仅productId: string),这也是 SKILL.md Don'ts 中 "Don't pass serializable data - Server → Client boundary limitations" 的具体体现(原文强调 Server→Client 边界的数据必须可序列化)。

Pattern 3:Server Actions

Server Actions 是 Next.js App Router 中原生的服务端变更(mutation)机制,用"use server"指令标记模块或函数:

// app/actions/cart.ts "use server"; import { revalidateTag } from "next/cache"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; export async function addToCart(productId: string) { const cookieStore = await cookies(); const sessionId = cookieStore.get("session")?.value; if (!sessionId) { redirect("/login"); } try { await db.cart.upsert({ where: { sessionId_productId: { sessionId, productId } }, update: { quantity: { increment: 1 } }, create: { sessionId, productId, quantity: 1 }, }); revalidateTag("cart"); return { success: true }; } catch (error) { return { error: "Failed to add item to cart" }; } } export async function checkout(formData: FormData) { const address = formData.get("address") as string; const payment = formData.get("payment") as string; // Validate if (!address || !payment) { return { error: "Missing required fields" }; } // Process order const order = await processOrder({ address, payment }); // Redirect to confirmation redirect(`/orders/${order.id}/confirmation`); }

(见 details.md)

该示例覆盖了 Server Actions 的三类关键能力:

  1. 认证检查与服务端重定向await cookies()(Promise 风格,Next.js 15)读取会话;未登录时redirect("/login")在服务端直接抛出重定向,不产生一次客户端往返。
  2. 幂等写入upsert以联合唯一键sessionId_productId定位购物车行,重复点击"加入购物车"时递增数量而非报错。
  3. 变更后缓存失效revalidateTag("cart")在数据写入后立即使打了cart标签的缓存失效,保证随后渲染的页面读到新数据——这与 Pattern 1 中fetchtags配置构成端到端的缓存闭环。

checkout则演示了 Server Action 作为<form action={checkout}>处理器的形态:直接接收FormData,校验失败返回结构化错误对象,成功则redirect到订单确认页(303 模式,避免重复提交)。

Pattern 4:Parallel Routes

并行路由(Parallel Routes)允许同一布局中的多个插槽独立加载、独立展示各自的 loading 状态,插槽用@name目录表示:

// app/dashboard/layout.tsx export default function DashboardLayout({ children, analytics, team, }: { children: React.ReactNode analytics: React.ReactNode team: React.ReactNode }) { return ( <div className="dashboard-grid"> <main>{children}</main> <aside className="analytics-panel">{analytics}</aside> <aside className="team-panel">{team}</aside> </div> ) } // app/dashboard/@analytics/page.tsx export default async function AnalyticsSlot() { const stats = await getAnalytics() return <AnalyticsChart data={stats} /> } // app/dashboard/@analytics/loading.tsx export default function AnalyticsLoading() { return <ChartSkeleton /> } // app/dashboard/@team/page.tsx export default async function TeamSlot() { const members = await getTeamMembers() return <TeamList members={members} /> }

(见 details.md)

目录到布局 prop 的映射规则是:app/dashboard/@analytics/下的页面组件渲染结果注入 layout 的analyticsprop,@team/注入teamprop,主路由仍是children。每个插槽可以拥有自己的loading.tsx(对应文件约定表中的 "Parallel route fallback" 条目),当该插槽数据未就绪时只挂起该面板,而不阻塞主内容。这是仪表盘类布局的标准解法:慢的 analytics 面板不会拖累主区域渲染。

Pattern 5:Intercepting Routes(模态框模式)

拦截路由(Intercepting Routes)让同一段 UI 既能作为模态框在当前页上叠出,也能作为独立全页存在,实现"同一数据、两种呈现":

// File structure for photo modal // app/ // ├── @modal/ // │ ├── (.)photos/[id]/page.tsx # Intercept // │ └── default.tsx // ├── photos/ // │ └── [id]/page.tsx # Full page // └── layout.tsx // app/@modal/(.)photos/[id]/page.tsx import { Modal } from '@/components/Modal' import { PhotoDetail } from '@/components/PhotoDetail' export default async function PhotoModal({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params const photo = await getPhoto(id) return ( <Modal> <PhotoDetail photo={photo} /> </Modal> ) } // app/photos/[id]/page.tsx - Full page version export default async function PhotoPage({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params const photo = await getPhoto(id) return ( <div className="photo-page"> <PhotoDetail photo={photo} /> <RelatedPhotos photoId={id} /> </div> ) } // app/layout.tsx export default function RootLayout({ children, modal, }: { children: React.ReactNode modal: React.ReactNode }) { return ( <html> <body> {children} {modal} </body> </html> ) }

(见 details.md)

从文件结构看关键机制:

  • (.)语法是拦截路由的核心:app/@modal/(.)photos/[id]/page.tsx中的(.)表示"拦截相对当前层级的路由"——当用户在/photos/123页内导航(如点击列表中的另一张照片)时,匹配到的渲染进入@modal插槽以模态框形式叠出,而不离开当前页面;当从外部直接访问/photos/123时,则正常落入全页版本。
  • default.tsx@modal插槽的空态回退:没有路由被拦截时插槽渲染default.tsx(通常为空组件),保证布局中{modal}位置不报错。
  • 两种形态共享PhotoDetail组件,模态框版本精简为纯详情,全页版本额外渲染RelatedPhotos,实现了 UI 复用且互不污染。

Pattern 6:Suspense 流式渲染

流式(Streaming)让页面"先出快的,再流慢的",把不同延迟的数据源放进各自独立的Suspense边界:

// app/product/[id]/page.tsx import { Suspense } from 'react' export default async function ProductPage({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params // This data loads first (blocking) const product = await getProduct(id) return ( <div> {/* Immediate render */} <ProductHeader product={product} /> {/* Stream in reviews */} <Suspense fallback={<ReviewsSkeleton />}> <Reviews productId={id} /> </Suspense> {/* Stream in recommendations */} <Suspense fallback={<RecommendationsSkeleton />}> <Recommendations productId={id} /> </Suspense> </div> ) } // These components fetch their own data async function Reviews({ productId }: { productId: string }) { const reviews = await getReviews(productId) // Slow API return <ReviewList reviews={reviews} /> } async function Recommendations({ productId }: { productId: string }) { const products = await getRecommendations(productId) // ML-based, slow return <ProductCarousel products={products} /> }

(见 details.md)

结构上的分工非常清晰:

  • 阻塞数据getProduct)在组件顶层await,决定页面的"首帧"——头部信息必须齐备才能渲染主体;
  • 非关键慢数据(评论 API、基于 ML 的推荐)下沉到ReviewsRecommendations内部自行 fetch,并各自包在独立边界中。哪个先返回哪个先替换 skeleton,互不等待。

这与 Pattern 1 的差别在于:Pattern 1 是单个边界包裹整块数据(配合key做参数级重建),Pattern 6 是多个边界按数据源延迟分层(配合渐进式水合)。两者可组合使用。

Pattern 7:Route Handlers(API 路由)

需要纯 JSON 接口(第三方调用、webhook、非 React 消费端)时,用app/api/**/route.ts导出 HTTP 方法处理器:

// app/api/products/route.ts import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const category = searchParams.get("category"); const products = await db.product.findMany({ where: category ? { category } : undefined, take: 20, }); return NextResponse.json(products); } export async function POST(request: NextRequest) { const body = await request.json(); const product = await db.product.create({ data: body, }); return NextResponse.json(product, { status: 201 }); } // app/api/products/[id]/route.ts export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { const { id } = await params; const product = await db.product.findUnique({ where: { id } }); if (!product) { return NextResponse.json({ error: "Product not found" }, { status: 404 }); } return NextResponse.json(product); }

(见 details.md)

要点:集合路由处理GET(带可选category查询过滤、take: 20分页上限)与POST(201 返回创建结果);动态段路由处理单资源GET,未找到时返回规范化的 404 JSON 而非抛错。注意动态段处理器的第二参数同样是params: Promise<{ id: string }>,需await解包,与页面组件保持一致的 15 风格。

Pattern 8:Metadata 与 SEO

动态页面的 SEO 元数据由generateMetadata异步生成,配合generateStaticParams预渲染与notFound()兜底:

// app/products/[slug]/page.tsx import { Metadata } from 'next' import { notFound } from 'next/navigation' type Props = { params: Promise<{ slug: string }> } export async function generateMetadata({ params }: Props): Promise<Metadata> { const { slug } = await params const product = await getProduct(slug) if (!product) return {} return { title: product.name, description: product.description, openGraph: { title: product.name, description: product.description, images: [{ url: product.image, width: 1200, height: 630 }], }, twitter: { card: 'summary_large_image', title: product.name, description: product.description, images: [product.image], }, } } export async function generateStaticParams() { const products = await db.product.findMany({ select: { slug: true } }) return products.map((p) => ({ slug: p.slug })) } export default async function ProductPage({ params }: Props) { const { slug } = await params const product = await getProduct(slug) if (!product) notFound() return <ProductDetail product={product} /> }

(见 details.md)

三个细节:

  • generateMetadata与页面组件共享同一数据源(都是getProduct(slug)),保证<title>/OG 卡与页面正文一致;查不到商品时 metadata 返回空对象、页面组件调用notFound()触发not-found.tsx(对应文件约定表)。
  • generateStaticParams构建时枚举全部 slug,使这批商品页进入 SSG/ISR 轨道;未枚举的 slug 在运行时按需动态渲染(配合 ISR 缓存,见下节)。
  • OG 图片显式声明1200×630,符合社交卡片分享的标准比例;文件约定表中的opengraph-image.tsx则是其文件式替代方案(程序化生成 OG 图)。

缓存策略:从 no-store 到标签化失效

文档最后一节(details.md)把 App Router 的缓存手段汇总为五种组合:

// No cache (always fresh) fetch(url, { cache: "no-store" }); // Cache forever (static) fetch(url, { cache: "force-cache" }); // ISR - revalidate after 60 seconds fetch(url, { next: { revalidate: 60 } }); // Tag-based invalidation fetch(url, { next: { tags: ["products"] } }); // Invalidate via Server Action ("use server"); import { revalidateTag, revalidatePath } from "next/cache"; export async function updateProduct(id: string, data: ProductData) { await db.product.update({ where: { id }, data }); revalidateTag("products"); revalidatePath("/products"); }

按"新鲜度—成本"权衡理解这五档:

配置行为典型场景
cache: "no-store"每次请求回源,不缓存会话数据、高频变化接口
cache: "force-cache"永久缓存(直至部署)静态资源、不变内容
next: { revalidate: 60 }ISR:60 秒后请求触发异步再生商品列表、内容页
next: { tags: ["products"] }打标签,等待显式失效被多处引用、需要精确控制失效时机的数据
revalidateTag/revalidatePath在 Server Action 中主动失效写操作后立即让读路径可见新数据

最后一个片段把前文所有模式串成闭环:updateProduct作为"use server"action 写库后,同时执行revalidateTag("products")(按 Pattern 1 中fetch打的标签失效数据缓存)和revalidatePath("/products")(按路径失效页面 HTML 缓存)。写操作与缓存失效集中在同一个 Server Action 中,避免了"数据已更新但页面仍展示旧缓存"的一致性窗口。

选择策略的经验路径:先用tags精确圈定"哪些数据会变",再在对应的 Server Action(如 Pattern 3 的addToCart中的revalidateTag("cart"))里失效;对整页级的粗粒度失效用revalidatePath;对只读且可容忍短暂滞后的读路径叠加revalidate秒数作为兜底。

最佳实践清单(继承自导航层)

SKILL.md 的 Do's/Don'ts 是对 8 个模式的收敛总结,逐条对应上文示例:

Do's

  • 从 Server Components 起步——确需交互时才加'use client'(Pattern 2 中仅按钮是客户端组件)
  • 数据获取就近放置——在消费它的地方 fetch(Pattern 1、6 均如此)
  • 使用 Suspense 边界——为慢数据启用流式(Pattern 1、6)
  • 利用 Parallel Routes——让各面板有独立 loading 状态(Pattern 4)
  • 使用 Server Actions——带渐进增强能力的变更操作(Pattern 3)

Don'ts

  • 不要跨边界传不可序列化数据——Server → Client 只允许可序列化 props(Pattern 2 仅传productId字符串)
  • 不要在 Server Components 中使用 hooks——无useState/useEffect
  • 不要在 Client Components 中 fetch——用 Server Components 或 React Query 承担数据获取
  • 不要过度嵌套 layouts——每层 layout 都增加组件树深度
  • 不要忽略 loading 状态——始终提供loading.tsxSuspensefallback(Pattern 4 的@analytics/loading.tsx即示例)

相关资源索引

资源路径
本文核心参考文档(8 模式 + 缓存策略)plugins/frontend-mobile-development/skills/nextjs-app-router-patterns/references/details.md
Skill 导航层(渲染模式表、文件约定、Quick Start、Do/Don'ts)plugins/frontend-mobile-development/skills/nextjs-app-router-patterns/SKILL.md
配套前端 Agent 定义plugins/frontend-mobile-development/agents/frontend-developer.md
插件元数据plugins/frontend-mobile-development/.claude-plugin/plugin.json
市场目录登记.claude-plugin/marketplace.json
插件市场安装与多 harness 支持说明README.md

以上模式代码均可直接复制到 Next.js 15 项目(TypeScript)中使用;示例中的db假定为 Prisma 或 Drizzle 等数据库客户端,process.env.API_URL等环境变量需按实际部署配置。若你在 Claude Code、Codex、Cursor、OpenCode、Antigravity 或 Copilot 中使用 agents 插件市场,安装frontend-mobile-development插件后即可让 Agent 在这些模式上按本文路径直接查阅参考文档。

【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 23:02:22

网购返利APP离线数据同步:无网络场景下本地缓存与断点续传方案

网购返利APP离线数据同步&#xff1a;无网络场景下本地缓存与断点续传方案 大家好&#xff0c;我是省赚客APP研发者微赚淘客&#xff01; 在移动网络环境复杂多变的今天&#xff0c;用户在地铁、电梯或信号不佳的偏远地区使用APP是常态。对于电商返利应用而言&#xff0c;网络中…

作者头像 李华
网站建设 2026/9/10 23:01:55

二分查找算法实现高效平方根计算

1. 二分查找算法基础与平方根问题二分查找&#xff08;Binary Search&#xff09;是计算机科学中最基础且高效的搜索算法之一&#xff0c;它的核心思想是通过不断缩小搜索范围来快速定位目标值。这个算法要求数据集必须是有序的&#xff0c;这也是它能达到O(log n)时间复杂度的…

作者头像 李华
网站建设 2026/9/10 23:01:46

电网故障下分布式能源系统的无功优化与GCC控制

1. 电网故障下分布式能源系统的无功优化挑战 现代电力系统中&#xff0c;分布式能源&#xff08;DER&#xff09;的渗透率不断提高&#xff0c;这对电网的稳定运行提出了新的要求。当电网发生故障时&#xff0c;如何通过并网转换器&#xff08;Grid-Connected Converter, GCC&a…

作者头像 李华
网站建设 2026/9/10 23:00:58

Sequence卡牌游戏AI:GNN状态编码与MCTS-DQN协同架构

简介&#xff1a;本资源是一个融合蒙特卡洛树搜索&#xff08;MCTS&#xff09;与深度Q学习&#xff08;Deep Q-Learning&#xff09;的卡牌游戏AI完整实现项目&#xff0c;面向强化学习初学者、游戏AI研究者及算法工程实践者&#xff0c;旨在解决不完全信息下复杂策略决策建模…

作者头像 李华
网站建设 2026/9/10 22:57:20

微信图片和文件如何保存下来?个人微信API接口中的媒体处理功能

一、保存触发——什么时候开始保存 保存动作的触发点有两个&#xff1a;消息回调触发&#xff08;实时收到图片或文件时立刻保存&#xff09;和定时补录触发&#xff08;扫描历史消息&#xff0c;补存遗漏的素材&#xff09;。 回调触发是主力&#xff0c;但回调可能丢&#…

作者头像 李华
网站建设 2026/9/10 22:57:14

如何准备GESP C++三级考试的数学部分

准备GESP C三级考试的数学部分&#xff0c;推荐采用‌“先抓核心考点→微训练巩固→真题闭环”‌的适配四年级零基础孩子的落地路径&#xff0c;每天仅需15分钟就能高效推进&#xff1a; 第一步&#xff1a;优先锁定核心考点&#xff0c;不做无用超前补习 先把占数学总分90%的…

作者头像 李华