Remix框架:高性能React全栈开发实战

引言

在Web开发快速发展的今天,React生态系统中涌现出众多优秀框架,其中Remix凭借独特的设计理念和强大功能,正成为构建现代Web应用的热门选择。这款由React Router团队打造的全栈框架,聚焦性能优化、开发体验和Web标准,为开发者提供了一站式解决方案,涵盖路由管理、数据加载、表单处理到部署优化等核心环节。

本文将系统解析Remix框架的核心特性与实现原理,并通过生态对比和实践案例,带您全方位认识这一工具。主要内容包括:

  • Remix的核心设计理念与架构优势
  • 嵌套路由与数据加载的集成实现
  • 服务端渲染(SSR)与客户端渲染(CSR)的协同机制
  • 与Next.js等主流框架的对比分析
  • 项目实战中的最佳实践
  • 性能调优与部署方案

无论您是技术选型决策者,还是追求开发效率的全栈工程师,本文都将提供实用洞见,助您高效运用Remix框架。

一、Remix框架概述与核心特性

1.1 Remix的起源与设计哲学

Remix诞生于React Router团队,这个团队在客户端路由领域有着深厚的积累和经验。正是基于对React应用路由管理的深刻理解,Remix被设计为一个全栈Web框架,而不仅仅是另一个React框架。它的核心理念是"回归Web本质",强调利用浏览器和HTTP协议的原生能力,而非与之对抗。

与传统的单页应用(SPA)框架不同,Remix采用了渐进增强 (Progressive Enhancement)的策略,确保应用即使在JavaScript不可用或加载失败的情况下也能正常工作。这种设计哲学使Remix应用具有更好的韧性 (Robustness)和可访问性(Accessibility)。

1.2 核心特性深度解析

1.2.1 嵌套路由与布局系统

Remix的路由系统是其最强大的特性之一。它采用基于文件系统的路由配置,类似于Next.js,但提供了更灵活的嵌套路由支持。在Remix中,文件系统的结构直接映射到URL路由:

bash 复制代码
app/routes/
  ├── index.tsx        -> /
  ├── about.tsx        -> /about
  └── dashboard/
       ├── index.tsx   -> /dashboard
       └── settings.tsx -> /dashboard/settings

嵌套路由的关键优势在于代码和资源的按需加载 。当用户访问/dashboard/settings时,Remix只会加载与dashboardsettings相关的代码,而不会加载应用中其他不相关的部分。这种设计显著减少了初始加载时间。

在布局方面,父路由可以通过<Outlet />组件渲染子路由的内容,实现布局继承和局部更新。例如,一个管理后台可能具有固定的侧边栏导航和顶部栏,只有内容区域会根据路由变化:

js 复制代码
// app/routes/dashboard.tsx
export default function Dashboard() {
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <div className="content-area">
        <Outlet /> {/* 子路由内容将在这里渲染 */}
      </div>
    </div>
  );
}

这种设计不仅提高了用户体验(避免了全页面刷新),还增强了代码的可维护性,因为共享的布局逻辑只需在父路由中定义一次。

1.2.2 数据加载与提交一体化

Remix创新性地将数据加载 (Data Loading)和数据变更(Data Mutation)与路由系统紧密集成。每个路由可以定义两个关键函数:

  1. loader:负责为路由提供所需数据
  2. action:处理路由接收的表单提交
js 复制代码
// app/routes/posts/$id.tsx
import { json } from "@remix-run/node";
import { useLoaderData, Form } from "@remix-run/react";

// 数据加载
export async function loader({ params }) {
  const post = await db.posts.findUnique({ where: { id: params.id } });
  return json(post);
}

// 数据变更
export async function action({ request }) {
  const formData = await request.formData();
  await db.posts.update({
    where: { id: formData.get("id") },
    data: { title: formData.get("title") }
  });
  return redirect("/posts");
}

export default function Post() {
  const post = useLoaderData<typeof loader>();
  
  return (
    <Form method="post">
      <input name="id" defaultValue={post.id} hidden />
      <input name="title" defaultValue={post.title} />
      <button type="submit">保存</button>
    </Form>
  );
}

这种设计有几个显著优势:

  • 数据与UI耦合:每个路由明确定义了其数据需求,便于理解和维护
  • 服务端优先:数据获取和变更逻辑主要在服务端执行,减少客户端复杂度
  • 自动重新验证 :在action执行后,Remix会自动重新调用相关路由的loader获取最新数据
