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를 사용하면서도 많이 배운 것 같다!