팀프로젝트 이후 진행한 것 + 리스트 클릭 효과 및 스크롤 이동

zizi·2023년 1월 5일
0

팀프로젝트

목록 보기
7/7
post-thumbnail

202212

12월의 마지막 주, 마지막 금요일 30일까지 팀원들과 함께 완성도를 좀 더 높이기 위해 성능 개선 및 리팩토링, 자잘한 기능 추가를 했다.

진행한 일

  • 반응형 구현 완료(태블릿 / 모바일)
  • 메인 브랜치 코드 정리 (import 및 폴더) => eslint-plugin-import sort 이용
    react → components → 알파벳순 → ant design → styles
  • README 작성 및 시연 동영상 촬영
  • 접근성 향상하려고 했으나 light house에서 98점이 나와서 패스
  • 버튼 / 필터박스 / 맵 등 리팩토링
  • 내 위치 마커 중복 생성 오류 해결
  • 지도의 마커 혹은 리스트 클릭시 해당 편의점 리스트에 클릭 효과 주기

공부한 점

* 지도의 마커 혹은 리스트 클릭시 해당 편의점 리스트에 클릭 효과 및 리스트뷰의 스크롤이 해당 리스트 위치로 이동

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

  • 현재 잘 작동하는 리스트 효과

* 처음 시도 -> 실패
1안) reducer 사용
문제점: convslice에 setTargetStore reducer를 만들고 리스트 클릭 OR 마커 클릭 시에 clickTargetStoreId를 store에 저장하게 하려고 했는데, 마커 이벤트는 ts 파일이라 사용 안됨 (모든 훅은 함수형 컴포넌트 안에서 사용해야한다고 함)

⇒ 오버레이 컴포넌트화 후 useEffect로 clickTargetStoreId를 저장하면 되지않을까?

2안) useState 사용 - 리스트 클릭시만 active(현재)

일단은 리스트 클릭 시만 state로 변경하게 해놓음
문제점: 마커 클릭시 리스트와 active 상태 리스트와 안 맞는 오류

* 이후 진행한 것 -> 성공

오버레이로 진행X -> 마커를 map context로 넣음

마커 클릭 시엔 클릭된 해당 편의점의 id OR 리스트 클릭 시엔 클릭된 해당 편의점의 id
두 경우를 하나로 모아줄 useState가 필요했음 (targetStoreId)

1. 마커를 map context에 함께 넣고, 마커 클릭 이벤트에 useState로 클릭된 마커의 정보를 넣음

Mapcontext.tsx

const MapProvider = ({ children }: { children: React.ReactNode }) => {
...생략...
  const [selectedMarker, setSelectedMarker] =
    useState<kakao.maps.Marker | null>(null)
    
    ...생략...
  const setMarkers = useCallback(
    ...생략...

      // 마커 클릭이벤트
      kakao.maps.event.addListener(newMarker, 'click', function () {
        dispatch(
          setClickedStore({
            placeName: data.place_name,
            storeId: data.id,
            address: data.address_name,
            phoneNumber: data.phone,
            reviewCount: data.reviewCount,
            starCount: data.starCount,
          })
        )
        KakaoService.overlay.setPosition(markerCenter)
        KakaoService.overlay.setContent('<div id="kakao-overlay"></div>')
        KakaoService.overlay.setMap(map)
        newMarker.setTitle(data.id)
        //선택된 마커 setSelectedMarker state에 넣어주기
        setSelectedMarker(newMarker)
        map.panTo(markerCenter)
      })
      
        const value = {
    mapApi: map,
    markers: newMarkers,
    selectedMarker,
    setMapApi,
    setMarkers,
    deleteMarkers,
    addMarkers,
    setSelectedMarker,
  }

  return <MapContext.Provider value={value}>{children}</MapContext.Provider>
}

export default MapProvider
    

2. 선택된 마커 context로 불러와서 해당 스토어 id를 state(setTargetStoreId)에 넣기 -> state, setState List 컴포넌트에 props로 전달 -> 리스트에 useRef 적용 -> moveToTarget 함수 생성 -> 전달한 targetStoreId state가 변경될 때마다 useEffect로 moveToTarget 함수 실행

ListBox.tsx

const ListBox = () => {
  const [targetStoreId, setTargetStoreId] = useState<string>('')
  const { selectedMarker } = useContext(MapContext)
  const listRef = useRef<HTMLLIElement[] | null[]>([])
  
  ...생략...
    useEffect(() => {
    // 선택된 마커의 해당 스토어 id를 setTargetStoreId에 넣기
    if (selectedMarker) setTargetStoreId(selectedMarker?.getTitle())
  }, [selectedMarker])
  
  // scrollIntoView 메소드를 사용하여 해당 id의 리스트로 이동하는 함수(부드럽게, 해당 리스트를 가운데로 두도록 설정함)
    const moveToTarget = useCallback((storeId: string) => {
    listRef.current[Number(storeId)]?.scrollIntoView({
      behavior: 'smooth',
      block: 'center',
    })
  }, [])
  
  // 전달한 targetStoreId state가 변경될 때마다 useEffect로 moveToTarget 함수 실행
    useEffect(() => {
    moveToTarget(targetStoreId)
  }, [targetStoreId, moveToTarget])
  
  ...생략...
    return (
    <ListWrapper>
    ...생략...
      <ResultBox>
		...생략...
          convList.map((store) => (
            <li
              key={store.id}
              ref={(el) => (listRef.current[Number(store.id)] = el)}
            >
              <List
                starCount={store.starCount}
                keywords={store.keywordList}
                reviewCount={store.reviewCount}
                placeName={store.place_name}
                lat={Number(store.y)}
                lng={Number(store.x)}
                storeId={store.id}
                address={store.address_name}
                phoneNumber={store.phone}
                targetStoreId={targetStoreId}
                setTargetStoreId={setTargetStoreId}
              />
            </li>
          ))
        )}
      </ResultBox>
    </ListWrapper>
  )
}

