Remix 透過巢狀路由和 links
幫助您使用一般 CSS 擴展應用程式。
CSS 維護問題可能會因為一些原因潛入 Web 應用程式。要知道以下狀況可能會變得困難:
Remix 透過基於路由的樣式表來減輕這些問題。巢狀路由可以各自將自己的樣式表新增至頁面,而 Remix 會隨著路由自動預先擷取、載入和卸載它們。當關注範圍僅限於作用中的路由時,這些問題的風險會顯著降低。衝突的唯一機會是與父路由的樣式發生衝突(即使如此,您很可能會看到衝突,因為父路由也在呈現)。
?url
。
每個路由都可以將樣式連結新增到頁面,例如
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import styles from "~/styles/dashboard.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import styles from "~/styles/accounts.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import styles from "~/styles/sales.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
在給定這些路由的情況下,下表顯示哪些 CSS 將應用於特定 URL
網址 | 樣式表 |
---|---|
/dashboard | dashboard.css |
/dashboard/accounts | dashboard.css accounts.css |
/dashboard/sales | dashboard.css sales.css |
這很微妙,但是這個小功能消除了使用一般樣式表來設定應用程式樣式時的許多困難。
無論大小的網站通常都有一組在整個應用程式中使用的共用元件:按鈕、表單元素、版面配置等。在 Remix 中使用一般樣式表時,我們建議兩種方法。
第一種方法非常簡單。將它們全部放入 app/root.tsx
中包含的 shared.css
檔案中。這使元件本身可以輕鬆共用 CSS 程式碼(並且您的編輯器可以為 自訂屬性 之類的東西提供智能感知),而且每個元件在 JavaScript 中都需要一個唯一的模組名稱,因此您可以將樣式範圍限定為唯一的類別名稱或資料屬性
/* scope with class names */
.PrimaryButton {
/* ... */
}
.TileGrid {
/* ... */
}
/* or scope with data attributes to avoid concatenating
className props, but it's really up to you */
[data-primary-button] {
/* ... */
}
[data-tile-grid] {
/* ... */
}
雖然此檔案可能會變得很大,但它將位於應用程式中所有路由共用的單一 URL。
這也讓路由可以輕鬆調整元件的樣式,而無需在該元件的 API 中新增官方的新變體。您知道這只會影響 /accounts
路由中的元件。
.PrimaryButton {
background: blue;
}
第二種方法是為每個元件編寫個別的 CSS 檔案,然後將樣式「浮現」到使用它們的路由。
假設您在 app/components/button/index.tsx
中有一個 <Button>
,其樣式位於 app/components/button/styles.css
,以及一個繼承它的 <PrimaryButton>
。
請注意,這些不是路由,但它們導出 links
函數,如同它們是路由一樣。我們將使用它將它們的樣式浮現到使用它們的路由。
[data-button] {
border: solid 1px;
background: white;
color: #454545;
}
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import styles from "./styles.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
export const Button = React.forwardRef(
({ children, ...props }, ref) => {
return <button {...props} ref={ref} data-button />;
}
);
Button.displayName = "Button";
然後是一個繼承它的 <PrimaryButton>
[data-primary-button] {
background: blue;
color: white;
}
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import { Button, links as buttonLinks } from "../button";
import styles from "./styles.css?url";
export const links: LinksFunction = () => [
...buttonLinks(),
{ rel: "stylesheet", href: styles },
];
export const PrimaryButton = React.forwardRef(
({ children, ...props }, ref) => {
return (
<Button {...props} ref={ref} data-primary-button />
);
}
);
PrimaryButton.displayName = "PrimaryButton";
請注意,主要按鈕的 links
包含基本按鈕的連結。這樣 <PrimaryButton>
的使用者就不需要知道它的依賴關係(就像 JavaScript 的導入一樣)。
因為這些按鈕不是路由,因此與 URL 區段沒有關聯,所以 Remix 不知道何時預先載入、載入或卸載樣式。我們需要將連結「浮現」到使用這些元件的路由。
假設 app/routes/_index.tsx
使用了主要按鈕元件
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import {
PrimaryButton,
links as primaryButtonLinks,
} from "~/components/primary-button";
import styles from "~/styles/index.css?url";
export const links: LinksFunction = () => [
...primaryButtonLinks(),
{ rel: "stylesheet", href: styles },
];
現在 Remix 可以預先載入、載入和卸載 button.css
、primary-button.css
和路由的 index.css
的樣式。
對此的初步反應是,路由必須知道比您希望的更多。請記住,每個元件都必須先被導入,所以它不是引入新的依賴關係,只是一些用於取得資源的樣板程式碼。例如,考慮一個像這樣的產品類別頁面
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import { AddFavoriteButton } from "~/components/add-favorite-button";
import { ProductDetails } from "~/components/product-details";
import { ProductTile } from "~/components/product-tile";
import { TileGrid } from "~/components/tile-grid";
import styles from "~/styles/$category.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
export default function Category() {
const products = useLoaderData<typeof loader>();
return (
<TileGrid>
{products.map((product) => (
<ProductTile key={product.id}>
<ProductDetails product={product} />
<AddFavoriteButton id={product.id} />
</ProductTile>
))}
</TileGrid>
);
}
元件的導入已經存在,我們只需要浮現資源
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import {
AddFavoriteButton,
links as addFavoriteLinks,
} from "~/components/add-favorite-button";
import {
ProductDetails,
links as productDetailsLinks,
} from "~/components/product-details";
import {
ProductTile,
links as productTileLinks,
} from "~/components/product-tile";
import {
TileGrid,
links as tileGridLinks,
} from "~/components/tile-grid";
import styles from "~/styles/$category.css?url";
export const links: LinksFunction = () => {
return [
...tileGridLinks(),
...productTileLinks(),
...productDetailsLinks(),
...addFavoriteLinks(),
{ rel: "stylesheet", href: styles },
];
};
// ...
雖然這有點樣板程式碼,但它啟用了許多功能
<Link prefetch>
預先載入下一頁的 CSS由於這些只是 <link>
標籤,您可以做的不只是樣式表連結,例如為您的元素的 SVG 圖示背景添加資源預先載入
[data-copy-to-clipboard] {
background: url("/icons/clipboard.svg");
}
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import styles from "./styles.css?url";
export const links: LinksFunction = () => [
{
rel: "preload",
href: "/icons/clipboard.svg",
as: "image",
type: "image/svg+xml",
},
{ rel: "stylesheet", href: styles },
];
export const CopyToClipboard = React.forwardRef(
({ children, ...props }, ref) => {
return (
<Button {...props} ref={ref} data-copy-to-clipboard />
);
}
);
CopyToClipboard.displayName = "CopyToClipboard";
這不僅會使資源在網路分頁中具有高優先順序,而且當您使用 <Link prefetch>
連結到該頁面時,Remix 會將該 preload
轉換為 prefetch
,因此 SVG 背景會與下一路由的資料、模組、樣式表和任何其他預先載入平行預先載入。
使用純樣式表和 <link>
標籤也開啟了減少使用者瀏覽器在繪製螢幕時必須處理的 CSS 量的能力。連結標籤支援 media
,因此您可以執行以下操作
export const links: LinksFunction = () => {
return [
{
rel: "stylesheet",
href: mainStyles,
},
{
rel: "stylesheet",
href: largeStyles,
media: "(min-width: 1024px)",
},
{
rel: "stylesheet",
href: xlStyles,
media: "(min-width: 1280px)",
},
{
rel: "stylesheet",
href: darkStyles,
media: "(prefers-color-scheme: dark)",
},
];
};