グローバルステータス管理の悩み-ワッフルカードリスト(feat.context)
ワッフルカードサービスが気になるなら!
ワッフルカードの表示サービス:https://waffle-card.com/
ワッフル寄宿センター見学:https://github.com/waffle-card
😱 ワッフルカードのリスト効率が低下
ワッフルカードのホームページには3つのラベルがあります.
오늘의 카드
:ワッフルカードのリストをすべてレンダリングします.나의 카드
:ワッフルカードをレンダリング.관심 카드
:レンダリングされたワッフルカードのリスト.ホームページを最初に実装する場合、ラベルを1つ押すたびに、各ラベルは独自のネットワーク要求を発行し、対応するラベルのワッフルカードリストデータを受信することによって実装されます.そして、
와플카드 리스트
状態を更新した後、Propで子供に伝えます.ビデオをよく見ると、ラベルをクリックするたびにネットワークリクエストが発行され、ロードされた螺旋線がレンダリングされます.
上記の構成の問題点は次のとおりです.
上記の問題を解決するために、設計を次の構造に変更します.
와플카드 리스트
をグローバル状態に分ける.홈페이지
와플카드 리스트
ショップにデータを要求する.와플카드 리스트
店舗にキャッシュされたデータがあれば、キャッシュされたデータを返します.와플카드 리스트
ストアからネットワーク要求があり、受信したデータをストアに格納してキャッシュし、データを返す.その結果,ラベルをクリックしたりページから飛び出したりするたびにキャッシュされたデータを優先的に利用し,盲目的なネットワーク要求を回避することができる.
また、ステータスをグローバルファイルとして使用することで、コンポーネントの興味を分離し、Propドリルを回避して再構築のメリットを得ることができます.
最終的には、より高速なレンダリングが行われ、ユーザーの可用性が向上します.
ラベルを押すたびにロードが見えず、ページを離れて戻ってくるたびにキャッシュされたデータをうまく利用できます.
ワッフルカードのグローバルステータス管理コードの表示
前の記事で述べたrecoilのキャッシュの問題でcontextを使用してカスタマイズし実現しました.
まずコード全体を置いてから始めます.次の説明では、追加コードを削除し、コアコードのみについて説明します.
import { createContext, useCallback, useContext, useState } from 'react';
import { waffleCardApi } from '@/apis';
import { userState } from '@/recoils';
import { useRecoilValue } from 'recoil';
import { WaffleCardType } from '@/types';
const cachedWaffleCards: { [type: string]: WaffleCardType[] | null } = {
total: null,
my: null,
like: null,
};
const WaffleCardsStateContext = createContext<WaffleCardType[] | null>([]);
const WaffleCardsDispatchContext = createContext<{
setWaffleCardsByType: (type: string, waffleCards: WaffleCardType[]) => void;
refreshWaffleCards: (type?: string) => void;
}>({
setWaffleCardsByType: () => {
return;
},
refreshWaffleCards: () => {
return;
},
});
interface WaffleCardsProviderProps {
children: React.ReactElement | React.ReactElement[];
}
export const WaffleCardsProvider = ({ children }: WaffleCardsProviderProps) => {
const [waffleCards, setWaffleCards] = useState<WaffleCardType[] | null>([]);
const user = useRecoilValue(userState);
const setWaffleCardsByType = useCallback(
async (type, options = {}) => {
if (!user && type !== 'total') {
setWaffleCards(() => []);
return;
}
if (options?.cached && cachedWaffleCards[type]) {
setWaffleCards(() => cachedWaffleCards[type]);
return;
}
const waffleCardsCommand: {
[command: string]: () => Promise<WaffleCardType[]>;
} = {
total: async () => {
const { data: waffleCards } = await waffleCardApi.getWaffleCards();
return waffleCards;
},
my: async () => {
if (cachedWaffleCards.total && user) {
return cachedWaffleCards.total.filter(
waffleCard => waffleCard.user.id === user.id,
);
}
const { data: waffleCards } = await waffleCardApi.getMyWaffleCard();
return waffleCards;
},
like: async () => {
if (cachedWaffleCards.total && user) {
return cachedWaffleCards.total.filter(waffleCard =>
waffleCard.likeUserIds.includes(user.id),
);
}
const { data: waffleCards } =
await waffleCardApi.getMyLikedWaffleCards();
return waffleCards;
},
};
try {
const waffleCards = await waffleCardsCommand[type]();
cachedWaffleCards[type] = [...waffleCards];
setWaffleCards(() => [...waffleCards]);
} catch (error: any) {
console.error(`in WaffleCards Recoil: ${error.message}`);
return [];
}
},
[user],
);
const refreshWaffleCards = async (type = 'total') => {
Object.keys(cachedWaffleCards).forEach(async type => {
cachedWaffleCards[type] = null;
});
await setWaffleCardsByType(type);
};
return (
<WaffleCardsStateContext.Provider value={waffleCards}>
<WaffleCardsDispatchContext.Provider
value={{
setWaffleCardsByType,
refreshWaffleCards,
}}
>
{children}
</WaffleCardsDispatchContext.Provider>
</WaffleCardsStateContext.Provider>
);
};
export const useWaffleCardsState = () => useContext(WaffleCardsStateContext);
export const useWaffleCardsDispatch = () =>
useContext(WaffleCardsDispatchContext);
まず、ストレージロール内のオブジェクトを作成してデータをキャッシュします.const cachedWaffleCards: { [type: string]: WaffleCardType[] | null } = {
total: null,
my: null,
like: null,
};
ワッフルカードカタログはラベルによって3種類に分けてキャッシュされます.そしてWaffleCardsProviderの骨格をつかむ
export const WaffleCardsProvider = ({ children }: WaffleCardsProviderProps) => {
const [waffleCards, setWaffleCards] = useState<WaffleCardType[] | null>([]);
const setWaffleCardsByType = async (type, options = {}) => {
};
const refreshWaffleCards = async (type = 'total') => {
};
return (
<WaffleCardsStateContext.Provider value={waffleCards}>
<WaffleCardsDispatchContext.Provider
value={{
setWaffleCardsByType,
refreshWaffleCards,
}}
>
{children}
</WaffleCardsDispatchContext.Provider>
</WaffleCardsStateContext.Provider>
);
};
WaffleCardsProvider
内部には2つのコアメソッドがある.setWaffleCardsByType
waffleCards
更新状態の関数タイプとオプション(
{cached: boolean}
)を因子とする.タイプに応じて対応するネットワークリクエストを発行した後、
cachedWaffleCards
オブジェクトに格納し、waffleCards
ステータスを更新する.{cached: true}
が選択されている場合は、キャッシュされたデータを優先的にチェックし、キャッシュされたデータがあれば、ネットワーク要求を行わず、既存のcachedWaffleCards
オブジェクトに格納されているデータを利用して更新waffleCards
ステータスを更新する.キャッシュされたデータがない場合は、ネットワーク要求が行われます.refreshWaffleCards
ステータスを更新するための関数ですネットワークリクエスト、更新、更新ステータスの削除、リクエストに使用する関数以下に示す2つの方法を実装する.export const WaffleCardsProvider = ({ children }: WaffleCardsProviderProps) => {
const [waffleCards, setWaffleCards] = useState<WaffleCardType[] | null>([]);
const setWaffleCardsByType = async (type, options = {}) => {
// 만약 {cached: boolean}이면 캐싱된 데이터로 waffleCards 상태 업데이트
if (options?.cached && cachedWaffleCards[type]) {
setWaffleCards(() => cachedWaffleCards[type]);
return;
}
// type별로 네트워 요청을 분기처리 하기 위한 객체
const waffleCardsCommand = {
total: async () => {
const { data: waffleCards } = await waffleCardApi.getWaffleCards();
return waffleCards;
},
my: async () => {
const { data: waffleCards } = await waffleCardApi.getMyWaffleCard();
return waffleCards;
},
like: async () => {
const { data: waffleCards } =
await waffleCardApi.getMyLikedWaffleCards();
return waffleCards;
},
};
// type별로 네트워크 요청후 waffleCards 상태 업데이트
try {
const waffleCards = await waffleCardsCommand[type]();
cachedWaffleCards[type] = [...waffleCards];
setWaffleCards(() => [...waffleCards]);
} catch (error: any) {
console.error(`in WaffleCards Recoil: ${error.message}`);
return [];
}
};
// 캐싱된 데이터를 삭제하고 네트워크 요청을 하여 waffleCards 상태를 최신으로 갱신
const refreshWaffleCards = async (type = 'total') => {
Object.keys(cachedWaffleCards).forEach(async type => {
cachedWaffleCards[type] = null;
});
await setWaffleCardsByType(type);
};
return (
<WaffleCardsStateContext.Provider value={waffleCards}>
<WaffleCardsDispatchContext.Provider
value={{
setWaffleCardsByType,
refreshWaffleCards,
}}
>
{children}
</WaffleCardsDispatchContext.Provider>
</WaffleCardsStateContext.Provider>
);
};
recoilのようなクリーンな実装ではないが,元のターゲットショップでデータに関するネットワークリクエストを管理しキャッシュの実装を完了した.振り返る
contextの最も致命的な欠点は固定的なパターンがないことだと思います.Reduxはフラックスモードという固定モードがあり,誰がコードを記述してもコード量が大きくても理解は難しくないと考えている.(もちろん...私はまだ...)
しかしcontextには固定的なパターンがないため,著者の主観が強く溶け込み,うっかりすると他人に理解されにくい可能性がある.リダイレクトモードはuseredcherを用いて実現できる人もいれば,userStateを単純に用いて実現できる人もいれば,外部モジュールを組み合わせることで実現できる人もいる.
私のコードを振り返ると、固定的なパターンはないと思います.まだパターンに対する経験や知識がないので、私が考えている論理だけを体現しているような気がします.
次回は反応式照会を利用したいと思います.
Reference
この問題について(グローバルステータス管理の悩み-ワッフルカードリスト(feat.context)), 我々は、より多くの情報をここで見つけました https://velog.io/@younoah/waffle-card-context-zyg93qh6テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol