レバレッジ stale-while-revalidate HTTP Cache-Control
拡張は一般的な手法です。キャッシュされた(古い)アセットがキャッシュ内で見つかった場合はそれを使用し、キャッシュを再検証して、必要に応じて新しいバージョンのアセットで更新します。したがって、名前はstale-while-revalidate
です。
stale-while-revalidate
作品リクエストが初めて送信されると、ブラウザによってキャッシュされます。次に、同じリクエストが2回送信されると、最初にキャッシュがチェックされます。そのリクエストのキャッシュが利用可能で有効な場合、キャッシュはレスポンスとして返されます。次に、キャッシュの古さがチェックされ、古くなった場合は更新されます。ザ・ キャッシュの古さ max-age
によって決定されますCache-Control
に存在する値stale-while-revalidate
とともにヘッダー。
これにより、 ページの読み込みが速い 、キャッシュされたアセットがクリティカルパスに存在しなくなったため。それらは即座にロードされます。また、開発者はキャッシュの使用頻度と更新頻度を制御するため、ブラウザーがユーザーに過度に古いデータを表示するのを防ぐことができます。
読者は、サーバーが応答で特定のヘッダーを使用し、ブラウザーにそこからヘッダーを取得させることができる場合、使用する必要があると考えているかもしれません。 React とキャッシュ用のフック?
c法人s法人の違い
サーバーとブラウザーのアプローチは、静的コンテンツをキャッシュする場合にのみうまく機能することがわかりました。 stale-while-revalidate
の使用はどうですか動的APIの場合? max-age
の適切な値を思い付くのは難しいですおよびstale-while-revalidate
その場合。多くの場合、キャッシュを無効にして、リクエストが送信されるたびに新しい応答をフェッチするのが最善のオプションです。これは事実上、キャッシュがまったくないことを意味します。しかし、Reactと フック 、もっとうまくやれる。
stale-while-revalidate
API用HTTPのstale-while-revalidate
に気づきましたAPI呼び出しなどの動的リクエストではうまく機能しません。
最終的に使用したとしても、ブラウザはキャッシュまたは新しい応答のいずれかを返しますが、両方は返しません。リクエストが送信されるたびに新しい応答が必要なため、これはAPIリクエストには適していません。ただし、新しい応答を待つと、アプリの有意義なユーザビリティが遅れます。
どうしようか?
カスタムキャッシュメカニズムを実装します。その中で、キャッシュと新しい応答の両方を返す方法を見つけます。 UIでは、キャッシュされた応答は、利用可能になると新しい応答に置き換えられます。ロジックは次のようになります。
このアプローチでは、すべてのAPIリクエストがキャッシュされるため、UIを瞬時に更新できますが、新しい応答データが利用可能になるとすぐに表示されるため、UIが最終的に正確になります。
このチュートリアルでは、これを実装する方法について段階的なアプローチを示します。このアプローチを呼びます 更新中の古い UIは実際には リフレッシュ 新鮮な反応が得られたとき。
このチュートリアルを開始するには、最初にデータをフェッチするAPIが必要です。幸いなことに、利用可能なモックAPIサービスはたくさんあります。このチュートリアルでは、 reqres.in 。
フェッチするデータは、page
のユーザーのリストです。クエリパラメータ。フェッチコードは次のようになります。
fetch('https://reqres.in/api/users?page=2') .then(res => res.json()) .then(json => { console.log(json); });
このコードを実行すると、次の出力が得られます。これは、非反復バージョンです。
{ page: 2, per_page: 6, total: 12, total_pages: 2, data: [ { id: 7, email: ' [email protected] ', first_name: 'Michael', last_name: 'Lawson', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/follettkyle/128.jpg' }, // 5 more items ] }
これは実際のAPIのようなものであることがわかります。応答にはページ付けがあります。 page
クエリパラメータはページの変更を担当し、データセットには合計2つのページがあります。
ReactアプリでAPIをどのように使用するかを見てみましょう。それを行う方法がわかったら、キャッシュ部分を理解します。クラスを使用してコンポーネントを作成します。コードは次のとおりです。
import React from 'react'; import PropTypes from 'prop-types'; export default class Component extends React.Component { state = { users: [] }; componentDidMount() { this.load(); } load() { fetch(`https://reqres.in/api/users?page=${this.props.page}`) .then(res => res.json()) .then(json => { this.setState({ users: json.data }); }); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { const users = this.state.users.map(user => ( {user.first_name} {user.last_name}
)); return {users} ; } } Component.propTypes = { page: PropTypes.number.isRequired };
page
を取得していることに注意してくださいprops
を介した値。これは、実際のアプリケーションでよく発生します。また、componentDidUpdate
があります毎回APIデータを再フェッチする関数this.props.page
変化します。
この時点で、APIはページごとに6つのアイテムを返すため、6人のユーザーのリストが表示されます。
これにstale-while-refreshキャッシュを追加する場合は、アプリロジックを次のように更新する必要があります。
これは、グローバルなCACHE
を持つことで実現できます。キャッシュを一意に格納するオブジェクト。一意性のために、this.props.page
を使用できますCACHE
のキーとしての値オブジェクト。次に、上記のアルゴリズムをコーディングするだけです。
import apiFetch from './apiFetch'; const CACHE = {}; export default class Component extends React.Component { state = { users: [] }; componentDidMount() { this.load(); } load() { if (CACHE[this.props.page] !== undefined) { this.setState({ users: CACHE[this.props.page] }); } apiFetch(`https://reqres.in/api/users?page=${this.props.page}`).then( json => { CACHE[this.props.page] = json.data; this.setState({ users: json.data }); } ); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { // same render code as above } }
キャッシュが見つかるとすぐに返され、新しい応答データがsetState
によって返されるためまた、これは、シームレスなUI更新があり、2回目のリクエスト以降のアプリでの待ち時間がなくなることを意味します。これは完璧であり、一言で言えば、更新中の古い方法です。
apiFetch
ここでの関数はfetch
のラッパーに他なりません。キャッシュの利点をリアルタイムで確認できるようにします。これは、ランダムなユーザーをusers
のリストに追加することによって行われます。 APIリクエストによって返されます。また、それにランダムな遅延を追加します。
export default async function apiFetch(...args) { await delay(Math.ceil(400 + Math.random() * 300)); const res = await fetch(...args); const json = await res.json(); json.data.push(getFakeUser()); return json; } function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
getFakeUser()
ここでの関数は、偽のユーザーオブジェクトの作成を担当します。
これらの変更により、APIは以前よりもリアルになりました。
これを考えると、page
を変更するとComponent
に渡された小道具メインコンポーネントから、APIキャッシングの動作を確認できます。クリックしてみてください トグル 数秒に1回ボタン このCodeSandbox 次のような動作が見られるはずです。
アジャイルコーチとは
よく見ると、いくつかのことが起こります。
これが、私たちが探していた更新中の古いキャッシュです。しかし、このアプローチにはコードの重複の問題があります。キャッシュを備えた別のデータフェッチコンポーネントがある場合、どうなるか見てみましょう。このコンポーネントは、最初のコンポーネントとは異なる方法でアイテムを表示します。
これは、最初のコンポーネントからロジックをコピーするだけで実行できます。 2番目のコンポーネントは、猫のリストを示しています。
const CACHE = {}; export default class Component2 extends React.Component { state = { cats: [] }; componentDidMount() { this.load(); } load() { if (CACHE[this.props.page] !== undefined) { this.setState({ cats: CACHE[this.props.page] }); } apiFetch(`https://reqres.in/api/cats?page=${this.props.page}`).then( json => { CACHE[this.props.page] = json.data; this.setState({ cats: json.data }); } ); } componentDidUpdate(prevProps) { if (prevProps.page !== this.props.page) { this.load(); } } render() { const cats = this.state.cats.map(cat => ( {cat.name} (born {cat.year})
)); return {cats} ; } }
ご覧のとおり、ここに含まれるコンポーネントロジックは、最初のコンポーネントとほとんど同じです。唯一の違いは、要求されたエンドポイントにあり、リストアイテムの表示が異なることです。
ここで、これら両方のコンポーネントを並べて示します。あなたが見ることができます それらは同じように動作します :
この結果を達成するには、多くのコード複製を行う必要がありました。このようなコンポーネントが複数あると、重複するコードが多すぎます。
asp net webapiチュートリアル
重複しない方法でそれを解決するために、データをフェッチしてキャッシュし、それを小道具として渡すための高次コンポーネントを持つことができます。理想的ではありませんが、機能します。ただし、1つのコンポーネントで複数のリクエストを実行する必要がある場合、複数の高次コンポーネントを使用すると、すぐに醜くなります。
次に、render propsパターンがあります。これは、クラスコンポーネントでこれを行うためのおそらく最良の方法です。これは完全に機能しますが、繰り返しになりますが、「ラッパー地獄」になりがちであり、現在のコンテキストをバインドする必要がある場合があります。これは優れた開発者エクスペリエンスではなく、フラストレーションやバグにつながる可能性があります。
これは、Reactフックがその日を救う場所です。コンポーネントロジックを再利用可能なコンテナにボックス化して、複数の場所で使用できるようにします。 Reactフック React 16.8で導入され、関数コンポーネントでのみ機能します。 Reactのキャッシュ制御(特にフックを使用したコンテンツのキャッシュ)に入る前に、まず関数コンポーネントで単純なデータフェッチを行う方法を見てみましょう。
関数コンポーネントでAPIデータをフェッチするには、useState
を使用しますおよびuseEffect
フック。
useState
クラスコンポーネントのstate
に類似していますおよびsetState
。このフックを使用して、関数コンポーネント内に状態のアトミックコンテナを作成します。
useEffect
はライフサイクルフックであり、次のように考えることができます。 組み合わせ componentDidMount
、componentDidUpdate
、およびcomponentWillUnmount
のuseEffect
に渡される2番目のパラメーター依存関係配列と呼ばれます。依存関係の配列が変更されると、コールバックは最初の引数としてuseEffect
に渡されます。再度実行されます。
これらのフックを使用してデータフェッチを実装する方法は次のとおりです。
import React, { useState, useEffect } from 'react'; export default function Component({ page }) { const [users, setUsers] = useState([]); useEffect(() => { fetch(`https://reqres.in/api/users?page=${page}`) .then(res => res.json()) .then(json => { setUsers(json.data); }); }, [page]); const usersDOM = users.map(user => ( {user.first_name} {user.last_name}
)); return {usersDOM} ; }
page
を指定するuseEffect
への依存関係として、page
のたびにuseEffectコールバックを実行するようにReactに指示します。変更されます。これはcomponentDidUpdate
と同じです。また、useEffect
常に最初に実行されるため、componentDidMount
のように機能しますあまりにも。
useEffect
コンポーネントのライフサイクルメソッドに似ています。そのため、渡されたコールバック関数を変更して、クラスコンポーネントにあったstale-while-refreshキャッシュを作成できます。 useEffect
以外はすべて同じです針。
const CACHE = {}; export default function Component({ page }) { const [users, setUsers] = useState([]); useEffect(() => { if (CACHE[page] !== undefined) { setUsers(CACHE[page]); } apiFetch(`https://reqres.in/api/users?page=${page}`).then(json => { CACHE[page] = json.data; setUsers(json.data); }); }, [page]); // ... create usersDOM from users return {usersDOM} ; }
したがって、関数コンポーネントで動作する更新中の古いキャッシュがあります。
2番目のコンポーネントについても同じことができます。つまり、それを関数に変換し、stale-while-refreshキャッシュを実装します。 結果 クラスで持っていたものと同じになります。
しかし、それはクラスコンポーネントよりも優れているわけではありませんか?それでは、カスタムフックの機能を使用して、複数のコンポーネントで使用できるモジュール式の古い更新中のロジックを作成する方法を見てみましょう。
まず、カスタムフックに移動するロジックを絞り込みます。前のコードを見ると、それがuseState
であることがわかります。およびuseEffect
部。具体的には、これがモジュール化するロジックです。
const [users, setUsers] = useState([]); useEffect(() => { if (CACHE[page] !== undefined) { setUsers(CACHE[page]); } apiFetch(`https://reqres.in/api/users?page=${page}`).then(json => { CACHE[page] = json.data; setUsers(json.data); }); }, [page]);
汎用にする必要があるため、URLを動的にする必要があります。したがって、url
が必要です。引数として。複数のリクエストが同じpage
を持つ可能性があるため、キャッシュロジックも更新する必要があります。値。幸いなことに、page
のときエンドポイントURLに含まれている場合、一意のリクエストごとに一意の値が生成されます。したがって、URL全体をキャッシュのキーとして使用できます。
const [data, setData] = useState([]); useEffect(() => { if (CACHE[url] !== undefined) { setData(CACHE[url]); } apiFetch(url).then(json => { CACHE[url] = json.data; setData(json.data); }); }, [url]);
それだけです。関数内にラップした後、カスタムフックを作成します。以下をご覧ください。
const CACHE = {}; export default function useStaleRefresh(url, defaultValue = []) { const [data, setData] = useState(defaultValue); useEffect(() => { // cacheID is how a cache is identified against a unique request const cacheID = url; // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); } // fetch new data apiFetch(url).then(newData => { CACHE[cacheID] = newData.data; setData(newData.data); }); }, [url]); return data; }
defaultValue
という別の引数を追加したことに注意してください。それに。このフックを複数のコンポーネントで使用する場合、API呼び出しのデフォルト値は異なる場合があります。そのため、カスタマイズ可能にしています。
data
についても同じことができます。 newData
を入力しますオブジェクト。カスタムフックがさまざまなデータを返す場合は、newData
を返すことをお勧めします。 newData.data
ではありませんコンポーネント側でそのトラバーサルを処理します。
これで、更新中の古いキャッシュを大幅に解除するカスタムフックができたので、これをコンポーネントにプラグインする方法を説明します。削減できたコードの量に注目してください。コンポーネント全体が3つのステートメントになりました。それは大きな勝利です。
import useStaleRefresh from './useStaleRefresh'; export default function Component({ page }) { const users = useStaleRefresh(`https://reqres.in/api/users?page=${page}`, []); const usersDOM = users.map(user => ( {user.first_name} {user.last_name}
)); return {usersDOM} ; }
2番目のコンポーネントについても同じことができます。次のようになります。
export default function Component2({ page }) { const cats = useStaleRefresh(`https://reqres.in/api/cats?page=${page}`, []); // ... create catsDOM from cats return {catsDOM} ; }
このフックを使用すると、どれだけの定型コードを節約できるかを簡単に確認できます。コードも見栄えがします。アプリ全体の動作を確認したい場合は、 このCodeSandbox 。
anglejsでウェブサイトを構築する
useStaleRefresh
への読み込みインジケーターの追加基本がわかったので、カスタムフックに機能を追加できます。たとえば、isLoading
を追加できます一意のリクエストが送信され、その間に表示するキャッシュがない場合は常にtrueとなるフックの値。
これを行うには、isLoading
に個別の状態を設定します。フックの状態に合わせて設定します。つまり、キャッシュされたWebコンテンツが利用できない場合は、true
に設定し、それ以外の場合はfalse
に設定します。
更新されたフックは次のとおりです。
export default function useStaleRefresh(url, defaultValue = []) { const [data, setData] = useState(defaultValue); const [isLoading, setLoading] = useState(true); useEffect(() => { // cacheID is how a cache is identified against a unique request const cacheID = url; // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); setLoading(false); } else { // else make sure loading set to true setLoading(true); } // fetch new data apiFetch(url).then(newData => { CACHE[cacheID] = newData.data; setData(newData.data); setLoading(false); }); }, [url]); return [data, isLoading]; }
これで、新しいisLoading
を使用できます。コンポーネントの価値。
export default function Component({ page }) { const [users, isLoading] = useStaleRefresh( `https://reqres.in/api/users?page=${page}`, [] ); if (isLoading) { return Loading ; } // ... create usersDOM from users return {usersDOM} ; }
そのことに注意してください それが終わったら 、一意のリクエストが初めて送信され、キャッシュが存在しない場合は、「読み込み中」というテキストが表示されます。
useStaleRefresh
すべてをサポートasync
関数async
をサポートすることで、カスタムフックをさらに強力にすることができます。 GET
だけでなく機能ネットワーク要求。その背後にある基本的な考え方は同じままです。
function.name
の単純な連結およびarguments
ユースケースのキャッシュキーとして機能します。これを使用すると、フックは次のようになります。
import { useState, useEffect, useRef } from 'react'; import isEqual from 'lodash/isEqual'; const CACHE = {}; export default function useStaleRefresh(fn, args, defaultValue = []) { const prevArgs = useRef(null); const [data, setData] = useState(defaultValue); const [isLoading, setLoading] = useState(true); useEffect(() => { // args is an object so deep compare to rule out false changes if (isEqual(args, prevArgs.current)) { return; } // cacheID is how a cache is identified against a unique request const cacheID = hashArgs(fn.name, ...args); // look in cache and set response if present if (CACHE[cacheID] !== undefined) { setData(CACHE[cacheID]); setLoading(false); } else { // else make sure loading set to true setLoading(true); } // fetch new data fn(...args).then(newData => { CACHE[cacheID] = newData; setData(newData); setLoading(false); }); }, [args, fn]); useEffect(() => { prevArgs.current = args; }); return [data, isLoading]; } function hashArgs(...args) { return args.reduce((acc, arg) => stringify(arg) + ':' + acc, ''); } function stringify(val) { return typeof val === 'object' ? JSON.stringify(val) : String(val); }
ご覧のとおり、関数名とその文字列化された引数の組み合わせを使用して、関数呼び出しを一意に識別し、キャッシュします。これは単純なアプリでは機能しますが、このアルゴリズムは衝突が発生しやすく、比較が遅くなります。 (シリアル化できない引数を使用すると、まったく機能しません。)したがって、実際のアプリの場合は、適切なハッシュアルゴリズムの方が適切です。
ここで注意すべきもう1つのことは、 useRef
。 useRef
囲んでいるコンポーネントのライフサイクル全体を通してデータを永続化するために使用されます。 args
以降は配列(JavaScriptのオブジェクト)です。フックを使用してコンポーネントを再レンダリングするたびに、args
が発生します。変更する参照ポインタ。しかしargs
最初のuseEffect
の依存関係リストの一部です。だからargs
変更するとuseEffect
が作成されます何も変更されていない場合でも実行されます。これに対抗するために、古いものと現在のものを深く比較しますargs
を使用して isEqual useEffect
だけにしますargs
の場合にコールバックを実行実際に変更されました。
これで、この新しいuseStaleRefresh
を使用できます次のようにフックします。 defaultValue
の変更に注意してくださいここに。これは汎用フックであるため、data
を返すためにフックに依存していません。応答オブジェクトを入力します。
export default function Component({ page }) { const [users, isLoading] = useStaleRefresh( apiFetch, [`https://reqres.in/api/users?page=${page}`], { data: [] } ); if (isLoading) { return Loading ; } const usersDOM = users.data.map(user => ( {user.first_name} {user.last_name}
)); return {usersDOM} ; }
あなたはでコード全体を見つけることができます このCodeSandbox 。
useStaleRefresh
この記事で作成したフックは、Reactフックで何ができるかを示す概念実証です。コードを試して、アプリケーションに適合できるかどうかを確認してください。
nodejsリアルタイムの例
または、次のような人気のある、手入れの行き届いたオープンソースライブラリを介して、stale-while-refreshを活用することもできます。 swr または 反応クエリ 。どちらも強力です ライブラリ APIリクエストを支援する多くの機能をサポートします。
ReactHooksはゲームチェンジャーです。これらにより、コンポーネントロジックをエレガントに共有できます。これは、コンポーネントの状態、ライフサイクルメソッド、およびレンダリングがすべて1つのエンティティ(クラスコンポーネント)にパッケージ化されていたため、以前は不可能でした。これで、それらすべてに異なるモジュールを使用できます。これは、構成可能性とより良いコードの記述に最適です。私が作成するすべての新しいReactコードに関数コンポーネントとフックを使用しています。これをすべてのReact開発者に強くお勧めします。
古いキャッシュは、古いデータが含まれているため、使用に適さないキャッシュです。 HTTPのコンテキストでは、これはキャッシュのmax-ageまたはs-maxageが期限切れになったときに発生します。これは、長期間保管されていた食品が古くなるのと似ているため、「古くなったキャッシュ」という用語が使用されます。
古いコンテンツとは、有効期限が切れたコンテンツです。 CDNのコンテキストでは、これは、コンテンツが添付されている存続可能時間(TTL)値を超えて存在していることを意味します。したがって、使用には適していないため、「古い」という用語が使用されます。
React Hookは、React関数コンポーネントの状態メソッドとライフサイクルメソッドにフックできるようにする関数です。このため、componentDidMount、componentDidUpdate、componentWillUnmountなどのライフサイクルメソッドにアクセスできるため、フックを使用するとクラスコンポーネントの使用をスキップできます。
Cache-Controlは、リクエストに関連付けられたキャッシュを指定するHTTPヘッダーです。リクエストヘッダーとレスポンスヘッダーの両方で個別に定義できます。 Cache-Controlを使用すると、no-cache、must-revalidate、max-ageなどのディレクティブを指定して、リクエストの応答をキャッシュする方法を指定する必要があります。
HTTPのCache-Controlのmust-revalidateディレクティブは、キャッシュが古くなったら、さらに再検証するまで使用してはならないことを指定しています。再検証は、キャッシュのオリジンサーバーに接続し、新しいバージョンが存在するかどうかを確認することで行われます。新しいバージョンが存在する場合は、フェッチされてから使用されます。
キャッシュされたデータは厳密には重要ではありませんが、効果的なパフォーマンスが向上するため望ましいです。システムはキャッシュされたデータを即座に提供できますが、新しいデータのロードには時間がかかります。キャッシュされたデータはディスク領域を占有するため、必要に応じて削除してもかまいません。