Remix 基於 React Router 建構,包含四個部分:
Remix 中的一切都從編譯器開始:remix vite:build
。 使用 Vite,這會建立幾個東西:
build/server/index.js
中(它是可配置的),包含所有路由和模組,以便能夠在伺服器上渲染並處理任何其他伺服器端資源請求。build/client/*
中。 這包括按路由自動程式碼分割、指紋識別的資產導入(如 CSS 和圖像)等。 執行瀏覽器應用程式所需的任何東西。有了這些建置產物,應用程式就可以部署到任何執行 JavaScript 的託管服務。
雖然 Remix 在伺服器上執行,但它實際上並不是伺服器。 它只是一個被提供給實際 JavaScript 伺服器的處理器。
它是基於 Web Fetch API 而非 Node.js 建構的。 這使 Remix 能夠在任何 Node.js 伺服器(如 Vercel、Netlify、Architect 等)以及非 Node.js 環境(如 Cloudflare Workers 和 Deno Deploy)中執行。
這是 Remix 在 express 應用程式中執行的樣子
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 回應改編為伺服器的回應 API,使它們適用於特定伺服器。 以下是轉接器所執行動作的一些虛擬程式碼:
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。
此外,如果 Remix 沒有適用於您伺服器的轉接器,您可以查看其中一個轉接器的原始碼並建立自己的轉接器。
如果您熟悉伺服器端 MVC 網頁框架(如 Rails 和 Laravel),Remix 是 View 和 Controller,但它會將 Model 交給您處理。 JavaScript 生態系統中有許多很棒的資料庫、ORM、郵件程式等等,可以填補這個空間。 Remix 在 Fetch API 周圍也有用於 Cookie 和 Session 管理的協助程式。
Remix 路由模組承擔 View 和 Controller 兩種責任,而不是將它們分開。
大多數伺服器端框架都是「以模型為中心」。一個控制器會為單一模型管理多個 URL。
Remix 則是以 UI 為中心。路由可以處理整個 URL,或者只是 URL 的一部分。當路由對應到 URL 的一部分時,巢狀的 URL 片段會變成 UI 中巢狀的佈局。透過這種方式,每個佈局(視圖)都可以成為自己的控制器,然後 Remix 會彙總資料和元件以建構完整的 UI。
通常,一個 Remix 路由模組可以包含 UI 以及與模型互動的部分,這能帶來非常好的開發人員人體工學和生產力。
路由模組有三個主要的輸出:loader
、action
和 default
(元件)。
// 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 上渲染元件的路由慣例,可以提供許多網頁專案的核心功能集。
透過這種方式,Remix 可以向下擴展。您的應用程式中不是每個頁面都需要大量的瀏覽器 JavaScript,也不是每次使用者互動都需要超出瀏覽器預設行為的額外功能。在 Remix 中,您可以先以簡單的方式建構,然後在不改變基本模型的情況下向上擴展。此外,大部分應用程式在瀏覽器載入 JavaScript 之前就可以運作,這使得 Remix 應用程式在設計上能夠適應不穩定的網路狀況。
如果您不熟悉傳統的後端網頁框架,您可以將 Remix 路由視為 React 元件,它們已經是自己的 API 路由,並且已經知道如何在伺服器上載入資料並將資料提交給自己。
一旦 Remix 將文件傳送到瀏覽器,它就會使用瀏覽器建置的 JavaScript 模組來「水合」頁面。這就是我們經常談論 Remix「模擬瀏覽器」的地方。
當使用者點擊連結時,Remix 並不會為了取得整個文件和所有資源而往返伺服器,而是簡單地提取下一個頁面的資料並更新 UI。
此外,當使用者提交 <Form>
來更新資料時,瀏覽器執行階段會改為向伺服器發出 fetch 請求,而不是執行正常的 HTML 文件請求,並自動重新驗證頁面上所有資料,並使用 React 更新它。
與發出完整文件請求相比,這有許多效能上的好處
<a>
和 <form>
),您的應用程式往往甚至在 JavaScript 載入到頁面上之前就可以運作Remix 還內建了一些用於客戶端導覽的優化。它知道哪些佈局會在兩個 URL 之間持續存在,因此它只會提取正在變更的佈局的資料。完整的文件請求會需要在伺服器上提取所有資料,浪費後端資源並減慢您的應用程式速度。
這種方法還有 UX 上的好處,例如不會重設側邊欄導覽的捲動位置,並允許您將焦點移到比文件頂部更有意義的位置。
當使用者即將點擊連結時,Remix 也可以預先提取頁面的所有資源。瀏覽器框架知道編譯器的資源清單。它可以比對連結的 URL,讀取清單,然後預先提取下一個頁面的所有資料、JavaScript 模組,甚至是 CSS 資源。這就是 Remix 應用程式即使在網路速度慢時也能感覺快速的原因。
然後,Remix 提供客戶端 API,讓您可以在不改變 HTML 和瀏覽器基本模型的情況下,建立豐富的使用者體驗。
以下是我們從先前的路由模組中取得的,對表單進行的一些小而有用的 UX 改善,這些只能透過瀏覽器中的 JavaScript 來完成
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 載入之前也能在基本層面上運作,唯一的區別在於使用者回饋將由瀏覽器(旋轉的 favicon 等)而不是應用程式 (useNavigation().state
) 提供。
由於 Remix 可以深入後端的控制器層級,因此可以無縫地完成此操作。
雖然它不像 Rails 和 Laravel 等伺服器端框架那樣深入堆疊,但它的確更進一步深入到瀏覽器中,以實現從後端到前端的無縫轉換。
例如,在後端繁重的網頁框架中,建構純 HTML 表單和伺服器端處理程式與在 Remix 中一樣容易。但是,一旦您想要跨入具有動畫驗證訊息、焦點管理和待處理 UI 的體驗時,就需要對程式碼進行根本性的變更。通常,人們會建構一個 API 路由,然後引入一些客戶端 JavaScript 來連接兩者。使用 Remix,您只需在現有的「伺服器端視圖」周圍新增一些程式碼,而無需從根本上變更其運作方式。瀏覽器執行階段會接管伺服器通訊,以提供超出預設瀏覽器行為的增強使用者體驗。
我們借用了一個舊術語,並在 Remix 中稱其為漸進增強。從簡單的 HTML 表單開始(Remix 可以向下擴展),然後在您有時間和雄心時向上擴展 UI。