ホームページ > 記事 > ウェブフロントエンド > サーバーアクションが修正されました
サーバー アクションは、クライアント コードを削減し、サーバーとの通信を必要とする対話を簡素化するアイデアとして登場しました。これは、開発者が記述するコードの量を減らすことができる優れたソリューションです。ただし、他のフレームワークでの実装にはいくつかの課題があり、それを見逃してはなりません。
この記事では、これらの問題と、Brisa がどのように解決策を見つけたかについて説明します。
サーバー アクションが提供するものを理解するには、サーバーとの通信が以前どのように行われていたかを確認することが役立ちます。おそらく、サーバーとのやり取りごとに次のアクションを実行することに慣れているでしょう:
これらの 7 つのアクションは、インタラクションごとに繰り返されます。たとえば、10 の異なるインタラクションを含むページがある場合、リクエストの種類、URL、送信されたデータ、顧客のステータスなどの詳細のみを変更して、非常に似たコードを 10 回繰り返します。
よく知られた例は次のとおりです
a:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
そしてサーバー内:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
クライアント バンドル サイズの増加…開発者のフラストレーション。
サーバー アクションは、これらのアクションを リモート プロシージャ コール (RPC) にカプセル化します。これにより、クライアントとサーバーの通信が管理され、クライアント上のコードが削減され、サーバー上のロジックが集中化されます。 :
ここでは、Brisa RPC によってすべてが行われます。
これは サーバーコンポーネント からのコードになります:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
ここでは、クライアント コードはサーバー コンポーネントであるため、開発者は作成しません。 onInput イベントはデバウンス後に受信され、クライアント RPC によって処理されます。一方、サーバー RPC は「アクション シグナル」を使用して、そのストア プロパティに登録されたシグナルを持つ Web コンポーネントをトリガーします。
ご覧のとおり、これによりサーバー コードが大幅に削減され、何よりも、対話のたびにクライアント上のコード サイズが増加しません。 RPC クライアント コードは、そのような対話が 10 回でも 1000 回でも、固定の 2 KB を占有します。これは、クライアント バンドル サイズが 0 バイト増加しても、増加しないことを意味します。
さらに、再レンダリングが必要な場合、これはサーバー上で行われ、HTML ストリーミングで返されるため、ユーザーは、後でクライアント上でこの作業を行う必要があった従来の方法よりもはるかに早く変更を確認できます。サーバーの応答。
このようにして:
React などの他のフレームワークでは、イベントではなく、フォーム onSubmit の一部であるアクションのみに焦点を当ててきました。
クライアント コードを追加せずにサーバー コンポーネントから処理する必要がある非フォーム イベントが多数あるため、これは問題です。たとえば、自動提案を実行する入力の onInput、無限スクロールをロードする onScroll、onMouseOver ホバーなどを実行します
多くのフレームワークも、HTMX ライブラリをサーバー アクションの非常に異なる代替手段としてみなしていますが、実際には、HTML に追加の属性を追加するだけで、サーバー アクションと組み合わせてより多くの可能性をもたらす非常に優れたアイデアがもたらされています。 RPC クライアントは、前に見た debounceInput などを考慮に入れることができます。また、リクエストの作成中にスピナーを表示するインジケーターや、RPC クライアントでエラーを処理できるようにするなど、他の HTMX アイデアもあります。
サーバー アクションが React に導入されたとき、新しいパラダイム シフトが起こり、多くの開発者はサーバー アクションを使用する際にメンタル チップを変更する必要がありました。
私たちは、Web プラットフォームにできるだけ馴染みのあるものにしたいと考えました。これにより、サーバーからシリアル化されたイベントをキャプチャし、そのプロパティを使用できるようになります。少し異なる唯一のイベントは、FormData が転送済みで e.formData プロパティを持つ onSubmit ですが、残りの イベント プロパティ は対話可能です。これはフォームをリセットするの例です:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
この例にはクライアント コードがまったくなく、サーバー アクション中に CSS を使用してインジケーターで送信ボタンを無効にすることができます。これにより、フォームが 2 回送信されなくなり、同時に、サーバー上でアクションを実行し、e.formData でフォーム データにアクセスし、イベントの同じ API を使用してフォームをリセットします。
精神的には、Web プラットフォーム での作業と非常に似ています。唯一の違いは、すべてのサーバー コンポーネントのすべてのイベントがサーバー アクションであることです。
このようにして、実際の関心事の分離が行われ、「ユーザーサーバー」 または 「使用クライアント」 を に入れる必要はありません。コンポーネントもう
.すべてはサーバー上でのみ実行されるということに注意してください。唯一の例外は、クライアント上で実行されるsrc/web-componentsフォルダであり、そこではイベントは正常です。
Brisa では、サーバー アクションは DOM イベントであるかのようにサーバー コンポーネント間で伝播されます。つまり、サーバー アクションからサーバー コンポーネントのプロパティのイベントを呼び出すことができ、親サーバー コンポーネントのサーバー アクションが実行されます。
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
この場合、onAfterMyAction イベントが親コンポーネントで実行され、サーバー上でアクションを実行できます。これは、複数のサーバー コンポーネントに影響を与えるアクションをサーバー上で行う場合に非常に役立ちます。
特にここ数週間、X (旧 Twitter) でのいくつかの議論の後、Web コンポーネントは少し眉をひそめるようになりました。ただし、HTML の一部であるため、次のような理由からサーバー アクションと対話するには最良の方法です。
Web コンポーネントで属性を使用するには、Web コンポーネントを使用せずにサーバーからクライアントにデータを送信するのと同じ方法でシリアル化が必要です。したがって、両方を使用すると、追加のシリアル化を管理する必要はありません。
注: HTML のストリーミングと差分アルゴリズムによる処理については、興味があればこの別の記事で説明しています。
Brisa では、サーバー アクションにさらに強力なパワーを与える新しい概念を追加しました。この概念は 「アクション シグナル」 と呼ばれます。 「アクションシグナル」の考え方は、2 つのストアがあり、1 つは サーバー に、もう 1 つは クライアント にあるということです。
なぜ 2 つの店舗があるのですか?
デフォルトのサーバーストアはリクエストレベルでのみ存在します。また、クライアントに表示されないデータを共有することもできます。たとえば、ミドルウェアにユーザーを設定させ、任意のサーバー コンポーネント内の機密ユーザー データにアクセスできるようにすることができます。リクエストレベルで動作することにより、各リクエストには独自のストアがあり、どのデータベースにも保存されないため、異なるリクエスト間で競合が発生することは不可能になります。リクエストが完了すると、デフォルトで終了します。
一方、クライアントストアでは、消費されたときの各プロパティがシグナルであるストアです。つまり、更新されると、そのシグナルをリッスンしていた Web コンポーネントが反応します。
しかし、「アクションシグナル」 の新しいコンセプトは、リクエストを超えてサーバーストアの寿命を延長できるということです。これを行うには、次のコードを使用する必要があります:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
この transferToClient メソッドは、サーバー データを クライアント ストアに共有し、シグナルに変換します。この方法では、多くの場合、サーバーから再レンダリングを行う必要がなく、サーバー アクションから、そのシグナルをリッスンしていた Web コンポーネントのシグナルに反応させるだけで済みます。
このストア転送により、サーバー ストアの寿命が決まりました:
初期サーバー コンポーネントをレンダリング → クライアント → サーバー アクション → クライアント → サーバー アクション...
つまり、リクエスト レベルでのみ存在する状態から、ページ間のナビゲーションと互換性のある永続的に存在する に変わります。
例:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });この例では、クライアントで使用されるのではなく、サーバー アクションで再利用され、最終的にサーバー アクションの再レンダリングで再利用されるように、エラー ストア プロパティの存続期間を延長します。この場合、非機密データであるため、暗号化する必要はありません。このコード例は、再レンダリングも含めてすべてサーバー上で行われ、サーバー上でのこのレンダリング後にエラーが表示されます。サーバー RPC は HTML チャンクをストリーミングで送信し、クライアント RPC はそれを処理して差分を取得し、エラーをユーザーにフィードバックします。
6. 機密データのみを暗号化する
Next.js 14 などの多くのフレームワークは、セキュリティ レベルでこのデータを暗号化して、次で使用されるデータのスナップショットを作成します。レンダリングの時間。これは多かれ少なかれ問題ありませんが、データの暗号化には常に関連する計算コストがあり、常に機密データであるとは限りません。
Brisa では、これを解決するためにさまざまなリクエストがあり、最初のレンダリングでは値があり、サーバー アクションではこのリクエストに含まれる値をキャプチャできます。
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />これは場合によっては便利ですが、常に役立つわけではありません。たとえば、Math.random を実行すると、最初のレンダリングとサーバー アクションの実行では確実に異なります。
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
これが、サーバー ストアからクライアント ストアデータを転送するために、「アクション シグナル」の概念を作成した理由です。 >、開発者は、暗号化するかしないを自由に決定できます。
場合によっては、サーバー アクションからデータベースにクエリを実行する代わりに、関連付けられた暗号化が必要な場合でも、最初のレンダリングですでに存在するデータを転送したい場合があります。これを行うには、単に次を使用します:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });次の場合:
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />Web コンポーネント (クライアント) 内では常に暗号化されますが、サーバー上では常に復号化されます。
注: Brisa は暗号化に aes-256-cbc を使用します。これは、OpenSSL が推奨する情報を安全に暗号化するために使用される暗号化アルゴリズムの組み合わせです。暗号化キーはプロジェクトのビルド中に生成されます。
結論
Brisa を試してみることをお勧めします。ターミナルで次のコマンドを実行するだけです: bun create brisa か、いくつかの例を試してどのように機能するかを確認してください。
参考文献
以上がサーバーアクションが修正されましたの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。