1.2.3 渐进式增强与韧性设计

Remix坚持渐进增强(Progressive Enhancement)原则,确保应用在多种环境下都能正常工作。最典型的例子是表单处理:

js 复制代码
// 传统React表单
<form onSubmit={async (e) => {
  e.preventDefault();
  await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) });
  // 处理响应...
}}>
js 复制代码
// Remix表单
<Form method="post">
  {/* 表单字段 */}
</Form>

传统React表单完全依赖JavaScript,如果JS加载失败或执行出错,表单将无法提交。而Remix的表单即使在没有JavaScript的情况下也能正常工作,因为浏览器会执行默认的表单提交行为。

这种设计不仅提高了应用的韧性 ,还对SEO可访问性有积极影响。搜索引擎爬虫和屏幕阅读器等辅助技术能够更好地理解和使用Remix构建的应用。

1.2.4 性能优化机制

Remix内置了多种性能优化策略:

  1. 自动代码拆分:基于路由的代码拆分确保用户只下载当前页面所需的代码
  2. 资源预加载 :当用户鼠标悬停在<Link>上时,Remix会自动预加载目标路由的代码和数据
  3. 并行数据加载 :嵌套路由的loader会并行执行,而非串行等待
  4. 智能缓存 :利用HTTP缓存头(Cache-Control)实现高效的资源缓存策略

这些优化共同作用,使得Remix应用即使在较慢的网络环境下也能提供流畅的用户体验。

二、Remix架构与工作原理

2.1 服务端与客户端协作模型

Remix采用了独特的服务端与客户端协作渲染模型,结合了服务器端渲染(SSR)和客户端导航的优点。让我们通过一个典型的页面加载流程来理解这一机制:

  1. 初始请求(SSR阶段)
    • 浏览器请求/dashboard/analytics
    • 服务端执行匹配路由的loader函数获取数据
    • 服务端渲染完整的HTML文档
    • 响应包含HTML和序列化的loader数据

Browser Server Database GET /dashboard/analytics 执行loader查询数据 返回数据 渲染React组件为HTML 返回HTML + 内联数据 Browser Server Database

  1. 客户端导航(CSR阶段)
    • 用户点击<Link to="/dashboard/settings">
    • Remix拦截导航,避免全页面刷新
    • 客户端获取/dashboard/settings的loader数据(JSON格式)
    • React仅更新变化的部分UI

Browser Server GET /dashboard/settings (数据请求) 执行loader函数 返回JSON数据 React协调更新DOM Browser Server

这种协作模型的关键优势在于:

  • 快速首屏渲染:SSR确保用户立即看到内容,无需等待JS加载执行
  • 流畅的后续导航:客户端导航避免了全页面刷新,提供类似SPA的体验
  • 数据一致性:无论是SSR还是CSR,数据获取逻辑保持一致(相同的loader函数)

2.2 编译器与运行时架构

Remix的架构可以清晰地分为编译器运行时两部分:

2.2.1 编译器(Compiler)

Remix编译器基于esbuild构建,负责:

  1. 代码拆分:根据路由结构自动拆分应用代码
  2. 服务端/客户端分离
    • 服务端包:包含loader/action逻辑,仅在服务端运行
    • 客户端包:包含UI组件和交互逻辑
  3. 资源优化:处理CSS、图像等静态资源

这种分离确保了客户端包不会包含任何服务端逻辑,减少了不必要的代码传输。

2.2.2 运行时(Runtime)

Remix运行时包括:

  1. 服务端适配器

    • @remix-run/node:Node.js环境
    • @remix-run/cloudflare:Cloudflare Workers环境
    • 其他平台适配器

    这些适配器将平台特定的请求/响应对象转换为Remix的标准Fetch API格式。

  2. 客户端路由

    • 基于React Router的增强实现
    • 处理客户端导航、预加载、滚动恢复等
  3. 数据管理

    • 处理loader数据的序列化/反序列化
    • 管理客户端数据缓存

2.3 错误处理与边界

Remix提供了强大的错误边界(Error Boundaries)机制,允许开发者在不同粒度上处理错误:

  1. 路由级错误边界
    每个路由可以定义ErrorBoundary组件,捕获该路由及其子路由的错误
js 复制代码
// app/routes/dashboard.tsx
import { useRouteError } from "@remix-run/react";

