remix-routes

remix-routes 自动生成类型安全的辅助函数,用于操作 Remix 应用中的内部链接。

https://user-images.githubusercontent.com/465125/205243864-3493733d-8586-405f-94eb-088fdb87fd23.mp4

remix-routes 也适用于 remix-modules

安装

$ npm add remix-routes

设置

使用 Vite

remix-routes 插件添加到您的 vite.config.ts 中。

import { defineConfig } from "vite";
import { vitePlugin as remix } from "@remix-run/dev";
import { remixRoutes } from "remix-routes/vite";

export default defineConfig({
  plugins: [
    remix(),
    remixRoutes(options?)
  ],
});

支持的配置选项

  • strict: boolean
  • outDir: string

不使用 Vite

package.json 中的开发和构建脚本中添加 remix-routes

使用 concurrently

{
  "scripts": {
    "build": "remix-routes && remix build",
    "dev": "concurrently \"remix-routes -w\" \"remix dev\""
  }
}

使用 npm-run-all

{
  "scripts": {
    "build": "run-s build:*",
    "build:routes": "remix-routes",
    "dev": "run-p dev:*",
    "dev:routes": "remix-routes -w",
  }
}

用法

基本用法

import type { ActionFunction } from 'remix';
import { redirect } from 'remix';
import { $path } from 'remix-routes'; // <-- Import magical $path helper from remix-routes.

export const action: ActionFunction = async ({ request }) => {
  let formData = await request.formData();
  const post = await createPost(formData);

  return redirect($path('/posts/:id', { id: post.id })); // <-- It's type safe.
};

追加查询字符串

import { $path } from 'remix-routes';

$path('/posts/:id', { id: 6 }, { version: 18 }); // => /posts/6?version=18
$path('/posts', { limit: 10 }); // => /posts?limit=10
// You can pass any URLSearchParams init as param
$path('/posts/delete', [['id', 1], ['id', 2]]); // => /posts/delete?id=1&id=2

类型化查询字符串

通过在路由文件中导出名为 SearchParams 的类型来定义查询字符串的类型。

// app/routes/posts.tsx

export type SearchParams = {
  view: 'list' | 'grid',
  sort?: 'date' | 'views',
  page?: number,
}
import { $path } from 'remix-routes';

// The query string is type-safe.
$path('/posts', { view: 'list', sort: 'date', page: 1 });

您可以将此功能与 zodremix-params-helper 结合使用,以添加运行时参数检查。

import { z } from "zod";
import { getSearchParams } from "remix-params-helper";

const SearchParamsSchema = z.object({
  view: z.enum(["list", "grid"]),
  sort: z.enum(["price", "size"]).optional(),
  page: z.number().int().optional(),
})

export type SearchParams = z.infer<typeof SearchParamsSchema>;

export const loader = async (request) => {
  const result = getSearchParams(request, SearchParamsSchema)
  if (!result.success) {
    return json(result.errors, { status: 400 })
  }
  const { view, sort, page } = result.data;
}

检查参数

import type { ActionFunction } from 'remix';
import { useParams } from "remix";
import { $params } from 'remix-routes'; // <-- Import $params helper.

export const action: ActionFunction = async ({ params }) => {
  const { id } = $params("/posts/:id/update", params) // <-- It's type safe, try renaming `id` param.

  // ...
}

export default function Component() {
  const params = useParams();
  const { id } = $params("/posts/:id/update", params);
  ...
}

$routeId 辅助函数用于 useRouteLoaderData 路由 ID

remix-routes 导出包含存储库中所有有效路由 ID 列表的 RouteId 类型定义,并提供一个辅助函数 $routeId,该函数告诉 TypeScript 将给定的字符串限制为有效 RouteId 值之一。

import type { RouteId } from 'remix-routes';
import type { loader as postsLoader } from './_layout.tsx';
import { useRouteLoaderData } from '@remix-run/react';
import { $routeId } from 'remix-routes';

export default function Post() {
  const postList = useRouteLoaderData<typeof postsLoader>($routeId('routes/posts/_layout'));

命令行选项

  • -w: 监视更改并自动重建。
  • -s: 启用严格模式。在严格模式下,仅允许定义 SearchParams 类型的路由具有查询字符串。
  • -o: 指定 remix-routes.d.ts 的输出路径。如果未提供参数,则默认为 ./node_modules

TypeScript 集成

提供了一个 TypeScript 插件,可帮助您在路由文件之间导航。

安装

$ npm add -D typescript-remix-routes-plugin

设置

将插件添加到您的 tsconfig.json 中。

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "typescript-remix-routes-plugin"
      }
    ]
  }
}

在 VSCode 中选择工作区版本的 TypeScript。

Screenshot 2022-12-02 at 5 56 39 pm

许可证

MIT