Remix这货一出来就刷屏了,虽然2019年就悄悄诞生,但那时候还得掏钱订阅才能用。2021年团队搞到种子轮融资,直接开源免费,瞬间炸开了锅。简单说,Remix是个死磕服务器边缘的JavaScript框架,前端暂时抱着React大腿,但核心是把渲染扔到服务器上跑。边缘服务器这玩意儿就像把饭店开到小区楼下,取餐贼快还便宜。Remix把自己叫做“中心栈”框架,专门在服务器和客户端之间当翻译官,适配各种平台。
部署那些事儿
Remix自己不带服务器,得自己找个宿主。支持Node.js或Deno环境,像Netlify边缘函数、DigitalOcean应用平台都能跑。整个编译过程用esbuild把请求转成对应平台能吃的格式,底层用的是Web Fetch API那套东西。
上手盘一个Remix项目,先得把环境支棱起来。电脑上装好Node.js(版本14以上),打开终端敲下面这串:
npx create-remix@latest my-remix-app跟着提示选部署目标,比如选“Netlify Edge”或者“Node.js”。装完后进目录:
cd my-remix-app
npm run dev浏览器打开http://localhost:3000,看到欢迎页面就算成了。想打包上线,跑npm run build,生成的生产代码在build/文件夹里。丢到对应平台前,得先配好平台需要的入口文件。比如Netlify平台,根目录下建个netlify.toml:
[build]
command = "npm run build"
publish = "public"[functions]
directory = “netlify/functions”
这里有个坑:不同平台的适配器不一样。创建项目时选错目标,后面改起来相当麻烦。要切换平台,得手动改remix.config.js里的serverModuleFormat和serverBuildTarget,新手直接重新初始化项目更省事。
路由玩出花
Remix的路由系统是基于文件系统的,但最骚的操作是嵌套路由。传统路由点一下跳整个新页面,嵌套路由是在同一页面上换不同区块,像王者荣耀里换皮肤只换英雄外观,地图和血条不动。
根文件结构
每个Remix应用都从root.tsx起家。这个文件里放着整个页面的骨架:<html>、<head>、<body>,还有一个关键组件<Outlet />。这玩意儿就像插线板,所有子路由的内容都从这儿怼进去。<Scripts>组件也别漏掉,没它页面上的JavaScript就废了一半。
看个最基础的文件夹布局:
app/
├── routes/
│ ├── index.tsx # 访问 / 时渲染
│ ├── about.tsx # 访问 /about 时渲染
│ └── blog/
│ └── index.tsx # 访问 /blog 时渲染
└── root.tsxOutlet组件真香
<Outlet />是嵌套路由的灵魂。假设要做一个关于页面,里面还想分团队介绍、联系方式等子板块。先在app/routes/about.tsx里写布局:
import { Outlet } from "@remix-run/react";
export default function About() {
return (
<div>
<h1>关于我们</h1>
<p>这里是固定头部,所有子页面都看得见</p>
<Outlet /> {/* 子路由的内容插这里 */}
</div>
);
}然后在app/routes/about/目录下建team.tsx和contact.tsx。访问/about/team时,team.tsx的内容就会从<Outlet />位置冒出来。这种玩法让代码按模块拆分,出bug也只崩当前区块,其他部分照常运行,体验绝绝子。
动态路由搞起来
URL里带参数的情况,比如博客文章详情页,用$前缀声明。文件名里的$后面跟啥都行,Remix只认这个前缀。
app/
├── routes/
│ ├── blog/
│ │ ├── $postId.tsx # 动态匹配 /blog/123、/blog/hello-world
│ │ └── index.tsx # 匹配 /blog
│ └── index.tsx在$postId.tsx里拿到URL上的参数,得靠loader函数的params参数:
import { useLoaderData } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/node";
export async function loader({ params }: LoaderArgs) {
// params.postId 就是URL上那个动态值
return { postId: params.postId };
}
export default function BlogPost() {
const { postId } = useLoaderData();
return <article>正在展示文章编号:{postId}</article>;
}缓存加速小妙招
虽然Remix必须跑在服务器上,但照样能蹭Jamstack的CDN缓存红利。静态站点生成器(SSG)是在构建时就把所有页面生成好,直接丢CDN上飞起。Remix不做传统SSG,但可以用HTTP头里的Cache-Control来缓存某个路由。
在root.tsx或者任意路由组件里导出headers函数:
export function headers() {
return {
"Cache-Control": "public, s-maxage=3600", // 缓存一小时
};
}更狠的操作是在remix.config.js旁边配一个headers文件。新建headers文件夹,里面放具体路由的缓存策略:
[[headers]]
for = "/static/*"[headers.values]
“Cache-Control” = “public, max-age=31536000, immutable”
注意嵌套路由的缓存策略有优先级:URL路径最长的那个路由说了算。比如/about/team会覆盖/about的缓存设置,别搞反了。
数据获取不烧脑
Remix里拿数据不用useState和useEffect那一套,因为数据早就在服务器上煮好了。每个路由模块可以导出一个loader函数,它只在服务器端跑,返回的数据通过useLoaderData注入组件。
来整个活:调用猫片API,随机展示一只喵。
import { useLoaderData } from "@remix-run/react";
export async function loader() {
const res = await fetch("https://cataas.com/cat?json=true");
const json = await res.json();
// 注意:cataas返回的json里有个_id字段
return { catId: json._id };
}
export default function RandomCat() {
const { catId } = useLoaderData();
return (
<img
src={`https://cataas.com/cat/${catId}`}
alt="一只随机喵星人"
/>
);
}动态路由里拿参数也方便,上面提过的params直接就能用。比如做一个标签页,URL里的$tag决定展示啥内容:
import { useLoaderData } from "@remix-run/react";
export async function loader({ params }) {
// 假设params.tag = "funny"
return { tag: params.tag };
}
export default function TagPage() {
const { tag } = useLoaderData();
return <div>当前标签:{tag},下面刷出一堆相关帖子</div>;
}这里有个隐藏福利:loader函数里可以直接用原生fetch,不用装axios啥的,因为Node环境和浏览器环境都支持。但别在loader里调浏览器专属API,比如localStorage,服务器上没这玩意儿。
其他实用小工具
Remix还塞了几个辅助函数,每个路由都能单独定义。
Action处理表单
action函数专门对付表单提交,用的是标准FormData。写一个加待办事项的示例:
import { redirect } from "@remix-run/node";
export async function action({ request }) {
const formData = await request.formData();
const title = formData.get("title");
// 假装调数据库存起来
const newId = Date.now();
return redirect(`/todos/${newId}`);
}
export default function TodoForm() {
return (
<form method="post">
<input name="title" type="text" />
<button type="submit">添加待办</button>
</form>
);
}Meta标签自定义
每个页面想换标题和描述,导出meta函数就行:
export function meta() {
return {
title: "关于我们 - 公司介绍",
description: "这家公司成立于2020年,专搞前端框架",
};
}Links按需加载CSS
不想让全局CSS文件污染所有页面?用links函数只在当前路由引入样式表:
import stylesHref from "./about.css";
export function links() {
return [
{ rel: "stylesheet", href: stylesHref },
{ rel: "icon", href: "/favicon.ico" },
];
}这样只有访问/about相关页面时,CSS才会被加载,其他路由完全不受影响。
页面跳转用Link
代替<a>标签,用Remix的<Link>组件实现客户端路由,不刷新整个页面。还能加prefetch属性提前加载数据:
import { Link } from "@remix-run/react";
export default function NavBar() {
return (
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/blog" prefetch="intent">博客</Link>
</nav>
);
}prefetch="intent"表示鼠标悬停或聚焦到链接时,偷偷在后台把目标页面的数据拉下来,点开瞬间加载,体验丝滑得像德芙。
上手Remix其实没啥门槛,把上面这些概念盘熟了,直接去啃官方那个Jokes示例应用或者K-Pop Stack,边抄边改,很快就能整个活出来。