export function ErrorBoundary() {
  const error = useRouteError();
  return (
    <div className="error-container">
      <h1>出错了!</h1>
      <p>{error.message}</p>
    </div>
  );
}
  1. 全局错误边界

    根路由(app/root.tsx)的ErrorBoundary会捕获未被其他边界处理的错误

  2. Catch边界

    对于预期的错误(如404),可以使用CatchBoundary提供更友好的UI

js 复制代码
import { useCatch } from "@remix-run/react";

export function CatchBoundary() {
  const caught = useCatch();
  return (
    <div>
      <h1>{caught.status} {caught.statusText}</h1>
      <p>{caught.data}</p>
    </div>
  );
}

这种分层次的错误处理机制使得开发者可以精细控制错误恢复策略,避免因局部错误导致整个应用崩溃。

三、Remix与生态系统的对比

3.1 Remix vs Next.js:架构与特性对比

作为React生态中最受欢迎的两个全栈框架,Remix和Next.js各有侧重。以下从多个维度进行详细对比:

路由系统
  • Remix :采用嵌套路由设计,通过文件系统约定自动生成路由结构。例如app/routes/posts/$id.tsx会自动映射到/posts/:id路径。嵌套路由支持布局共享和数据继承。
  • Next.js :早期基于pages目录(如pages/posts/[id].js),13版本后引入App Router,支持更灵活的布局系统。App Router同样采用文件系统约定,但支持并行路由和拦截路由等高级特性。
数据加载
  • Remix :每个路由可定义loader函数处理数据获取,action处理表单提交。数据与路由强绑定,例如:

    javascript 复制代码
    export async function loader({ params }) {
      return getPost(params.id);
    }
  • Next.js :传统方式使用getServerSideProps(SSR)或getStaticProps(SSG)。App Router中改用async组件,如:

    javascript 复制代码
    export default async function Page({ params }) {
      const post = await getPost(params.id);
      return <PostDetail post={post} />;
    }
表单处理
  • Remix :深度集成HTML原生表单,支持<Form>组件和action处理。提交时自动处理CSRF防护,典型流程:
    1. 用户提交表单
    2. 触发路由action函数
    3. 自动重新执行相关loader
  • Next.js :默认无内置表单处理,需配合useState或React Hook Form等库。App Router引入服务端Actions后,可通过"use server"实现类似功能。
渲染策略
  • Remix :默认SSR渲染,支持动态JS加载(如:clientLoader)。适合内容频繁变化的场景,如电商产品页。
  • Next.js
    • SSG:构建时生成静态HTML(博客文档)
    • ISR:静态页面增量更新(商品列表)
    • CSR:客户端动态渲染(用户仪表盘)
部署适配
  • Remix :通过适配器支持多种运行时:
    • @remix-run/node:传统Node服务
    • @remix-run/deno:Deno环境
    • @remix-run/cloudflare:Edge环境
  • Next.js:在Vercel平台有自动优化(如边缘函数、图片优化)。也可部署到Node服务器或其他支持静态托管的平台。
典型应用场景
  • 选择Remix
    • 表单密集型应用(后台管理系统)
    • 需要精细错误处理的金融系统
    • 多平台部署需求(需同时支持Node和Edge)
  • 选择Next.js
    • 内容营销站点(利用SSG/ISR)
    • Vercel生态深度集成项目
    • 需要混合渲染策略的复杂应用
特性 Remix Next.js
路由系统 嵌套路由,文件系统约定 早期基于pages,现支持App Router
数据加载 路由级loader/action getServerSideProps/getStaticProps
表单处理 内置HTML表单+action处理 依赖客户端状态管理
渲染策略 SSR为主,支持CSR SSR/SSG/ISR/CSR混合
代码拆分 基于路由自动拆分 基于路由自动拆分
部署目标 多平台适配(Node, Edge等) 原生优化Vercel部署
错误处理 路由级ErrorBoundary 全局错误页面
API路由 无单独API路由,action即API 单独的pages/api目录
静态导出 支持但非重点 核心功能(SSG)
学习曲线 中等,需理解嵌套路由 较低,文档完善

3.2 适用场景分析

根据架构差异,两个框架各有最适合的应用场景:

选择Remix当:

  1. 应用高度交互:需要复杂表单处理、频繁数据变更的管理系统
  2. 嵌套布局复杂:如仪表盘、多级导航的管理后台
  3. 重视渐进增强:需要确保基础功能在无JS环境下可用
  4. 部署灵活性:需要部署到多种环境(Node, Edge, Serverless等)

