简介、技术说明
On this page

简介、技术说明

¥Introduction, Technical Explanation

基于 React 路由 构建的 Remix 具有以下四个特点:

¥Built on top of React Router, Remix is four things:

  1. 编译器

    ¥A compiler

  2. 服务器端 HTTP 处理程序

    ¥A server-side HTTP handler

  3. 服务器框架

    ¥A server framework

  4. 浏览器框架

    ¥A browser framework

编译器

¥Compiler

Remix 中的一切都始于编译器:remix vite:build。使用 Vite 可以实现以下几点:

¥Everything in Remix starts with the compiler: remix vite:build. Using Vite, this creates a few things:

  1. 一个服务器 HTTP 处理程序,通常在 build/server/index.js 中(它是可配置的),它将所有路由和模块组合在一起,以便能够在服务器上渲染并处理任何其他服务器端的资源请求。

    ¥A server HTTP handler, usually in build/server/index.js (it's configurable) that includes all routes and modules together to be able to render on the server and handle any other server-side requests for resources.

  2. 浏览器版本,通常使用 build/client/*。这包括按路由自动代码拆分、带指纹的资源导入(例如 CSS 和图片)等。所有在浏览器中运行应用所需的功能。

    ¥A browser build, usually in build/client/*. This includes automatic code splitting by route, fingerprinted asset imports (like CSS and images), etc. Anything needed to run an application in the browser.

  3. 资源清单。客户端和服务器都使用此清单来了解整个依赖图。这对于在初始服务器渲染中预加载资源以及在客户端转换时预取资源非常有用。这就是 Remix 能够消除当今 Web 应用中常见的渲染+抓取瀑布流的原因。

    ¥An asset manifest. Both the client and the server use this manifest to know the entire dependency graph. This is useful for preloading resources in the initial server render as well as prefetching them for client-side transitions. This is how Remix is able to eliminate the render+fetch waterfalls so common in web apps today.

使用这些构建构件,应用可以部署到任何运行 JavaScript 的托管服务上。

¥With these build artifacts, an application can be deployed to any hosting service that runs JavaScript.

HTTP 处理程序和适配器

¥HTTP Handler and Adapters

虽然 Remix 在服务器上运行,但它实际上并不是服务器。它只是一个传递给实际 JavaScript 服务器的处理程序。

¥While Remix runs on the server, it is not actually a server. It's just a handler that is given to an actual JavaScript server.

它基于 Web Fetch API 构建,而不是 Node.js。这使得 Remix 能够在任何 Node.js 服务器(例如 VercelNetlify架构师 等)以及非 Node.js 环境(例如 Cloudflare WorkersDeno 部署)中运行。

¥It's built on the Web Fetch API instead of Node.js. This enables Remix to run in any Node.js server like Vercel, Netlify, Architect, etc. as well as non-Node.js environments like Cloudflare Workers and Deno Deploy.

Remix 在 Express 应用中运行时如下所示:

¥This is what Remix looks like when running in an express app:

const remix = require("@remix-run/express");
const express = require("express");

const app = express();

app.all(
  "*",
  remix.createRequestHandler({
    build: require("./build/server"),
  })
);

Express(或 Node.js)是实际的服务器,Remix 只是该服务器上的一个处理程序。"@remix-run/express" 包被称为适配器。Remix 处理程序与服务器无关。适配器通过在传入过程中将服务器的请求/响应 API 转换为 Fetch API,然后将来自 Remix 的 Fetch Response 适配到服务器的响应 API 中,使它们适用于特定的服务器。以下是适配器功能的一些伪代码:

¥Express (or Node.js) is the actual server, Remix is just a handler on that server. The "@remix-run/express" package is called an adapter. Remix handlers are server agnostic. Adapters make them work for a specific server by converting the server's request/response API into the Fetch API on the way in and then adapting the Fetch Response coming from Remix into the server's response API. Here's some pseudocode of what an adapter does:

export function createRequestHandler({ build }) {
  // creates a Fetch API request handler from the server build
  const handleRequest = createRemixRequestHandler(build);

  // returns an express.js specific handler for the express server
  return async (req, res) => {
    // adapts the express.req to a Fetch API request
    const request = createRemixRequest(req);

    // calls the app handler and receives a Fetch API response
    const response = await handleRequest(request);

    // adapts the Fetch API response to the express.res
    sendRemixResponse(res, response);
  };
}

真正的适配器功能远不止这些,但这就是它的要点。这不仅使你能够在任何地方部署 Remix,还允许你在现有的 JavaScript 服务器中逐步采用它,因为你可以在 Remix 之外拥有路由,而服务器会在到达 Remix 之前继续处理这些路由。

¥Real adapters do a bit more than that, but that's the gist of it. Not only does this enable you to deploy Remix anywhere, it also lets you incrementally adopt it in an existing JavaScript server since you can have routes outside of Remix that your server continues to handle before getting to Remix.

此外,如果 Remix 尚未提供适合你服务器的适配器,你可以查看其中一个适配器的源代码并构建自己的适配器。

¥Additionally, if Remix doesn't have an adapter for your server already, you can look at the source of one of the adapters and build your own.

服务器框架

¥Server Framework

如果你熟悉 Rails 和 Laravel 等服务器端 MVC Web 框架,那么 Remix 就是视图和控制器,但它将模型留给你自己处理。JavaScript 生态系统中有很多优秀的数据库、ORM、邮件程序等,可以填补这一空白。Remix 还提供了围绕 Fetch API 的辅助函数,用于 Cookie 和会话管理。

¥If you're familiar with server-side MVC web frameworks like Rails and Laravel, Remix is the View and Controller, but it leaves the Model up to you. There are a lot of great databases, ORMs, mailers, etc. in the JavaScript ecosystem to fill that space. Remix also has helpers around the Fetch API for cookie and session management.

Remix 路由模块不再区分视图和控制器,而是同时承担这两个职责。

¥Instead of having a split between View and Controller, Remix Route modules take on both responsibilities.

大多数服务器端框架都基于 "模型聚焦"。控制器管理单个模型的多个 URL。

¥Most server-side frameworks are "model focused". A controller manages multiple URLs for a single model.

Remix 以 UI 为中心。路由可以处理整个 URL,也可以只是 URL 的某个片段。当路由仅映射到一个片段时,嵌套的 URL 片段将成为 UI 中的嵌套布局。这样,每个布局(视图)都可以作为其自己的控制器,然后 Remix 将聚合数据和组件以构建完整的 UI。

¥Remix is UI-focused. Routes can handle an entire URL or just a segment of the URL. When a route maps to just a segment, the nested URL segments become nested layouts in the UI. In this way, each layout (view) can be its own controller and then Remix will aggregate the data and components to build the complete UI.

通常情况下,Remix 路由模块可以在同一个文件中同时包含 UI 和与模型的交互,从而显著提升开发者的人机工程学和工作效率。

¥More often than not, a Remix route module can contain both the UI and the interactions with the models in the same file, which leads to really nice developer ergonomics and productivity.

路由模块主要有三个导出方式:loaderactiondefault(组件)。

¥Route modules have three primary exports: loader, action, and default (component).

// Loaders only run on the server and provide data
// to your component on GET requests
export async function loader() {
  return json(await db.projects.findAll());
}

// The default export is the component that will be
// rendered when a route matches the URL. This runs
// both on the server and the client
export default function Projects() {
  const projects = useLoaderData<typeof loader>();
  const actionData = useActionData<typeof action>();

  return (
    <div>
      {projects.map((project) => (
        <Link key={project.slug} to={project.slug}>
          {project.title}
        </Link>
      ))}

      <Form method="post">
        <input name="title" />
        <button type="submit">Create New Project</button>
      </Form>
      {actionData?.errors ? (
        <ErrorMessages errors={actionData.errors} />
      ) : null}

      {/* outlets render the nested child routes
          that match the URL deeper than this route,
          allowing each layout to co-locate the UI and
          controller code in the same file */}
      <Outlet />
    </div>
  );
}

