refactor client cache - boostcampwm-2022/web33-Mildo GitHub Wiki

โœ‚๏ธ ๋ถ„๋ฐฐ๋œ ์ด์Šˆ

  • ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ (์บ์‹ฑ ์ ์šฉ)
  • apis ๊ฐœ์„  (axios ํƒ€์ž…, ์ธ์Šคํ„ด์Šค)
  • ๋กœ๋”ฉ jotai suspense
  • eject ๋˜๋Œ๋ฆฌ๊ธฐ

๐Ÿšฉ ๊ตฌํ˜„ ๋ชฉํ‘œ

  • ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ์ ์šฉ
  • eject ๋˜๋Œ๋ฆฌ๊ธฐ
  • ๋กœ๋”ฉ jotai suspense๋กœ ๋ณ€๊ฒฝ
  • apis ๋ชจ๋“ˆ ๊ฐœ์„ 

๐Ÿ€ ์„ธ๋ถ€ ๋ชฉํ‘œ

  • ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ํ•™์Šต ๋ฐ ์ ์šฉ

๐Ÿ–ฅ๏ธ ๊ตฌํ˜„ ๋‚ด์šฉ

๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ์ ์šฉ

์™œ ์บ์‹ฑํ•˜๋ ค ํ•˜๋Š”๊ฐ€?

  • ๋งˆ์ปค๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋ชจ๋‹ฌ์ฐฝ์ด ํ‘œ์‹œ๋œ๋‹ค. ๋ชจ๋‹ฌ์ฐฝ์€ 1๋‹จ๊ณ„, 2๋‹จ๊ณ„๋กœ ๋‚˜๋‰œ๋‹ค. 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์—์„œ ํ•ด๋‹น ์ง€์—ญ์˜ 24์‹œ๊ฐ„ ์ด๋‚ด์˜ ์ธ๊ตฌ ๋ฐ€๋„ ์ •๋ณด๋ฅผ ๊ทธ๋ž˜ํ”„๋กœ ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค.
  • 24์‹œ๊ฐ„ ์ด๋‚ด์˜ ์ธ๊ตฌ ๋ฐ€๋„ ์ •๋ณด๋Š” 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์„ ์—ด ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„์— ์š”์ฒญ๋œ๋‹ค. ์ด ์‘๋‹ต์€ ์•ฝ 1.2~1.8์ดˆ๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค.
  • ์ฆ‰, 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์„ ์—ด ๋•Œ๋งˆ๋‹ค 1.2์ดˆ~1.8์ดˆ๋ฅผ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค.
  • 24์‹œ๊ฐ„ ์ด๋‚ด์˜ ์ธ๊ตฌ ๋ฐ€๋„ ์ •๋ณด๋Š” 30๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์‹œ๊ฐ„์„ฑ์ด ํฌ๊ฒŒ ๋ณด์žฅ๋˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
  • ๊ทธ๋ ‡๋‹ค๋ฉด ํ•œ ๋ฒˆ ์š”์ฒญํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋ช‡ ๋ถ„ ์ •๋„ ์ง€๋‚˜๋„ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค˜๋„ ๋˜์ง€ ์•Š์„๊นŒ?
  • ๊ทธ๋ž˜์„œ jotai๋ฅผ ์ด์šฉํ•ด ํ•œ ๋ฒˆ ์‘๋‹ต ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋Š” ์ „์—ญ ์ƒํƒœ๋กœ ์ €์žฅํ•˜๊ณ , ์ดํ›„ ๊ฐ™์€ ์ง€์—ญ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค˜์•ผ ํ•  ๋•Œ์—๋Š” ์ „์—ญ ์ƒํƒœ์—์„œ ๊ฐ€์ ธ์˜ค๋„๋ก ํ–ˆ๋‹ค.