选择Next.js当:

  1. 内容为主:博客、营销网站等静态内容较多的场景
  2. 需要ISR:增量静态再生对于频繁更新的内容很有效
  3. Vercel生态:计划使用Vercel部署并利用其优化功能
  4. 图像优化:需要自动图像优化和响应式图片

3.3 性能对比

在性能方面,两个框架各有优势:

  1. 初始加载性能

    • Next.js的SSG/ISR可以提供最快的初始加载,因为内容已预生成
    • Remix的SSR需要执行loader,但通过并行数据加载优化
  2. 后续导航性能

    • Remix的嵌套路由和预加载策略使客户端导航极其流畅
    • Next.js的客户端导航同样快速,但缺少Remix的细粒度数据预加载
  3. 资源效率

    • Remix的代码拆分更细粒度(到组件级别)
    • Next.js的代码拆分基于路由,但支持动态导入

实际性能差异取决于具体实现,但两者都属于高性能框架范畴。选择的关键在于应用的特性和开发团队的偏好。

四、Remix实践指南

4.1 项目初始化与开发流程

开始一个Remix项目非常简单:

bash 复制代码
# 创建新项目
npx create-remix@latest

# 进入项目目录
cd my-remix-app

# 安装依赖(如果未自动安装)
npm install

# 启动开发服务器
npm run dev

Remix提供了多种模板选择,包括:

  1. 基础模板:最简Remix设置
  2. TypeScript模板:包含TypeScript配置
  3. Express模板:集成Express服务器
  4. Cloudflare Workers模板:针对边缘计算环境

4.2 数据库集成实践

Remix与各种数据库都能良好集成。以下是使用Prisma(流行的ORM)的示例:

  1. 安装Prisma:
bash 复制代码
npm install prisma @prisma/client
  1. 初始化Prisma:
bash 复制代码
npx prisma init
  1. 定义数据模型(prisma/schema.prisma):
prisma 复制代码
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
}
  1. 在loader中使用:
typescript 复制代码
import { prisma } from "~/db.server";

export async function loader() {
  const posts = await prisma.post.findMany({
    where: { published: true },
    orderBy: { createdAt: "desc" }
  });
  return json(posts);
}

4.3 身份验证实现

身份验证是大多数应用的核心需求。Remix提供了灵活的认证方案:

  1. 基于Session的认证
typescript 复制代码
// app/session.server.ts
import { createCookieSessionStorage } from "@remix-run/node";

const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "__session",
    secure: process.env.NODE_ENV === "production",
    secrets: [process.env.SESSION_SECRET],
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24 * 30, // 30天
    httpOnly: true
  }
});

export { sessionStorage };
  1. 登录Action
typescript 复制代码
export async function action({ request }: ActionArgs) {
  const formData = await request.formData();
  const email = formData.get("email");
  const password = formData.get("password");
  
  const user = await authenticateUser(email, password);
  if (!user) return json({ error: "Invalid credentials" }, { status: 401 });
  
  const session = await sessionStorage.getSession(request.headers.get("Cookie"));
  session.set("userId", user.id);
  
  return redirect("/dashboard", {
    headers: {
      "Set-Cookie": await sessionStorage.commitSession(session)
    }
  });
}
  1. 保护路由
typescript 复制代码
export async function loader({ request }: LoaderArgs) {
  const session = await sessionStorage.getSession(request.headers.get("Cookie"));
  const userId = session.get("userId");
  if (!userId) throw new Response("Unauthorized", { status: 401 });
  
  return json(await getDashboardData(userId));
}

4.4 测试策略

Remix应用可以采用多种测试策略:

  1. 单元测试:使用Jest/Vitest测试独立函数
typescript 复制代码
import { loader } from "./posts.route";

test("loader returns published posts", async () => {
  const posts = await loader();
  expect(posts).toEqual(expect.arrayContaining([
    expect.objectContaining({ published: true })
  ]));
});
  1. 集成测试:测试loader/action与数据库的交互
typescript 复制代码
import { createTestClient } from "@remix-run/testing";

test("submit post creates new record", async () => {
  const client = createTestClient();
  const response = await client.post("/posts/new", {
    title: "Test Post",
    content: "Test content"
  });
  
  expect(response.status).toBe(200);
  expect(await db.post.count()).toBe(1);
});
  1. 端到端测试:使用Cypress或Playwright测试完整流程
