Remixを始めたいがWeb Fetch APIがわからない
Remixについて
Web標準に沿った技術を使っていて、ネット回線の速度が遅くても速く感じる。さらにエッジサーバで動く。
それは良いのですが、Web標準についてロクに学ばないままここまで来てしまったので、Remixのサイトで言われていることが1割くらいしかわかりません。これを期にちゃんと勉強したいと思います。
Web Fetch API
Remixのサイトには「RemixはWeb Fetch APIをベースにしています」とあります。
以下、MDNからの引用です。
『Fetch API は (ネットワーク越しの通信を含む) リソース取得のためのインターフェイスを提供しています。 XMLHttpRequest と似たものではありますが、より強力で柔軟な操作が可能です。』
XMLHttpRequestとは?
以下、MDNからの引用です。
『XMLHttpRequest (XHR) オブジェクトは、サーバーと対話するために使用されます。ページ全体を更新する必要なしに、データを受け取ることができます。これでユーザーの作業を中断させることなく、ウェブページの一部を更新することができます。 XMLHttpRequest は AJAX プログラミングで頻繁に使用されます』
つまり、fetchはサーバからリソースを取得するためのもので、XMLHttpRequestよりも便利でパワフル。ウェブページの一部だけを更新することが可能、すごい!ということですね。XMLHttpRequestはコールバック関数を扱わないといけないのに対し、fetchはPromiseオブジェクトを返すようです。
Response
ここまででRemixのサイトの1行目がぼんやりと理解できました。
次の行に進みます。
const res = await fetch(url);
That res is an instance of Response.
とあります。
Responseね、聞いたことある。ないと困るよね。
そんな知識で大丈夫か?
ググりました。
以下、MDNからの引用。
『Response は Fetch API のインターフェイスで、リクエストのレスポンスを表します。』
答えになっていないような??
Responseはレスポンスを表します。そんな小泉構文を見るためにググったわけではありません。
wikipediaを見てみましょう。
以下、ざっくりした和訳です。
『コンピュータサイエンスにおいて、リクエスト・レスポンスとは、コンピュータがネットワーク上で互いに通信するために用いる基本的な方法の1つである。第1のコンピュータが何らかのデータに対するリクエストを送信し、第2のコンピュータがそのリクエストに応答(レスポンス)するものである』
つまり、まずリクエスト(fetch)があり、その応答としてレスポンスがあるということですね。まず神様がいて、そこから天地が創られた、みたいな話でしょうか。
とにかくおおよそは理解できました。resはクライアントがfetchした結果、fetchした相手(サーバー)から返ってくるもの(JavaScript上はPromiseオブジェクト)ですね。
ここまでで1つ湧いた疑問として、FetchとRequestの違いがわたし、気になります。どう違うんでしょうか?
Request
MDNを見てみます。
『Request は Fetch API のインターフェイスで、リソースのリクエストを表します』
小泉構文でダメでした。ですが、別なページでは間接的にRequestとFetchの違いが説明されていました。
『fetch() の呼び出しに、リクエストしたいリソースへのパスを渡す代わりに、Request() コンストラクターを使用して Request オブジェクトを作成し、 fetch() メソッドの引数として渡すこともできます』
とあります。Fetch APIにおいては、fetchの内容を細かく伝えたいときはRequestを使ってもいいよ、みたいな扱いのようです。XMLHttpRequestのときはRequestが主役だったのになあ。
Response再び
Remixのサイトの4行目を見てみます。
And you can make a response yourself:
const res = new Response(
JSON.stringify({ hello: "there" })
);
『Responseを自分自身で作ることができるよ』とおっしゃっていますね。ただ、Responseはfetchしないと動かないので、これを動かすにはfetchが必要そうです。Responseするのは誰か?という話ですが、これはサーバーを指します。クライアントがfetchし、サーバーがResponseします。
new Response()によりコンストラクタを呼び出しています。JSON.stringifyは文字通り、JavaScriptのオブジェクトを文字列に変換する関数です。Responseコンストラクタの第1引数にはbodyが指定されています。bodyはリーダブルストリームとのことです。ストリームは送受信をする際のデータのことを指しているので、今は単にデータと理解しておけば良さそうです。より詳しくはMDNのHTTPメッセージのところにある本文(Body)を見ると良さそうです。
これでResponseを発行しました。以下続きのコードです。
const json = await res.json();
console.log(json);
// { hello: "there" }
res.json()はデータを取得して読み取るメソッドです。なのでこれはクライアント側の話のようです。
以下、続きを和訳した文章です。
RemixはサーバーサイドのAPIを選ぶのではなく、すべてのhttp処理にWeb Fetch APIを採用しています。
サーバー側であってもクライアント側であってもFetch APIということですね。Remixを使う側としては初めはちょっと混乱しそうです。でも、慣れてくればその方が良いのかもしれませんね。
remix-run/express のようなデプロイメントラッパーは、デプロイメントサーバーの API と Web API の間の単なるアダプタであることに注意してください。remix-run/express は、loader やサーバエントリから返されるWeb APIレスポンスをexpressレスポンスに解釈します。
Remixで便利なラッパーを用意してくれてるようです。
これらのAPIはRemixで直接利用することもできますが、通常は次のようなレスポンスヘルパーを利用することになるでしょう。
json
redirect
結局はnew Responseの代わりにRemixにあるjsonとredirectを使うみたいです。もう何がなんだかという気もしますが、Remixがこっちを使ってねと言っているのでそれに従います。公式には巻かれたほうが良いに決まっています。試しにjsonのページを見てみましょう。
import type { LoaderFunction } from "remix";
import { json } from "remix";
export const loader: LoaderFunction = () => {
// So you can write this:
return json({ any: "thing" });
// Instead of this:
return new Response(JSON.stringify({ any: "thing" }), {
headers: {
"Content-Type": "application/json; charset=utf-8"
}
});
};
『This is a shortcut for creating application/json responses.』とあります。確かにこれを見ると便利な気がする。5行が1行になるのはとってもお得ですね。
Hello, World
これまでの知識を使って"Hello, World"を開発サーバーから受け取りブラウザ上に表示するというのをやってみたいと思います。
まずは前回の最後に出てきたスクリプトにもう一度目を通してみます。
import type { LoaderFunction } from "remix";
import { json } from "remix";
export const loader: LoaderFunction = () => {
return json({ any: "thing" });
};
loaderというのが出てきていますね。しかもexportされています。よくわからないので公式を見てみます。
各routesは、レンダリング前にサーバー上で呼び出され、routesにデータを提供する「loader関数」を定義することができます。
とあります。routesというのは今の所webページと置き換えて良さそうです。
この関数はサーバー上でのみ実行されます。最初のサーバーレンダリングにおいて、HTMLドキュメントにデータを提供します。ブラウザでナビゲートされると、Remixはfetch経由でこの関数を呼び出します。つまり、データベースに直接アクセスしたり、サーバのみのAPIシークレットを使用したりすることができます。
とあります。なので、ページを読み込むとブラウザの内部ではfetchが呼び出されるようです。fetchはサーバーからリソースを取ってこようとし、サーバーに問い合わせします。サーバー内ではloader関数の中身(json関数)が呼び出されます。json関数はResponseを生成します。その結果、クライアントにResponseが返されます。
返されたResponseはどうやって見れば良いのかと疑問に思ったのですが、すぐ下に答えの例が書いてありました。
import { useLoaderData } from "remix";
import { prisma } from "../db";
export const loader = async () => {
return prisma.user.findMany();
};
export default function Users() {
const data = useLoaderData();
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
prismaは使ったことないんで「データベースからデータ取ってきてるんやな」ということしかわからないんですが、useLoaderDataでloaderのdataを引っ張ってこれています。やっぱりHooksは最高だぜ!
これでHello, Worldするために必要な部品は揃いました。
環境構築
必要となるNodeのバージョンは2022年2月現在14以上とのことでした。
以下引用npx create-remix@latest
# choose Remix App Server
cd [whatever you named the project]
npm run dev
ちなみに型がある方が好きなのでTypeScriptを選びました。
npm run devの後、指定のポートに飛んで「Welcome to Remix」が表示されていればOKです。
実行
app/routesフォルダにあるindex.tsxファイルを開きます。
Index関数のreturnの中身を全部消します。
export default function Index() {
return (
);
}
まだ何も返せていません。
Remixの関数と型をインポートし、loader関数を定義します。useLoaderDataでResponse結果を取得します。
import { useLoaderData, json } from "remix";
import type { LoaderFunction } from "remix";
export const loader: LoaderFunction = () => {
return json({ message: "Hello, Remix!" });
};
export default function Index() {
const data = useLoaderData();
return (
<h1>{data.message}</h1>
);
}
これでHello, Worldできるようになったと思われます。
ちなみに、json関数がなくてもオブジェクトならloaderの戻り値は自動的にResponseオブジェクトになります。なので、上記のコードはもっと短くなります。
Returning Response Instances
プレーンなオブジェクトを返すと、RemixはそれをFetch Responseに変換します。
以上でRemixの基礎的なことを検討するのは一旦終わりにいたします。あいまいな知識のまま話している部分もあるため、間違いなどありましたらコメントいただければ嬉しいです。
現場からは以上です。
Discussion