์™œ ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๋ฅผ ์ ์šฉํ•˜๋ ค ํ•˜๋Š”๊ฐ€?

  • jotai๋ฅผ ์ด์šฉํ•ด ์ „์—ญ ์ƒํƒœ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ์บ์‹ฑ ํšจ๊ณผ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, ํ•œ ๋ฒˆ ์ „์—ญ ์ƒํƒœ๋กœ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋Š” ์ดํ›„ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค.

  • ์ฆ‰, ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ ํ•˜์ง€ ์•Š์œผ๋ฉด ์•„๋ฌด๋ฆฌ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋„ ์ตœ์ดˆ๋กœ ๋ชจ๋‹ฌ์ฐฝ์„ ์—ด์—ˆ๋˜ ๊ทธ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ 24์‹œ๊ฐ„ ์ด๋‚ด์˜ ์ธ๊ตฌ ๋ฐ€๋„ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.

    ex) ์˜คํ›„ 2์‹œ์— ์ฒ˜์Œ ๋ชจ๋‹ฌ์ฐฝ์„ ์—ด๊ณ  ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ์˜คํ›„ 9์‹œ์— ๋ชจ๋‹ฌ์ฐฝ์„ ์—ด๊ฒŒ ๋˜๋ฉด, ์˜คํ›„ 2์‹œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ 24์‹œ๊ฐ„ ์ด๋‚ด์˜ ์ธ๊ตฌ ๋ฐ€๋„ ์ •๋ณด ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค.

  • ์ข€ ๋” ๋ณธ๊ฒฉ์ ์ธ ์บ์‹ฑ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๋˜ ๋„์ค‘ ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๋กœ ์บ์‹ฑ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ , ํ•™์Šต์—๋„ ๋„์›€์ด ๋  ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•˜์—ฌ ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๋ฅผ ์ ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  • ์ด์ „ ์ฝ”๋“œ

    ...
    
    const [secondLevelInfoCache, setSecondLevelInfoCache] = useAtom(
      secondLevelInfoCacheAtom
    );
    const [graphInfo, setGraphInfo] = useState<SecondLevelTimeInfoCacheTypes>({});
    
    ...
    
    // ์ „์—ญ์— areaName์„ ํ‚ค๋กœ ๊ฐ–๊ณ  ์žˆ๋Š” ์†์„ฑ์ด ์žˆ์œผ๋ฉด hit
    if (secondLevelInfoCache[areaName]) {
      setGraphInfo(secondLevelInfoCache[areaName]);
      return;
    }
    
    // ์•„๋‹ˆ๋ฉด api ํ˜ธ์ถœ
    const { data } = await apis.getPastInformation(areaName);
    
    // ์ „์—ญ ์ƒํƒœ๋กœ ์ €์žฅํ•˜๊ณ 
    setSecondLevelInfoCache({ ...secondLevelInfoCache, [areaName]: data });
    // ๊ทธ๋ž˜ํ”„ ๋ณด์—ฌ์ฃผ๊ธฐ
    setGraphInfo(data);
  • ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ์ ์šฉ ํ›„

    // hooks/useGraphInfo.tsx
    const useGraphInfo = (
      enabled: boolean,
      firstLevelInfo: SortAllAreasTypes | null,
      success: (data: graphInfoResponseTypes | null) => void
    ) => {
    	const { data: graphInfoResponse } = useQuery(
    	    ['getGraphInfo', firstLevelInfo ? firstLevelInfo[0] : ''],
    	    async () => {
    	      if (!firstLevelInfo) {
    	        return null;
    	      }
    	      const result = await apis.getPastInformation(firstLevelInfo[0]);
    	
    	      return result;
    	    },
    	    {
    	      enabled,
    	      staleTime: QUERY_TIME.STALE_TIME, // 5๋ถ„
    	      cacheTime: QUERY_TIME.CACHE_TIME, // 30๋ถ„
    	      onSuccess: data => {
    	        success(data);
    	      },
    	      onError: e => {
    	        console.log('error', e);
    	      }
    	    }
    	  );
    	return [graphInfoResponse];
    };
    
    export default useGraphInfo;
    • ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์‹ถ์–ด, ์ปค์Šคํ…€ ํ›…์œผ๋กœ ์ƒ์„ฑ
    1. query key

      • ์ง€์—ญ์— ๋”ฐ๋ผ 24์‹œ๊ฐ„ ์ด๋‚ด์˜ ์ธ๊ตฌ ๋ฐ€๋„ ์ •๋ณด ๋ฅผ ์š”์ฒญํ•˜๋ฏ€๋กœ 1๋ฒˆ์งธ ์ธ๋ฑ์Šค์— ์ง€์—ญ ์ด๋ฆ„์„ ๋ถ™์—ฌ์คŒ

        ex) [โ€™getGraphInfoโ€™, โ€˜์„œ์šธ์—ญโ€™]

    2. query function

      • ๋ชจ๋‹ฌ์ฐฝ์„ ์—ด์ง€ ์•Š์•˜์„ ๋•Œ๋Š” null์„ returnํ•˜๊ณ  ๊ทธ ์™ธ์—๋Š” ์„œ๋ฒ„์— ์š”์ฒญ
    3. query options

      • enabled : ํŠน์ • ์ƒํ™ฉ์—๋งŒ query function์ด ์‹คํ–‰๋˜๋„๋ก ์„ค์ •(ํ›„์— ์ถ”๊ฐ€ ์„ค๋ช…)
      • staleTime : ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ธ์ œ ์ƒํ•˜๊ฒŒ(?) ๋˜๋Š”์ง€ ๊ฒฐ์ •, 5๋ถ„์œผ๋กœ ์„ค์ •
      • cacheTime : ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ 30๋ถ„ ๋™์•ˆ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜๋Š” ๊ฒƒ์œผ๋กœ ์„ค์ •
      • onSuccess : ์š”์ฒญ์— ์„ฑ๊ณต ์‹œ ๋ฐ์ดํ„ฐ๋กœ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ทธ๋ ค์คŒ
  • enabled

    const enabled = () => {
    	// 1.
      if (!isSecondLevel) {
        return false;
      }
    	// 2.
      if (!firstLevelInfo || !prevFirstLevelInfo) {
        return true;
      }
    	// 3.
      if (prevFirstLevelInfo[0] === firstLevelInfo[0]) {
        return false;
      }
    	// 4.
      return true;
    };
    1. 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์ด ์—ด๋ฆฌ์ง€ ์•Š์•˜์„ ๋•Œ๋Š” ์š”์ฒญํ•˜์ง€ ์•Š์Œ

    2. ์ด์ „์— 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์ด ํ•œ ๋ฒˆ๋„ ์—ด๋ฆฌ์ง€ ์•Š์•˜์„ ๋•Œ, ์ฆ‰ ์ฒ˜์Œ์œผ๋กœ 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์ด ์—ด๋ฆด ๋•Œ๋Š” ์š”์ฒญ

    3. ์ด์ „์— ์„ ํƒ๋๋˜ ์ง€์—ญ๊ณผ ๊ฐ™์€ ์ง€์—ญ์„ ์„ ํƒํ–ˆ์„ ๋•Œ๋Š” ์š”์ฒญํ•˜์ง€ ์•Š์Œ

      ex) ์„œ์šธ์—ญ ๋งˆ์ปค๋ฅผ ๋ˆ„๋ฅด๊ณ , 2๋‹จ๊ณ„ ๋ชจ๋‹ฌ์ฐฝ์„ ๋‹ซ๊ณ , ๋‹ค์‹œ ์„œ์šธ์—ญ ๋งˆ์ปค๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ

    4. ์ด์ „์— ์„ ํƒ๋๋˜ ์ง€์—ญ๊ณผ ๋‹ค๋ฅธ ์ง€์—ญ์„ ์„ ํƒํ–ˆ์„ ๋•Œ๋Š” ์š”์ฒญ

      ex) ์„œ์šธ์—ญ ๋งˆ์ปค๋ฅผ ๋ˆ„๋ฅด๊ณ , ๋ช…๋™ ๋งˆ์ปค๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ

  • ๊ทธ๋ž˜ํ”„ ์ ์šฉ

    // components/InfoDetailModal/InfoDetailModal.tsx
    const success = (data: graphInfoResponseTypes | null) => {
      if (data) {
        setGraphInfo(data.data);
        setPrevFirstLevelInfo(firstLevelInfo);
      }
    };
    
    const setPastInformation = async (): Promise<undefined> => {
      if (!firstLevelInfo) {
        return;
      }
    
      if (graphInfoResponse) {
        setGraphInfo(graphInfoResponse.data);
        setPrevFirstLevelInfo(firstLevelInfo);
      }
    
      // eslint-disable-next-line no-useless-return
      return;
    };
    
    useEffect(() => {
      if (!isSecondLevel) {
        ...
      }
    
      setPastInformation();
    }, [isSecondLevel]);
    • ์š”์ฒญ์„ ์„ฑ๊ณตํ–ˆ์„ ๋• success ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ณ , ์ด๋ฏธ ์บ์‹ฑ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋• setPastInformation ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค!

