クライアントオンリーなアプリを構築するのに慣れている場合、サーバーとクライアントにまたがった state management(状態管理) について怖く感じるかもしれません。このセクションでは、よくある落とし穴を回避するためのヒントを提供します。 ## サーバーでは state の共有を避ける ブラウザは state を保持します(Browsers are _stateful_) — ユーザーがアプリケーションとやりとりする際に、state はメモリ内に保存されます。一方、サーバーは state を保持しません(Servers are _stateless_) — レスポンスの内容は、完全にリクエストの内容によって決定されます。 概念としては、そうです。現実では、サーバーは長い期間存在し、複数のユーザーで共有されることが多いです。そのため、共有される変数にデータを保存しないことが重要です。例えば、こちらのコードを考えてみます: ```js // @errors: 7034 7005 /// file: +page.server.js let user; /** @type {import('./$types').PageServerLoad} */ export function load() { return { user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { default: async ({ request }) => { const data = await request.formData(); // NEVER DO THIS! user = { name: data.get('name'), embarrassingSecret: data.get('secret') }; } } ``` この `user` 変数はサーバーに接続する全員に共有されます。もしアリスが恥ずかしい秘密を送信し、ボブがアリスのあとにページにアクセスした場合、ボブはアリスの秘密を知ることになります (訳注: アリスやボブについては[こちら](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%AA%E3%82%B9%E3%81%A8%E3%83%9C%E3%83%96))。さらに付け加えると、アリスが後でサイトに戻ってきたとき、サーバーは再起動していて彼女のデータは失われているかもしれません。 代わりに、[`cookies`](load#Cookies) を使用してユーザーを _認証_ し、データベースにデータを保存すると良いでしょう。 ## load に副作用を持たせない 同じ理由で、`load` 関数は _純粋(pure)_ であるべきです — 副作用(side-effect)を持つべきではありません (必要なときに使用する `console.log(...)` は除く)。例えば、コンポーネントで store やグローバルな state の値を使用できるようにするために、`load` 関数の内側で store やグローバルな state に書き込みをしたくなるかもしれません: ```js /// file: +page.js // @filename: ambient.d.ts declare module '$lib/user' { export const user: { set: (value: any) => void }; } // @filename: index.js // ---cut--- import { user } from '$lib/user'; /** @type {import('./$types').PageLoad} */ export async function load({ fetch }) { const response = await fetch('/api/user'); // NEVER DO THIS! user.set(await response.json()); } ``` 前の例と同様に、これはあるユーザーの情報を _すべての_ ユーザーに共有される場所に置くことになります。代わりに、ただデータを返すようにしましょう… ```js /// file: +page.js /** @type {import('./$types').PageServerLoad} */ export async function load({ fetch }) { const response = await fetch('/api/user'); +++ return { user: await response.json() };+++ } ``` …そしてそのデータを必要とするコンポーネントに渡すか、[`page.data`](load#page.data) を使用してください。 SSR を使用していない場合は、あるユーザーのデータを別の人に誤って公開してしまうリスクはありません。しかし、それでも `load` 関数の中で副作用を持つべきではありません — 副作用がなければ、あなたのアプリケーションはより理解がしやすいものになります。 ## context と共に state と store を使う グローバルな state が使用できないのであれば、どうやって `page.data` や他の [app state]($app-state) (や [app stores]($app-stores)) を使用できるようにしているのだろう、と思うかもしれません。その答えは、サーバーの app state や app stores は Svelte の [context API](/tutorial/svelte/context-api) を使用しているから、です — state (や store) は `setContext` でコンポーネントツリーにアタッチされ、subscribe するときは `getContext` で取得します。同じことを独自の state でも行うことができます: ```svelte ``` ```svelte
Welcome {user().name}
``` > [!NOTE] We're passing a function into `setContext` to keep reactivity across boundaries. Read more about it [here](/docs/svelte/$state#Passing-state-into-functions) > [!LEGACY] > You also use stores from `svelte/store` for this, but when using Svelte 5 it is recommended to make use of universal reactivity instead. SSR でページがレンダリングされる場合、階層が深いページやコンポーネントで context ベースの state の値が更新されても、その親のコンポーネントの値には影響しません。なぜなら、その state の値が更新されるときには、親のコンポーネントはすでにレンダリング済みだからです。それとは対照的に、クライアントでは (CSR が有効な場合(デフォルトでは有効))、値は伝搬し、階層の上位にあるコンポーネントやページ、レイアウトに値が反映されます。従って、ハイドレーション中の state の更新による値の 'ちらつき(flashing)' を避けるため、通常は state を上から下にコンポーネントに渡すことを推奨します。 SSR を使用していない場合 (そして将来的にも SSR を使用する必要がないという保証がある場合) は、context API を使用しなくても、共有されるモジュールの中で state を安全に保持することができます。 ## コンポーネントとページの state は保持される アプリケーションの中を移動するとき、SvelteKit はすでに存在するレイアウトやページコンポーネントを再利用します。例えば、このようなルート(route)があるとして… ```svelteReading time: {Math.round(estimatedReadingTime)} minutes