// Actions only run on the server and handle POST
// PUT, PATCH, and DELETE. They can also provide data
// to the component
export async function action({
  request,
}: ActionFunctionArgs) {
  const form = await request.formData();
  const errors = validate(form);
  if (errors) {
    return json({ errors });
  }
  await createProject({ title: form.get("title") });
  return json({ ok: true });
}

你实际上可以将 Remix 用作服务器端框架,而无需使用任何浏览器 JavaScript。使用 loader 进行数据加载、使用 action 进行突变和 HTML 表单,以及在 URL 上渲染的组件的路由约定,可以为许多 Web 项目提供核心功能集。

¥You can actually use Remix as just a server-side framework without using any browser JavaScript at all. The route conventions for data loading with loader, mutations with action and HTML forms, and components that render at URLs, can provide the core feature set of a lot of web projects.

通过这种方式,Remix 可以缩减规模。并非应用中的每个页面都需要在浏览器中运行大量 JavaScript,也并非每个用户交互都需要比浏览器默认行为更复杂的操作。在 Remix 中,你可以先以简单的方式构建,然后在不更改基本模型的情况下进行扩展。此外,应用的大部分工作在 JavaScript 加载到浏览器之前就已完成,这使得 Remix 应用在设计上能够抵御不稳定的网络条件。

