在ReScript x Next.js环境中引入GraphQL库”Relay”
在基于Next.js,ReScript和tailwind的环境中,尝试使用GraphQL进行开发。
图灵完备查询语言
Facebook开发的查询语言。
用户可以在客户端指定响应格式。
当在选择使用React作为GraphQL客户端时,两个主要的选择似乎是Apollo Client和Relay。
GraphQL的官方网站上还列出了服务器端和其他客户端的选择。
阿波罗客户端
Apollo Client
阿波罗客户端
Rescript Apollo Client
Rescript 阿波罗客户端
转发
转播
重新修改的中继
实施
按照RescriptRelay的说明进行基本操作。
增加接力
为了利用Suspense,将React库设为实验版本。
yarn add react@0.0.0-experimental-4e08fb10c react-dom@0.0.0-experimental-4e08fb10c
yarn add relay-runtime@11.0.0 relay-compiler@11.0.0 react-relay@11.0.0 relay-config@11.0.0
yarn add --dev rescript-relay graphql reason-promise bs-fetch
中继设定
配置
module.exports = {
src: "./src",
schema: "./schema.graphql",
artifactDirectory: "./src/__generated__",
customScalars: {
Datetime: "string",
Color: "Color.t",
},
};
为了将Next.js的res文件统一放置到一个文件夹中,需要借助RescriptRelay配置文件中src目录的指定方法影响将其移到src文件夹中。
mv pages components bindings src/
"sources": [
- {
- "dir": "components",
- "subdirs": true
- },
- {
- "dir": "pages",
- "subdirs": true
- },
- {
- "dir": "bindings",
- "subdirs": true
- }
+ {
+ "dir": "src",
+ "subdirs": true
+ }
],
+ "ppx-flags": [
+ "rescript-relay/ppx"
+ ],
"bs-dependencies": [
- "@rescript/react"
+ "@rescript/react",
+ "rescript-relay",
+ "reason-promise",
+ "bs-fetch"
],
如果使用了Tailwind,也需要更改目录。
module.exports = {
purge: [
- './pages/**/*.res',
- './components/**/*.res',
+ './src/pages/**/*.res',
+ './src/components/**/*.res',
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
"scripts": {
"dev": "concurrently \"bsb -clean-world -make-world -w\" \"next dev\"",
"dev:reason": "bsb -clean-world -make-world -w",
"dev:next": "next dev",
"build": "bsb -clean-world -make-world && next build",
- "start": "next start"
+ "start": "next start",
+ "relay": "rescript-relay-compiler",
+ "relay:watch": "rescript-relay-compiler --watch"
},
const withTM = require('next-transpile-modules')(['bs-platform'])
module.exports = withTM({
- pageExtensions: ['jsx', 'js', 'bs.js']
+ pageExtensions: ['jsx', 'js', 'bs.js'],
+ experimental: {
+ reactMode: 'concurrent'
+ }
})
开启实验性设置。
如果忘记了,会出现Cannot hydrate Suspense in legacy mode错误,所以要注意。
es6改为commonjs
由于bs-fetch的关系,将bsconfig.json中的package-specs从es6更改为commonjs。
参考にしながら、ReasonMLを使用してNext.jsでページ遷移し、Web APIを呼び出し、レスポンスを表示します。bs-fetchとbs-jsonを追加します。
"package-specs": {
- "module": "es6",
+ "module": "commonjs",
"in-source": true
},
写配置组件
/* This is just a custom exception to indicate that something went wrong. */
exception Graphql_error(string)
/**
* A standard fetch that sends our operation and variables to the
* GraphQL server, and then decodes and returns the response.
*/
let fetchQuery: RescriptRelay.Network.fetchFunctionPromise = (
operation,
variables,
_cacheConfig,
_uploadables,
) => {
open Fetch
fetchWithInit(
Env.graphQlUrl,
RequestInit.make(
~method_=Post,
~body=Js.Dict.fromList(list{
("query", Js.Json.string(operation.text)),
("variables", variables),
})
->Js.Json.object_
->Js.Json.stringify
->BodyInit.make,
~headers=HeadersInit.make({
"content-type": "application/json",
"accept": "application/json",
// "authorization": "Bearer " ++ Env.graphQlToken, // 今回は認可(authorization)を要しないのでこの行は不要。graphCMS等用いる場合記述する。
}),
(),
),
) |> Js.Promise.then_(resp =>
if Response.ok(resp) {
Response.json(resp)
} else {
Js.Promise.reject(Graphql_error("Request failed: " ++ Response.statusText(resp)))
}
)
}
let network = RescriptRelay.Network.makePromiseBased(~fetchFunction=fetchQuery, ())
let environment = RescriptRelay.Environment.make(
~network,
~store=RescriptRelay.Store.make(
~source=RescriptRelay.RecordSource.make(),
~gcReleaseBufferSize=10, /* This sets the query cache size to 10 */
(),
),
(),
)
// api
@val external graphQlUrl:string = "process.env.NEXT_PUBLIC_GRAPHQL_URL"
// @val external graphQlToken:string = "process.env.NEXT_PUBLIC_GRAPHQL_TOKEN" // authorizationが必要な場合
在使用Next.js时,如果要在浏览器中使用环境变量,则需要在开头加上NEXT_PUBLIC_前缀。
NEXT_PUBLIC_GRAPHQL_URL="<YOUR_GRAPHQL_API_URL>"
# NEXT_PUBLIC_GRAPHQL_TOKEN="<YOUR_GRAPHQL_API_TOKEN>" # authorizationが必要な場合
这次我们将使用SpaceX API。
将以下内容改写为:
这次我们将使用SpaceX的应用程序接口。
NEXT_PUBLIC_GRAPHQL_URL=https://api.spacex.land/graphql/
下载架构
从API下载模式。
yarn add --dev get-graphql-schema
yarn get-graphql-schema https://api.spacex.land/graphql/ > schema.graphql
供应商
@react.component
let make = (~children) => <RescriptRelay.Context.Provider environment=RelayEnv.environment>
<div>
children
</div>
</RescriptRelay.Context.Provider>
let default = make
import '../../styles/main.css'
import App from '../components/App.bs'
const MyApp = ({ Component, pageProps }) => (<App>
<Component {...pageProps} />
</App>);
export default MyApp;
GraphQL 打击组件
module MyQuery = %relay(
`query FetchMissionsQuery {
missions {
name
id
description
}
}`
)
@react.component
let make = () => {
let response = MyQuery.use(~variables=(), ())
let missions = switch (response.missions) {
| Some(data) => data
| _ => []
}
<div>
<div><p className="font-black">{React.string("missions")}</p></div>
{
missions
->Belt.Array.map(
mission => switch(mission) {
| Some(data) => <div key={Belt.Option.getWithDefault(data.id, "XXXXX")}>
<div><p className="font-medium">{React.string(Belt.Option.getWithDefault(data.name, ""))}</p></div>
<div>{React.string(Belt.Option.getWithDefault(data.description, ""))}</div>
<hr/>
</div>
| _ => <></>
}
)->React.array
}
</div>
}
let default = make
关于响应类型,会在通过下载的schema.graphql创建的.res文件中,使用realy:watch命令进行记录。
/* @generated */
%%raw("/* @generated */")
module Types = {
@@ocaml.warning("-30")
type rec response_missions = {
name: option<string>,
id: option<string>,
description: option<string>,
}
type response = {
missions: option<array<option<response_missions>>>,
}
type rawResponse = response
type variables = unit
}
为什么option是id呢?如果使用iteration,每个组件都需要有一个独特的key,但是如果没有id的话应该会被报错…
因为有option存在,去除它会很麻烦,所以希望能有其他的解决办法。
@react.component
let make = () => {
<div>
<Header />
<div className="py-6 md:py-12">
<div className="container px-4 mx-auto">
<React.Suspense fallback={<div>{React.string(`Loading...`)}</div>}>
<FetchMissions />
</React.Suspense>
</div>
</div>
</div>
}
let default = make
使用Relay的组件外部使用React.Suspense包裹,并在Suspense标签内指定在出现fallback的情况下(即尚未加载或加载失败时)要显示的组件。
只在外部进行包裹即可,例如在src/components/App.res的
的外部使用Suspense包裹也可以。但是整体会被视为正在加载中的状态,所以基本上不会这样做。
执行dev
yarn relay:watch
yarn dev:rescript
将`yarn dev:reason`命名为其他名称。
yarn dev:next
访问http://localhost:3000/missions。
印象
对于查询名称的指定有一些特殊要求。
源目录会受限制。
可以使用GraphQL和fetch进行调用。
当存在多个终点时该怎么办?(可能不太常见的情况?)
做了但不成功的事情
重写-阿波罗客户端
一) 背景
二)根据
三) 由于
依赖项是 reason-react。尽管尝试了强制修改并重新构建,但并未成功。
发生了无效的hook错误,无法修复。
交换
提供返回《星球大战》角色名称等相关API集合。最初我打算使用这个。
swapi-qlaphql(GitHub)
swapi-graphql 测试页面
我在使用get-graphql-schema时收到了无效的JSON。为什么会这样?
实际应用
我们正在使用graphCMS以相同的结构。
请查阅参考文献。
或者
请踏入参考文献。
GraphQL
GraphQL代码库、工具和服务
Apollo
Apollo客户端
Rescript Apollo客户端
Relay
RescriptRelay
开始使用RescriptRelay | RescriptRelay
使用悬挂功能获取数据(实验性功能)
使用ReasonML在Next.js中进行页面转换并显示Web API的响应 – 添加bs-fetch、bs-json
swapi-qlaphql(GitHub)
swapi-graphql测试页面
SpaceX API
Next.js – 基本功能:环境变量 – 将环境变量暴露给浏览器
graphCMS