본문 바로가기
학습끝!/원티드 프리온보딩 프론트엔드 인턴십

3주차

by sweesweet 2023. 1. 16.

 

하단의 추가 구현+ 오류 수정한 개인 레포 링크 


3주차 회고

 

 

이번 과제는 아래 사이트의 검색 기능을 클론하는 과제였다.

clinicaltrialskorea.com

 

한국임상정보

쉽고 간단하게 국내 모든 임상시험 참여하기. 간암, 대장암, 부인암, 유방암, 위암, 전립선암, 폐암 임상시험을 알아보세요.

clinicaltrialskorea.com

크게 분류하면 아래와 같은 task가 주어졌다. 

1. 질환명 검색시 API 호출 통해서 검색어 추천 기능 구현
2. API 호출 최적화 (로컬 캐싱)
3. 키보드만으로 추천 검색어들로 이동 가능하도록 구현

 

우리 조는 best practice를 위해 하루에 한번씩 회의를 진행했는데, 컴포넌트/ 검색시 api 호출 구현을 구현할 때 나도 구현을  했지만 다른 분이 선택이 됐기 때문에 내가 구현한 모습과 사뭇 다를 수 있다!

 

1. 질환명 검색시 API 호출 통해서 검색어 추천 기능 구현

가장 주가 되는 부분은 API 호출 최적화라고 생각했기 때문에 처음엔 컴포넌트/검색시 api호출 구현을 끝내고 수정해 나가는 방향으로 하기로 했다.

 

2.API 호출 최적화 (로컬 캐싱)

 

최적화를 하기 위해 디바운싱과 캐싱 구현을 했는데 나 같은 경우는 둘다 훅으로 구현했다.

 

디바운스인 경우, 훅으로 구현 했지만, 계속 처음 입력 시 버벅거리는걸 볼 수가 있었는데,

이렇게 if문을 걸어주게 될 경우 버벅거리는 걸 없앨 수 있다.

  useEffect(() => {
    if (debounce) {
// 어쩌구저쩌구
    }
  }, [debounce]);

 캐싱은 생각이 좀 많았었는데, 처음에는 리코일로 캐싱을 구현하는 방법이었다.

객체로 프로퍼티 키 값은 서치쿼리 부분, 값은 해당하는 데이터 이런식으로 해서 state를 관리하는 방식으로 구현했다.

오래된 데이터를 계속 갖고 있을 수 있으니, 해당 프로퍼티 값이 없을 때 api호출을 하면서 setTImeout 으로 정해진 시간 후에 값이 삭제되도록 했다.

//useCache 훅

const [cache, setCache] = useRecoilState(sickCacheState(url));
//앞부분이 잘려있는데 아래 캐시스토리지처럼 캐시값이있으면 캐시값을 내뱉는 부분이있었음
 axios
  .get(`http://localhost:4000/sick?q=${url}`)
  .then(({ data }) => data)
  .then((data: SickTypes[]) => {
    setCache({ mode: 'set', data });
    setTimeout(() => {
      setCache({ mode: 'delete', url });
    }, 1000 * 60 * 60);
  });
  
   return cacheResult;
 
 
 
 //store
 export interface Cachemode {
   [index: string]: CacheSickTypes | string;
   mode: string;
   data: CacheSickTypes | string;
 }
 export interface CacheData {
   [index: string]: SickTypes[] | string;
   mode: string;
   data: SickTypes[] | string;
 }
 export const sickCacheState = selectorFamily<any | SickTypes[] | ((currVal: SickTypes[]) => SickTypes[]), string>({
   key: 'sickCacheState',
   get:
     (param) =>
     ({ get }) => {
       const data = get(sickValue);
       return data[param];
     },
   set:
     (param) =>
     ({ set, get }, value) => {
       const prev = { ...get(sickValue) };
       const { mode, data } = value;
       if (mode === 'set') {
         set(sickValue, { ...prev, [param]: data });
       } else if (mode === 'delete') {
         delete prev[data];
         set(sickValue, prev);
       }
     },
 });