export default ListBox
  

*List 컴포넌트에 ref를 직접 사용하지 못한 이유
직접 DOM을 조작하려면 useRef의 초깃값이 null로 들어가야함. 그런데 List 컴포넌트는 useRef를 배열로 받아야하기 때문에 초깃값이 []로 들어가야함. => 문제는 둘 모두를 초깃값으로 쓸 수는 없다는 점!
forward Ref 등 여러가지를 시도해봤지만 ref에 빨간줄이 뜨는 오류가 해결되지 않아서 마지막 방법으로 List 컴포넌트를 한번 li로 감싸주고 그 li에 ref를 사용했다.

https://velog.io/@apparatus1/Typescript-useRef-읽기-전용-에러
https://darrengwon.tistory.com/865

3. 리스트 클릭 시 setTargetStoreId state에 해당 스토어 id 넣기 -> 선택됐을 때만 className에 active 클래스를 넣어 스타일을 넣어줌

List.tsx

const List: React.FC<ListProps> = ({
  placeName,
  lat,
  lng,
  storeId,
  starCount,
  reviewCount,
  keywords,
  address,
  phoneNumber,
  targetStoreId,
  setTargetStoreId,
}) => {
...생략...
  const listClickHandler = () => {
    if (mapApi) {
      KakaoService.overlay.setPosition(center)
      KakaoService.overlay.setContent('<div id="kakao-overlay"></div>')
      KakaoService.overlay.setMap(mapApi)
      mapApi.panTo(center)
	// setTargetStoreId에 클릭된 리스트 편의점의 id 넣기
      setTargetStoreId(storeId)
      dispatch(
        setClickedStore({
          placeName,
          storeId,
          address,
          phoneNumber,
          reviewCount,
          starCount,
        })
      )
    }
  }
  
    return (
      <ConBox
      onClick={() => {
        listClickHandler()
      }}
      // 선택됐을때만 className에 active 클래스를 넣어 스타일을 넣어줌 
      className={targetStoreId === storeId ? 'active' : ''}
    >
    ...생략...

느낀 점

일주일도 안 됐는데 벌써 기억이 휘발되고 있어서 더 까먹기전에 얼른 적어야겠다.
갑자기 나타난 자잘한 오류들이나 내 위치 중복 오류 마커도 해결하느라 애를 먹었지만, 편의점 리스트에 클릭 효과 구현은 더 고민을 많이 했어야 했다.
처음에는 마커가 함수형 컴포넌트 안에 있지 않았기때문에 훅을 사용할 수 없어서 1차로 실패하고 좀 어렵지 않을까 못할 수도 있겠다라는 생각을 했는데 팀원이 마커를 컨텍스트로 옮기고 나는 나머지 부분들을 해결해서 결국엔 구현해낼 수 있어서 뿌듯했다. 또 아무래도 타입스크립트로 진행하다보니 타입에러 때문에 문제가 되는 경우가 많은데 이번 useRef를 사용하면서도 많이 배운 것 같다!

0개의 댓글