๊ทธ๋ž˜ํ”„ ์บ์‹ฑ ๊ฒฐ๊ณผ

์บ์‹ฑ ์ „

Day21 - ์บ์‹ฑ ์ „

์บ์‹ฑ ํ›„

Day21 - ์บ์‹ฑ ํ›„

  • ์„œ์šธ์—ญ์„ ๋‹ค์‹œ ์—ด์—ˆ์„ ๋•Œ ๊ทธ๋ž˜ํ”„ ๊ทธ๋ ค์ง€๋Š” ์†๋„๊ฐ€ ๋งค์šฐ ๋นจ๋ผ์ง

Day21 - ์บ์‹ฑ ํ›„4

  • ํ•œ ๋ฒˆ ์š”์ฒญํ•œ ๋ฐ์ดํ„ฐ(์„œ์šธ์—ญ)๋Š” ๋‹ค์‹œ ์š”์ฒญ๋˜์ง€ ์•Š์Œ

๐Ÿšง Trouble Shooting

์กฐ๊ฑด๋ฌธ๊ณผ react-query

  • ๋ฌธ์ œ์ 

    • firstLevelInfo ๋Š” null์ผ ์ˆ˜๋„ ์žˆ์Œ

    • useQuery๋ฅผ ์ด์šฉํ•˜๋ ค๋ฉด axios ์ฃผ์†Œ์— firstLevelInfo[0] (์ง€์—ญ ์ด๋ฆ„)๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ

    • ํ•˜์ง€๋งŒ ์กฐ๊ฑด๋ฌธ์œผ๋กœ firstLevelInfo ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ useQuery๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋”๋‹ˆ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ ๋ฐœ์ƒ

      Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
      1. You might have mismatching versions of React and the renderer (such as React DOM)
      2. You might be breaking the Rules of Hooks
      3. You might have more than one copy of React in the same app
      
  • ํ•ด๊ฒฐ

    const { data: graphInfoResponse } = useQuery(
        ...,
        async () => {
          if (!firstLevelInfo) {
            return null;
          }
          const result = await apis.getPastInformation(firstLevelInfo[0]);
    
          return result;
        },
        {
          ...
        }
      );
    • query Function์—์„œ firstLevelInfo ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์š”์ฒญํ•˜๋„๋ก ๋ณ€๊ฒฝ
    • query Funciton์— ๋Œ€ํ•ด ์ž˜ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๊ณ  ์ ‘๊ทผํ•œ ๊ฒƒ์ด ์›์ธ
โš ๏ธ **GitHub.com Fallback** โš ๏ธ