근데 내가 리코일 사용을 잘 못하는건지뭔지...mode에따라 다르게 구현하고 싶었는데, return값이 이게 아니라구 계속 그래서 우선 any로 뒀다. selectorFamily 쓰니까 타입지정하는게 좀 어려웠다...

 

이걸 다 구현하고 다른 분들과 진행 상황을 얘기하기 위해 회의에 들어갔는데, 아무런 추가 구현 없이 store 안에서 비동기처리를 했을 뿐인데 캐싱기능이 저절로 되는걸 볼 수 있었다...!

알고보니 리코일에 그런 기능이 있었다...

https://recoiljs.org/docs/guides/asynchronous-data-queries/

 

Asynchronous Data Queries | Recoil

Recoil provides a way to map state and derived state to React components via a data-flow graph. What's really powerful is that the functions in the graph can also be asynchronous. This makes it easy to use asynchronous functions in synchronous React compon

recoiljs.org

나중에 혼자 써볼 때 한번 써봐야겠다 싶었음

 

원래는 기능별로 계속 회의하고 best Prac을 뽑아서 했었는데, 캐싱 부분이 다들 조금 부족해서 기능을 나누지 않고 키보드 이벤트까지 하기로 했다.

 

원래는 리코일로 한거를 내려했는데, 캐시 스토리지라는 것도 검색하다가 알게 된 겸, 다 갈아 엎고 캐시스토리지로 구현해보기로 했다. (새로고침해도 캐시값을 계속 갖고있을수도 있어서...!)

캐시 스토리지는 조금 이상(?)했는데 다 Promise 이기때문에 다 await처리 해줘야하고, 캐시스토리지에 넣으려면 Response객체만 value값으로 넣을 수 있다. 그리고 json()이거 하고 Response객체를 캐시스토리지에 넣으려했는데, 한번 읽힌 ReadableStream은 다시 읽힐 수 없대서 방법을 찾다가 .clone()이라는 메서드가 있길래 사용해 처리할 수 있었다.

 

https://developer.mozilla.org/en-US/docs/Web/API/Response/clone

 

Response.clone() - Web APIs | MDN

The clone() method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.

developer.mozilla.org

 

import { SickTypes } from '../types/sick.type';
import { useState, useEffect } from 'react';

const useCache = (keyword: string) => {
  const [cacheResult, setCacheResult] = useState<SickTypes[] | null>(null);
  useEffect(() => {
    if (keyword) {
      handleCache(keyword);
    }
  }, [keyword]);
  if (!keyword) return null;

  const handleCache = async (url: string) => {
    const encode = `sick?q=${encodeURI(url)}`;
    const cacheStorage = await caches.open('searchQuery');
    const cache = await cacheStorage.match(encode);
    if (cache) {
      setCacheResult(await cache.json());
      return;
    }
    await fetch(`http://localhost:4000/${encode}`)
      .then((data) => {
        const clone = data.clone();
        cacheStorage.put(encode, clone);
        setTimeout(() => {
          cacheStorage.delete(encode);
        }, 60 * 1000);
        return data.json();
      })
      .then((data) => setCacheResult(data))
      .catch((err) => console.log(err));
  };
  return cacheResult;
};

export default useCache;

https://developer.mozilla.org/ko/docs/Web/API/Cache

 

Cache - Web API | MDN

Cache 인터페이스는 ServiceWorker 의 생명주기의 일부로 캐시 된 Request와 Response를 나타냅니다.

developer.mozilla.org

여기서 또 문제점이 발생했는데, 새로고침하게 되면 아직 해당 시간이 지나지 않는 데이터는 삭제가 안된다는 점이였다.

어떻게 헤더로 어떻게 할 수 없을까? 싶어서 response Header를 살펴보았다

expires 는 -1이기 때문에 던지고

Date 가 있어서 그걸 좀 가져오고 싶었는데, 아무리 해도 null값이 찍히길래 서버역할을 해주는 레포지토리 가니까 설정이 안 되어 있는걸 확인할 수 있었다.

아니 브라우저에선 보이는데, 서버쪽에서 설정안한 값은 안나오는건가싶고!!(거기에 적혀있는데 헤더설정은 다 나오는것 봐서는...)