¥In this way, Remix scales down. Not every page in your application needs a bunch of JavaScript in the browser, and not every user interaction requires any extra flair than the browser's default behaviors. In Remix, you can build it the simple way first, and then scale up without changing the fundamental model. Additionally, the majority of the app works before JavaScript loads in the browser, which makes Remix apps resilient to choppy network conditions by design.

如果你不熟悉传统的后端 Web 框架,你可以将 Remix 路由视为 React 组件,这些组件本身就是 API 路由,并且知道如何在服务器上加载和提交数据。

¥If you're not familiar with traditional back-end web frameworks, you can think of Remix routes as React components that are already their own API route and already know how to load and submit data to themselves on the server.

浏览器框架

¥Browser Framework

Remix 将文档提供给浏览器后,它会使用浏览器构建的 JavaScript 模块对页面进行 "hydrates" 操作。我们在这里详细讨论了 Remix "模拟浏览器"。

¥Once Remix has served the document to the browser, it "hydrates" the page with the browser build's JavaScript modules. This is where we talk a lot about Remix "emulating the browser".

当用户点击链接时,Remix 只需获取下一页的数据并更新 UI,而无需往返服务器获取整个文档和所有资源。

¥When the user clicks a link, instead of making a round trip to the server for the entire document and all the assets, Remix simply fetches the data for the next page and updates the UI.

此外,当用户提交 <Form> 来更新数据时,浏览器运行时将改为获取服务器数据,并自动重新验证页面上的所有数据并使用 React 进行更新,而不是执行正常的 HTML 文档请求。

¥Additionally, when users submit a <Form> to update data, instead of doing a normal HTML document request, the browser runtime will make a fetch to the server instead and automatically revalidate all data on the page and update it with React.

与发出完整文档请求相比,这具有许多性能优势:

¥This has many performance benefits over making a full-document request:

  1. 资源无需重新下载(或从缓存中提取)

    ¥Assets don't need to be re-downloaded (or pulled from the cache)

  2. 资源无需再次由浏览器解析

    ¥Assets don't need to be parsed by the browser again

  3. 获取的数据比整个文档小得多(有时是几个数量级)

    ¥The data fetched is much smaller than the entire document (sometimes orders of magnitude)

  4. 由于 Remix 增强了 HTML API(<a><form>),你的应用往往在 JavaScript 加载到页面之前就能正常工作。

    ¥Because Remix enhances HTML APIs (<a> and <form>), your app tends to work even before JavaScript has loaded on the page

Remix 还内置了一些针对客户端导航的优化。它知道哪些布局会在两个 URL 之间保持不变,因此它只会获取正在更改的布局的数据。完整的文档请求需要从服务器获取所有数据,这会浪费后端资源并降低应用速度。

¥Remix also has some built-in optimizations for client-side navigation. It knows which layouts will persist between the two URLs, so it only fetches the data for the ones that are changing. A full document request would require all data to be fetched on the server, wasting resources on your back end and slowing down your app.

这种方法还具有用户体验优势,例如无需重置侧边栏导航的滚动位置,并允许你将焦点移动到比文档顶部更有意义的位置。

¥This approach also has UX benefits like not resetting the scroll position of a sidebar nav and allowing you to move focus to something that makes more sense than the top of the document.

Remix 还可以在用户即将点击链接时预加载页面的所有资源。浏览器框架知道编译器的资源清单。它可以匹配链接的 URL,读取清单,然后预取下一页所需的所有数据、JavaScript 模块甚至 CSS 资源。这就是 Remix 应用即使在网络速度较慢的情况下也能感觉快速运行的原因。