typescript 复制代码
// Cypress测试示例
describe("Post Creation", () => {
  it("should create a new post", () => {
    cy.login();
    cy.visit("/posts/new");
    cy.get("input[name=title]").type("Test Post");
    cy.get("textarea[name=content]").type("Test content");
    cy.get("button[type=submit]").click();
    cy.url().should("include", "/posts");
    cy.contains("Test Post").should("exist");
  });
});

4.5 部署策略

Remix支持部署到多种平台:

  1. Node.js服务器
bash 复制代码
npm run build
npm run start
  1. Serverless平台

    • Vercel:配置vercel.json
    json 复制代码
    {
      "version": 2,
      "builds": [{ "src": "build/index.js", "use": "@vercel/node" }],
      "routes": [{ "src": "/(.*)", "dest": "build/index.js" }]
    }
  2. 边缘计算

    • Cloudflare Workers:使用@remix-run/cloudflare适配器
    typescript 复制代码
    // worker.ts
    import { createEventHandler } from "@remix-run/cloudflare";
    import * as build from "../build";
    
    addEventListener("fetch", createEventHandler({ build }));
  3. 静态站点:对于内容为主的站点,可以导出静态HTML

bash 复制代码
npm run build
npm run remix export

五、高级主题与最佳实践

5.1 性能优化进阶

除了内置的优化机制,Remix应用还可以采用以下高级优化策略:

  1. 缓存策略
    在loader中设置适当的HTTP缓存头,减少重复请求
typescript 复制代码
export async function loader({ request }: LoaderArgs) {
  const data = await getData();
  return json(data, {
    headers: {
      "Cache-Control": "public, max-age=60, stale-while-revalidate=86400"
    }
  });
}
  1. 关键CSS内联
    对于关键路径CSS,可以内联到HTML中避免额外请求
typescript 复制代码
export function links() {
  return [
    {
      rel: "stylesheet",
      href: stylesHref,
      "data-inline": "true" // 标记为内联
    }
  ];
}
  1. 资源预取
    使用<Link prefetch="intent">实现智能预加载
js 复制代码
<Link to="/about" prefetch="intent">关于我们</Link>

5.2 状态管理策略

虽然Remix减少了客户端状态管理的需求,但复杂场景仍需要状态管理方案:

  1. URL状态
    对于UI状态(如筛选条件),可以存储在URL查询参数中
js 复制代码
function Filter() {
  const [searchParams, setSearchParams] = useSearchParams();
  const filter = searchParams.get("filter") || "all";
  
  const handleChange = (value) => {
    setSearchParams({ filter: value });
  };
  
  return <select value={filter} onChange={(e) => handleChange(e.target.value)}>...</select>;
}
  1. 客户端缓存
    使用useFetcher实现乐观UI更新
js 复制代码
function LikeButton({ postId }) {
  const fetcher = useFetcher();
  
  return (
    <fetcher.Form method="post" action={`/posts/${postId}/like`}>
      <button type="submit">
        {fetcher.state === "submitting" ? "处理中..." : "点赞"}
      </button>
    </fetcher.Form>
  );
}
  1. 全局状态
    对于真正的全局状态(如主题偏好),可以使用Context或状态管理库
js 复制代码
const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState("light");
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {/* 应用内容 */}
    </ThemeContext.Provider>
  );
}

5.3 国际化(i18n)实现

Remix没有内置i18n支持,但可以轻松集成解决方案:

  1. 基于路由的国际化
typescript 复制代码
// app/routes/[lang]/about.tsx
export async function loader({ params }: LoaderArgs) {
  const translations = await getTranslations(params.lang);
  return json(translations);
}
  1. Cookie/Header检测
typescript 复制代码
export async function loader({ request }: LoaderArgs) {
  const lang = detectLanguage(request);
  const translations = await getTranslations(lang);
  return json(translations);
}
  1. 客户端切换
js 复制代码
function LanguageSwitcher() {
  const locales = ["en", "zh", "es"];
  
  return (
    <Form method="post" action="/set-locale">
      <select name="locale">
        {locales.map((locale) => (
          <option key={locale} value={locale}>
            {locale}
          </option>
        ))}
      </select>
      <button type="submit">切换语言</button>
    </Form>
  );
}

5.4 监控与错误追踪