// 캐시가 있을 떄 오래된 데이터를 삭제하기 위해 Date를 비교하려 했던 방식
const date = new Date(response.headers.get('Date')); //<- 헤더에있는 시간 데이터를 가져오려 했음

쨋든 어쩔 수 없이 그냥 setTimeout으로 만족하기로 했다.

 

 

3. 키보드만으로 추천 검색어들로 이동 가능하도록 구현

키보드만으로 구현하는거 사실 막 그렇게 어렵지는 않다구 생각이 들긴 했었다. (오만이었다) 

그냥 키마다 상태 변경해주면 되는거 아닌가? 싶은데 아니였다...

아래 화살표 키로 내리게 될 경우, 위치가 변경되면서 focus된 검색 값은 계속 가운데에 있어야한다고 생각했다.그래서 translate를 사용해서 위치를 계속 변경해줬는데, 그걸 구현하게 되면 overflow-y:scroll을 줬을 때 이전의 영역이 비어있기도 하고, translate로 위치를 옮긴 탓에 맨위로 올려도 이전과 같은 모습이 나타나지않았담... 그래서 그 부분은 그냥 5개로 잘라서 받는 걸로 처리했다.

 

그리고 os차이인건지, 윈도우와 맥에서 이벤트가 다르게 발생되었는데(브라우저 같은 chrome이었음),처음 key이벤트가 실행될 떄 윈도우는 그냥 멀쩡한데 맥은 두칸씩 내려가는 버그?가 발생했다.맥 유저이신 분이 찾아보니 입력창에서 한글을 입력할 때 글자가 조합 중인지 조합이 끝났는지 확인할 수 없는 경우 발생하는 문제 라고 했다. 그래서

  if (e.nativeEvent.isComposing) return; // isComposing이 true일 경우 바로 return

이런식으로 처리를 해줘야 한다고 한다!

 

배포된 페이지(개인 레포기때문에 함께했던 부분이랑 조금 다르다)

- 근데 서버를 local로 돌려야 하기 때문에 검색하면 네트워크오류난다.

https://wanted-search-dropdown-1pdk3habq-sweesweett.vercel.app/

 

원티드 프리온보딩 인턴십 | 3주차

 

wanted-search-dropdown-1pdk3habq-sweesweett.vercel.app


**오류 수정

fix:빈 배열일 시 오류 수정, 통신 실패에도 값이 저장되던 오류 수정

- 검색 후 빈배열일 때 계속 undefined 오류나는 부분 수정

- 통신실패에도 값이 저장되던 오류 수정

이유는 useCache 부분에서 fetch는 ok를 체크해줘야한다는 점을 잊고있었다.....axios만 쓰다보니..머쓱

그부분 수정~

 await fetch(`http://localhost:4000/${encode}`)
      .then((data) => {
        if (data.ok) {
          const clone = data.clone();
          cacheStorage.put(encode, clone);
          setTimeout(() => {
            cacheStorage.delete(encode);
          }, 60 * 1000);
          return data.json();
        }
        throw data.status;
      })
      .then((data) => setCacheResult(data))
      .catch((err) => console.log(`err :${err}`));

 

**추가 구현

목록이 있을 때 상,하 화살표 키보드 누를 시  스크롤 구현

위에서 translate로 접근했던 부분을 index가 변화할 때 scrollTop의 값을 변경해주는 것으로 접근해 구현에 성공했다.

ul을 감싸고있는 wrapper에 useRef를 사용했다

  useEffect(() => {
    refList.current!.scrollTop = searchIdx <= 2 ? 0 : (searchIdx - 2) * 60;
  }, [searchIdx]);

 

 

 

코드 보러가기! => 개인 레포 링크 

'학습끝! > 원티드 프리온보딩 프론트엔드 인턴십' 카테고리의 다른 글

4주차이자 마지막  (1) 2023.01.24
2주차-2  (0) 2023.01.07
2주차-1  (0) 2023.01.04
1주차-2  (1) 2022.12.23
1주차-1  (0) 2022.12.21