¥Remix can also prefetch all resources for a page when the user is about to click a link. The browser framework knows about the compiler's asset manifest. It can match the URL of the link, read the manifest, and then prefetch all data, JavaScript modules, and even CSS resources for the next page. This is how Remix apps feel fast even when networks are slow.

Remix 随后提供客户端 API,因此你无需更改 HTML 和浏览器的基本模型即可创建丰富的用户体验。

¥Remix then provides client side APIs, so you can create rich user experiences without changing the fundamental model of HTML and browsers.

延续我们之前的路由模块,以下是一些小而实用的用户体验改进,这些改进只能在浏览器中使用 JavaScript 实现:

¥Taking our route module from before, here are a few small but useful UX improvements to the form that you can only do with JavaScript in the browser:

  1. 提交表单时禁用按钮

    ¥Disable the button when the form is being submitted

  2. 当服务器端表单验证失败时聚焦输入

    ¥Focus the input when server-side form validation fails

  3. 在错误消息中添加动画

    ¥Animate in the error messages

export default function Projects() {
  const projects = useLoaderData<typeof loader>();
  const actionData = useActionData<typeof action>();
  const { state } = useNavigation();
  const busy = state === "submitting";
  const inputRef = React.useRef();

  React.useEffect(() => {
    if (actionData.errors) {
      inputRef.current.focus();
    }
  }, [actionData]);

  return (
    <div>
      {projects.map((project) => (
        <Link key={project.slug} to={project.slug}>
          {project.title}
        </Link>
      ))}

      <Form method="post">
        <input ref={inputRef} name="title" />
        <button type="submit" disabled={busy}>
          {busy ? "Creating..." : "Create New Project"}
        </button>
      </Form>

      {actionData?.errors ? (
        <FadeIn>
          <ErrorMessages errors={actionData.errors} />
        </FadeIn>
      ) : null}

      <Outlet />
    </div>
  );
}

这段代码示例最有趣的地方在于,它只是一种附加功能。整个交互本质上仍然是相同的,甚至在 JavaScript 加载之前也能在基本层面上工作,唯一的区别是用户反馈将由浏览器(旋转的图标等)而不是应用(useNavigation().state)提供。

¥What's most interesting about this code sample is that it is only additive. The entire interaction is still fundamentally the same thing and even works at a basic level before JavaScript loads, the only difference is the user feedback will be provided by the browser (spinning favicon, etc.) instead of the app (useNavigation().state).

由于 Remix 深入到后端的控制器级别,因此可以无缝地执行此操作。

¥Because Remix reaches into the controller level of the backend, it can do this seamlessly.

虽然它不像 Rails 和 Laravel 等服务器端框架那样深入到堆栈深处,但它确实深入到堆栈的更深处,进入浏览器,从而实现从后端到前端的无缝转换。

¥And while it doesn't reach as far back into the stack as server-side frameworks like Rails and Laravel, it does reach way farther up the stack into the browser to make the transition from the back end to the front end seamless.

例如。在后端繁重的 Web 框架中构建纯 HTML 表单和服务器端处理程序与在 Remix 中一样简单。但是,一旦你想要过渡到包含动画验证消息、焦点管理和待处理 UI 的体验,就需要对代码进行根本性的更改。通常,人们会构建一个 API 路由,然后引入一些客户端 JavaScript 代码来连接两者。使用 Remix,你只需在现有 "服务器端视图" 周围添加一些代码,而无需从根本上改变其工作方式。浏览器运行时接管服务器通信,以提供超越默认浏览器行为的增强用户体验。

¥For example. Building a plain HTML form and server-side handler in a back-end heavy web framework is just as easy to do as it is in Remix. But as soon as you want to cross over into an experience with animated validation messages, focus management, and pending UI, it requires a fundamental change in the code. Typically, people build an API route and then bring in a splash of client-side JavaScript to connect the two. With Remix, you simply add some code around the existing "server side view" without changing how it works fundamentally. The browser runtime takes over the server communication to provide an enhanced user experience beyond the default browser behaviors.

我们借用了一个古老的术语,在 Remix 中称之为“渐进式增强”。从一个简单的 HTML 表单开始(Remix 会缩小规模),然后在有时间和雄心壮志的时候再扩展 UI。

¥We borrowed an old term and called this Progressive Enhancement in Remix. Start small with a plain HTML form (Remix scales down) and then scale the UI up when you have the time and ambition.

Remix v2.17 中文网 - 粤ICP备13048890号