[카우치코딩] 5주차 진행 회고

jun gwon·2022년 4월 25일
0

카우치코딩

목록 보기
5/6

5주차 진행

  • 검색 리스트를 받아오고, 무한스크롤을 통해 구현하였다.
  • 상세 페이지 레이아웃 및 데이터처리.
  • 카카오맵을 사용한 위치 지도를 구현하였다.
  • 프록시를 사용하여 실제 DB와의 데이터를 연동하였다.
  • firebase의 oAuth를 수정하였다.

검식 리스트 및 무한스크롤 구현

레이아웃 렌더부분 -

각각의 아이템에 사용된 레이아웃은 antd의 card를 사용하였다. 사용된 card는 약간의 커스텀을 하였는데, 기본 제공된 모델에서는 title의 txt가 길경우 자체적으로 overflowhidden의 css가 적용되야하는데 약간의 커스텀을 거치니 정상적으로 작동이 되지 않았다.
아마 title의 바로 옆에 인라인 요소를 추가로 사용한게 문제였던것 같다. 때문에 antd의 title부분의 width를 강제적으로 90%로 조절하였고 해결하였다.

무한스크롤 부분 -

리액트의 무한스크롤의 첫번째 방법으로 스크롤의 높이와 top,그리고 사용자의 화면의 높이를 실시간으로 비교하여, 일정 조건에서 실행되게 하는 이벤트를 addEventListener 를 통하여 등록하는 방법이 있었다.
하지만 이 방법은 evenet가 스크롤이 움직일때마다 계속 실행되기 때문에 좋은 방법은 아니라고 생각하였다.

두번째 방법은 js에서 제공하는 IntersectionObserver를 사용하는것인데, 이것은 정해둔 타겟의 정해둔 부분만큼 클라이언트의 뷰포인트와 교차하면 정해둔 함수가 실행되는 식이다. 실 구현은 IntersectionObserver를 사용하여 구현하였다.


  let index = useRef(false).current;
  let throttle = useRef(false).current;

  const setIndex = useCallback(() => {
    index++;
  }, [index]);

  const setThrottle = useCallback(() => {
    throttle = !throttle;
  }, []);

  useEffect(() => {
       if (throttle) return;
     // IntersectionObserver이 실행될때의 옵션을 지정한다. threshold는 타겟이 얼마만큼 노출되는지에 따라서 실행될것인지를 지정한다.
    const options = {
      threshold: 0,
    };
    // 이벤트가 실행될때 작동하는 함수, entries는 타겟을 값으로 갖고잇으며, 여러가지 기능을 제공한다. 
    const handleIntersection = (entries, observer) => {
      entries.forEach((entry) => {
     		//옵션에서 지정한 만큼 타겟과 뷰포인트가 교차 하지 않을때는 실행되지 않는다.
        if (!entry.isIntersecting) {
          return;
        }
            // 교차지점에 도달하면 계속 실행이 되기때문에, 쓰로틀을 사용하였다.
        if (index < totalPages) {
          setThrottle();
          setTimeout(() => {
            onSearch(index);
            setIndex();
            setThrottle();
          }, 1000);
        }
      });
    };    

//위에서 만들 콜백함수와 옵션을 가지고 IntersectionObserver를 생성하고
    const io = new IntersectionObserver(handleIntersection, options);

    if (target.current) {
      //실행한다
      io.observe(target.current);
    }
    return () => {
      //언마운트시 IntersectionObserver를 제거한다
      io && io.disconnect();
    };
    };
  }, [index, totalPages, onSearch, throttle, setThrottle, setIndex]);

  <Spacer ref={target} />

useEffect 내부에서 리액트의 state를 사용하는경우 의존성을 갖게되며, 값이 변동될때 useEffect가 다시 실행되게 되어, 한번만 실행되어야 하는데 두번 실행되게 된다. 때문에
Index와 Throttle은 리액트의 state를 사용하지 않고, useRef를 사용하여 구현하였다.

  useEffect(() => {
    try {
      if (places.length !== 0) {
        setData((data) => [...data, ...places]);
      }
    } catch (e) {
      setData([]);
    }
    return () => {
      if (places.length === 0) setData([]);
    };
  }, [places]);

받아온 데이터는 컴포넌트내의 data State를 만들어서 데이터의 길이가 0이 아닐경우 스프레드 연산자를 사용하여 그대로 붙여서 렌더링 되게 하였다.

상세 페이지,카카오맵 구현

프로젝트를 진행하면서 가장 스트레스를 받았던건 css와 디자인 구성이였던거 같은데, 그렇다고 딱히 적을만한건 없는것 같다.

이미지와 타이틀 부분은 직접 구현하였고, 정보를 나타내는 Tabs부분은 antd의 tabs를 사용하였다.


(상세 페이지의 정보를 나타내는 탭)

