Remix和GraphQL可以和谐共存❤️ 本包含有基本的实用函数,可以帮助你实现这一点。
更具体地说,最新版本的remix-graphql
可以帮助你完成以下任务:
以下是一些未来可能实现的酷点子:
你可以使用你喜欢的包管理器安装remix-graphql
。它依赖于graphql
包,所以请确保也安装了该包。
# 使用`npm` npm install graphql remix-graphql # 或使用`yarn` yarn add graphql remix-graphql
它还列出了一些Remix包作为对等依赖。(如果你使用Remix CLI设置项目,你很可能已经安装了它们。)如果遇到意外错误,请再次检查是否安装了以下包:
@remix-run/dev
@remix-run/react
@remix-run/serve
remix
remix-graphql
导入此模块不适用于浏览器环境,它仅在服务器上工作。你可以通过从扩展名为.server.js
(或.server.ts
)的文件中导入,强制Remix编译器永远不会在客户端包中包含remix-graphql
的内容。
// 这样做不行,实际上会抛出错误: import { anything } from "remix-graphql"; // 应该这样做: import { anything } from "remix-graphql/index.server";
remix-graphql
保持简单,让你自己决定定义GraphQL schema的最佳方式。在所有需要"将schema传递给remix-graphql
"的地方 ,相应的函数都期望一个GraphQLSchema
对象。
这意味着以下所有方法都可以用来定义schema:
graphql
包中的GraphQLSchema
类(显然...)makeExecutableSchema
(来自@graphql-tools/schema
)合并两者nexus
和makeSchema
我们建议从一个文件中导出schema,例如app/graphql/schema.server.ts
。通过使用.server.ts
扩展名,你可以确保这些代码不会被发送到浏览器。(这是对Remix编译器的提示,在构建浏览器包时应忽略此模块。)
loaders
和actions
都只是简单的函数,给定一个Request
返回一个Response
。使用remix-graphql
,你可以使用GraphQL来处理这个请求!以下是一个完整且可工作的示例,展示了它是如何工作的:
// app/routes/index.tsx import type { GraphQLError } from "graphql"; import { Form } from "remix"; import type { ActionFunction, LoaderFunction } from "@remix-run/node";; import { processRequestWithGraphQL } from "remix-graphql/index.server"; // 从你导出schema的地方导入 import { schema } from "~/graphql/schema"; const ALL_POSTS_QUERY = /* GraphQL */ ` query Posts($limit: Int) { posts(limit: $limit) { id title likes author { name } } } `; export const loader: LoaderFunction = (args) => processRequestWithGraphQL({ // 传递Remix传给loader函数的参数。 args, // 提供你的schema。 schema, // 提供应该执行的GraphQL操作。这也可以是一个mutation, // 它被命名为`query`是为了与通过HTTP发送GraphQL请求的常见命名保持一致。 query: ALL_POSTS_QUERY, // 可选地提供执行操作时应使用的变量。如果不传递,`remix-graphql`将从以下位置派生变量: // - ...路由参数。 // - ...提交的`formData`(如果存在)。 variables: { limit: 10 }, // 可选地传递一个对象,其属性应包含在执行上下文中。 context: {}, // 可选地传递一个函数,为成功执行的操作派生自定义HTTP状态码。 deriveStatusCode( // 执行的结果。 executionResult: ExecutionResult, // 默认情况下会返回的状态码,即如果不传递`deriveStatusCode`函数。 defaultStatusCode: number ) { return defaultStatusCode; }, }); const LIKE_POST_MUTATION = /* GraphQL */ ` mutation LikePost($id: ID!) { likePost(id: $id) { id likes } } `; // `processRequestWithGraphQL`函数可以用于loader和action! export const action: ActionFunction = (args) => processRequestWithGraphQL({ args, schema, query: LIKE_POST_MUTATION }); export default function IndexRoute() { const { data } = useLoaderData<LoaderData>(); if (!data) { return "哎呀,出了点问题 :("; } return ( <main> <h1>博客文章</h1> <ul> {data.posts.map((post) => ( <li key={post.id}> {post.title}(作者:{post.author.name}) <br /> {post.likes} 个赞 <Form method="post"> {/* `remix-graphql`会自动将所有提交的表单数据 转换为GraphQL操作的同名变量 */} <input hidden name="id" value={post.id} /> <button type="submit">点赞</button> </Form> </li> ))} </ul> </main> ); } type LoaderData = { data?: { posts: { id: string; title: string; likes: number; author: { name: string }; }[]; }; errors?: GraphQLError[]; };
在上面示例的末尾,你可以看到从loader函数返回的数据类型必须手动定义。由于GraphQL是强类型的,如果你想的话,可以自动化这个过程!
首先,你需要从你的schema生成内省数据作为JSON,并将其存储在本地文件中。为此,你可以创建一个简单的脚本,如下所示:
// app/graphql/introspection.{js,ts} import fs from "fs"; import { introspectionFromSchema } from "graphql"; import path from "path"; import { schema } from "./schema"; fs.writeFileSync( path.join(__dirname, "introspection.json"), JSON.stringify(introspectionFromSchema(schema)) );
通常,你不希望将生成的JSON文件提交到版本控制中,所以我们建议将其添加到你的.gitignore
文件中。
为了更方便地运行这个脚本,在你的package.json
中创建一个简单的NPM脚本:
{ "scripts": { // 如果你用JavaScript创建了脚本 "introspection": "node app/graphql/introspection.js", // 如果你用TypeScript创建了脚本(确保在这种情况下安装 // `esbuild-register`作为开发依赖) "introspection": "node --require esbuild-register app/graphql/introspection.ts" } }
要实际从你的查询和变更生成类型,我们推荐使用GraphQL Code Generator。为此,你需要安装几个依赖:
# 使用`npm` npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations # 或使用`yarn` yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
快完成了!现在在你项目的根目录创建一个名为codegen.yml
的配置文件,内容如下:
overwrite: true # 之前生成的内省数据存储的路径 schema: "app/graphql/introspection.json" # 匹配所有包含操作定义的文件的glob documents: "app/routes/**/*.{ts,tsx}" generates: # 这是生成的类型将被存储的路径 app/graphql/types.ts: plugins: - "typescript" - "typescript-operations" config: skipTypename: true