生产环境应用需要完善的监控:

  1. 错误追踪
typescript 复制代码
export function ErrorBoundary() {
  const error = useRouteError();
  
  // 上报错误到监控系统
  useEffect(() => {
    trackError(error);
  }, [error]);
  
  return <div>...</div>;
}
  1. 性能监控
typescript 复制代码
export function links() {
  return [
    {
      rel: "preconnect",
      href: "https://analytics.example.com",
      crossOrigin: "anonymous"
    }
  ];
}
  1. 真实用户监控(RUM)
typescript 复制代码
export default function App() {
  useEffect(() => {
    initAnalytics();
  }, []);
  
  return <Outlet />;
}

六、总结与展望

6.1 Remix的核心价值

通过对Remix的全面分析,我们可以总结出它的核心价值主张:

  1. 开发者体验(DX)

    • 简洁的API设计,减少样板代码
    • 约定优于配置,降低决策疲劳
    • 热模块替换(HMR)和快速重载提升开发效率
  2. 用户体验(UX)

    • 快速的初始加载和流畅的导航
    • 渐进增强确保基础功能可用
    • 自动处理加载状态和错误恢复
  3. 架构优势

    • 前后端关注点分离但紧密集成
    • 内置最佳实践(如代码拆分、缓存策略)
    • 灵活的部署选项适应各种环境

6.2 适用场景再评估

基于实际使用经验,Remix特别适合以下场景:

  1. 数据密集型应用:如CRM、ERP等业务系统
  2. 表单丰富的应用:如调查问卷、数据录入界面
  3. 需要渐进增强的项目:对可访问性和SEO要求高的网站
  4. 全栈团队:希望统一前后端开发流程的团队

6.3 学习路径建议

对于想要掌握Remix的开发者,建议的学习路径:

  1. 基础阶段

    • 熟悉React和React Router
    • 完成Remix官方教程
    • 构建简单的CRUD应用
  2. 进阶阶段

    • 深入理解loader/action模型
    • 实践嵌套路由和布局
    • 集成认证和数据库
  3. 专家阶段

    • 自定义适配器
    • 性能优化和监控
    • 复杂状态管理方案

6.4 未来展望

随着Web开发的演进,Remix可能会在以下方面继续发展:

  1. 更紧密的React集成:如React Server Components的官方支持
  2. 边缘计算优化:更好的边缘部署和运行时
  3. 开发者工具增强:更强大的调试和分析工具
  4. 生态系统扩展:更多官方和社区插件

6.5 最终建议

对于正在考虑采用Remix的团队,我们建议:

  1. 从小开始:在非关键项目上试用,评估团队适应度
  2. 全栈思维:充分利用Remix的全栈能力,重新思考数据流
  3. 性能预算:建立性能基准,监控关键指标
  4. 社区参与:加入Remix社区,分享经验和最佳实践

Remix代表了Web框架发展的一个重要方向------回归Web本质,同时利用现代开发工具和模式。它可能不是所有场景的最佳选择,但对于适合的项目,Remix能够显著提升开发效率和用户体验。作为开发者,理解并掌握这样的工具,将有助于我们在快速变化的技术 landscape 中保持竞争力。

相关推荐
烛阴35 分钟前
Ceil -- 从平滑到阶梯
前端·webgl
90后的晨仔44 分钟前
🔍Vue 模板引用(Template Refs)全解析:当你必须操作 DOM 时
前端·vue.js
90后的晨仔1 小时前
👂 Vue 侦听器(watch)详解:监听数据的变化
前端·vue.js
90后的晨仔1 小时前
深入浅出 Vue 的 computed:不仅仅是“计算属性”那么简单!
前端·vue.js
Nan_Shu_6141 小时前
学习:入门uniapp Vue3组合式API版本(17)
前端·vue.js·学习·uni-app
萌萌哒草头将军2 小时前
🚀🚀🚀 深入探索 Node.js v22.18.0 新特性;默认支持运行 ts 文件了!
前端·typescript·node.js
安心不心安2 小时前
React ahooks——副作用类hooks之useThrottleFn
前端·javascript·react.js
秋田君3 小时前
Vue3 + WebSocket网页接入弹窗客服功能的完整实现
前端·javascript·websocket·网络协议·学习
浪里行舟3 小时前
一网打尽 Promise 组合技:race vs any, all vs allSettled,再也不迷糊!
前端·javascript·vue.js