탭의 첫번째 메뉴는 어떤 아이템이라도 필수적으로 존재하는 주소 정보와, 그걸 토대로 카카오맵 API를 사용하였고, 두번째 메뉴는 아이템마다 있을수도 없을수도있는 부가적인 정보가 들어가게된다. 때문에 부가정보가 아예 없을경우에는 나타나지 않게 하였다.

  const hasContent = (place)=> {
    const content = ['contact', 'cost', 'info', 'link_url'];
    let check = false;
    content.forEach((item) => {
      if (!check && place[item] !== null) {
        check = true;
      }
    });
    return check;
  }
    {hasContent(place) ? ....

(content에 해당하는 값이 받아온 데이터에 없을경우 탭 메뉴가 보이지 않도록 설정하였다.)

정보의 텍스트량은 아이템마다 다른데, 너무 길거나 줄바꿈이 2번이상 있을경우는 축약되어 보이게 하였다.

  const [preViewChecked, setPreViewChecked] = useState({});

  const onPreviewClick = (cat) => {
    setPreViewChecked({
      ...preViewChecked,
      [cat]: !preViewChecked[cat],
    });
  };

  const preview = (text, cat) => {
    const result = [];
    let preText = text;
    if (text.length > 70 || text.split('\n').length > 2) {
      if (!preViewChecked[cat]) {
        preText = text.replaceAll('\n', ' ').substr(0, 55).concat('... ');
      }
      result.push(preText);
      result.push(
        <span
          className="textToggl"
          onClick={() => onPreviewClick(cat)}
          key={cat + '_toggle'}
        >
          {!preViewChecked[cat] ? <CaretDownOutlined /> : <CaretUpOutlined />}
        </span>,
      );
    } else {
      result.push(preText);
    }
    return result;
  };
....
<pre className="info" key={'cost_ifno'}>
  {preview(place.cost, 'cost')}
    </pre>

(조건에 해당되는 텍스트일경우, 축약될 때 ...을 붙이게 하였고, 이벤트를 등록한 아이콘을 옆에 붙여주었다.)

//카카오맵 api를 사용하기 위해선 index.html에 cdn을 등록해주어야 한다
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=..."></script>

//카카오맵에서 제공해주는 API 
const { kakao } = window;

export const KakaoMapScript = (address) => {
  const container = document.getElementById('kakaoMap');
  const options = {
    center: new kakao.maps.LatLng(33.450701, 126.570667),
    level: 3,
  };

  const map = new kakao.maps.Map(container, options);

  const geocoder = new kakao.maps.services.Geocoder();
  geocoder.addressSearch(address, function (result, status) {
    if (status === kakao.maps.services.Status.OK) {
      const coords = new kakao.maps.LatLng(result[0].y, result[0].x);
      const marker = new kakao.maps.Marker({
        map: map,
        position: coords,
      });
      map.setCenter(coords);
    }
  });

  container.style.width = '300px';
  container.style.height = '300px';
  map.relayout();
};
// 필요한 컴포넌트에서 useEffect를 사용하여 불러온다.
  useEffect(() => {
    KakaoMapScript(place.address);
  }, [place.address]);
//   const container = document.getElementById('kakaoMap'); 에서 사용할 div를 만들어줘야 한다.
<div id="kakaoMap" />

카카오맵의 경우는 antd의 tabs와 사용할때 약간의 버그가 있는것 같은데, 처음에 display:none으로 되어 있을때는 제대로 값을 못 받아오는것 같았다. 이미지가 깨져보이고 정상적으로 작동하지 않았다. 원래 계획은 카카오맵을 두번째 탭에서 보여주게 하려는거였는데.. 끝내 해결하지 그냥 디폴트 정보로 나타내는걸로 해결하였다.

연동과 fireBase oAuth

우리팀은 DB팀에서 헤로쿠를 사용하여 서비스 하였는데, 처음에는 DB에 저장되있는걸 어떻게 사용하는건지 몰라 헤맸다.
결과적으로는 package.json의 proxy 설정을 헤로쿠와 동일하게 해서 해결하였다.

로그인부터 연동을 시작하엿는데, 이전에 구현한 oAuth가 제대로 작동하지 않았다. 백과 프론트 모두 fireBase를 사용하고 잇는데. 결과적으로는 몇몇 문제를 못 찾아서 그런거였다.

첫째로는 백과 프론트 모두 같은 fireBase프로젝트를 사용하여야 하는데, 따로 사용하였기 때문이고. 둘째로는 백에서는 fireBase의 idToken값을 받아서 검증을 하는데, 내가 작성한 프론트에서는 GoogleAuthProvider의 idToken값을 전달하였기 때문이다.
fireBase의 경우 서비스 하는 다른 업체들의 provider값을 취급하기도 하지만, 자체적으로 이것들을 모두 감싼 fireBase의 idToken값이 있다는것을 몰랐기 때문에 일어난 헤프닝이였다.

때문에 이전 oAuth코드를 아래와 같이 변경하였다.

export const signInGoogle = () => {
  const provider = new GoogleAuthProvider();
  return signInWithPopup(auth, provider)
    .then((result) => {
      return result.user.getIdToken();
    })
    .catch((error) => {
      console.log(error.message);
      throw new Error(error.code);
    });
};

(받아온 result의 user에서 fireBase의 id토큰으로 접근이 가능하다)

마무리

무한스크롤 부분은 어떻게 해야 할지 처음엔 감을 잘 못잡았는데, 검색해서 나온것들이 생각보다 실행자체는 잘 되서 좋았다.. IntersectionObserver의 경우는 많은 옵션이 있는것 같은데, 기회가 되면 좀 더 자세히 알아보는게 좋을것 같다. 지금은 너무 실행 위주의 공부였던것 같다.
useEffect에서 IntersectionObserver를 실행하였는데, 그 안에서 리액트의 state를 사용하여 에러가 나는것을 어떻게 해결해야하는지 생각을 못하였다. 이부분은 멘토님께 여쭤봤는데, 따로 useRef를 사용하면 된다 하여서 그 말을 듣고 해결하였다. 생각해보면 당연한거 같다.

되돌아보면 문제가 많아 헤매기도 했지만 덕분에 알게된 지식도 많았던것 같다.. 무한 스크롤 두번실행문제, oAuth연동문제, 카카오맵 display:none으로? 렌더링 될시 이후 작동안됨 문제 등..

0개의 댓글