리팩토링 / 부산에 가면 7. 타입스크립트 마이그레이션

이창훈·2023년 2월 8일
0

부산에가면

목록 보기
11/11
post-thumbnail

1) 🔁 타입스크립트 마이그레이션

// React에서 typescript를 사용할 수 있도록 타입스크립트 전용 패키지를 설치 한다.
 npm install --save typescript @types/node @types/react @types/react-dom @types/jest

//tsconfig.json을 생성한다.
npx tsc --init

//내가 사용한 스타일드 컴포넌트를 제외한 다른 라이브러리들은 타입스크립트 전용 패키지가 내장 되어 있었다.
npm i @types/styled-components

이제 js 혹은 jsx확장자를 ts, tsx확장자로 바꿔준다.

1-1) Redux Toolkit에 타입스크립트 적용하기

Redux Toolkit TypeScript Quick Start

//store.tsx
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
//기존의 useDispatch와 useSelector를 아래와 같이 변경하여 사용할 수 도 있지만
const dispatch = useDispatch<AppDispatch>();
const state = useSelector((state : RootState) => state.restaurantReducer)
// 아래와 같이 hook을 만들어서
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from "../store";

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

// 매번 타입을 설정해주지 않고 쓸 수 있다. 
const dispatch = useAppDispatch();
const state = useAppSelector((state) => state.restaurantReducer)

1-2) styled-components에 타입스크립트 적용하기

컴포넌트의 props를 넘겨 줄 경우 props의 타입을 적어주어야한다.

interface MapSpanProps {
  currentVillage : string;
  name : string;
}
const MapSpan =styled.span<MapSpanProps>`
  background-color :  ${(props)=>props.theme.theme==='light' ? props.currentVillage === props.name ? 'darkslategrey' : 'white'  : props.currentVillage === props.name ? 'darkslategrey' : 'navy'} ;
  color :  ${(props)=>props.theme.theme==='light' ? props.currentVillage === props.name ? 'white' : 'black'  : 'grey'} ;
    padding: 5%;
    border-radius: 10px;
    transition : 0.3s;
`

1-3) React Query에 타입스크립트 적용하기

const { mutate } = useMutation((data : {email : string; docId : string}) =>deleteWishListItem(data), 
        {
        onSuccess: () => queryClient.invalidateQueries(['wishList'])
    })
    
<CardBtn onClick={()=>mutate({email, docId : info.docId})}>삭제하기</CardBtn>

mutate는 첫번째 인수가 변수이기 때문에 복수의 변수가 필요할 경우 객체형태로 전달해야한다.

2) 추가 변경 사항

2-1) 🔆 날씨 데이터 구조 수정

날짜 데이터 전체를 props로 전달했었다.
하지만 날짜 데이터 중 temp와 icon만 사용하는데 복잡한 구조를 가진 객체를 props로 전달하고 또 타입을 정의하는 것이 비효율적이라 판단하였다.
따라서 날짜 데이터 중에서 temp와 icon만 발췌해서 전달하는 방식으로 변경하였다.

export const getWeatherInfo : GetWeatherInfoParams  = async (name, lat , lon ) => {
  const weatherKey = process.env.REACT_APP_WEATHER_KEY
  const { data } = await axios.get(`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${weatherKey}`)
  return {
    name,
    temp : data.main.temp,
    icon : data.weather[0].icon,
  }
}

2-2) 📂디렉토리 구조 변경

components 폴더안에 파일들 중에 공통점을 가진 파일들이 있어 보였다.
부산프로젝트는
1. 로그인, 회원가입 페이지 / 비로그인시 홈화면
2. 날짜, 카카오맵을 이용한 랜딩페이지 / 로그인시 홈화면
3. 축제, 식당, wishToGoList 데이터를 기반으로한 카드들이 모여있는 페이지

크게 3가지 기능을 가진 페이지로 나눌 수 있다.

  1. 로그인 유무에 따른 Home페이지를 구성하는 컴포넌트
  2. 카드기반 페이지를 구성하는 컴포넌트
  3. 지도관련 컴포넌트
  4. 공용으로 사용하는 컴포넌트
📂components
    ┗ 📂CardPage
    	┗ 📃Card.tsx
    	┗ 📃DropDown.tsx
    	┗ 📃Pagination.tsx
    ┗ 📂Home
    	┗ 📃AuthForm.tsx
    	┗ 📃MainPage.tsx
    ┗ 📂Common
    	┗ 📃Title.tsx
    	┗ 📃ScrollToTop.tsx
    	┗ 📃Nav.tsx
    	┗ 📃Weather.tsx
    	┗ 📃LoadingSpinner.tsx
    	┗ 📃LoadingIndicator.tsx
    ┗ 📂Map
    	┗ 📃KakaoMap.tsx
    	┗ 📃MapModal.tsx
    	┗ 📃WeatherMap.tsx  

2-3) 🚀 React.Suspense 와 React Query의 isLoading

React Query를 통해 비동기 데이터를 받아 올경우

const { data, isLoading } = useQuery(
  ['wishList'], () => getWishList(email))

(isLoading : true, data : undefined)
(isLoading : false, data : [...])

isLoading 상태일 경우 데이터가 없다.
data가 undefined일 때 컴포넌트에서 data.length, data.map을 요구할 경우 에러를 발생시킨다.
때문에 비동기데이터함수의 상태에 따라 분기를 따줘야 했었다.

if(isLoading) {<div>로딩 중</div>}
return (
  <div>{data.length} 데이터 갯수</div>
)

나는 컴포넌트마다 위와 같은 분기처리를 해주는 것이 기형적이라 생각했었다.

React Query 면접 질문 공부를 하다가 isLoading상태를 Suspense를 통해 공통적으로 넘겨 줄 수 있다는 것을 알게 되었다.

React-Query 개념 및 정리

//suspense : true 옵션을 켜줘야 한다. 
    const { data } = useQuery(
        ['wishList'], () => getWishList(email),
        {
            suspense : true
        }
    )
//app.tsx 에서 Suspense fallback시 어떻게 넘어갈지 설정해줘야 한다. 
<BrowserRouter>
      <ThemeProvider theme={themeState as DefaultTheme}>
        <GlobalStyle theme={themeObject} />
        <Suspense fallback={<CommonContainer><LoadingSpinner/>	</CommonContainer>}>
          <Router />
        </Suspense>
      </ThemeProvider>
    </BrowserRouter>

React의 Suspense를 사용하는 형태는 대부분 위의 형태와 같은데, 이때 React-Query의 suspense 옵션을 true로 하여 data fetching시에도 fallback UI가 나타나도록 하면 아래의 순서로 동작한다.

  1. Suspense mount
  2. MainComponent mount
  3. MainComponent에서 useQuery에 있는 api Call
  4. MainComponent unmount, fallback UI인 Loader mount
  5. api Call Success일 경우, useQuery에 있는 onSuccess 동작
  6. onSuccess 완료 이후 Loader unmount, MainComponent mount

suspense : true 옵션을 통해 isLoading를 매 번 적어 줄 필요없이 일괄적으로 처리 할 수 있게 되었다.

profile
실패를 두려워하지 않고 배우고 기록하여 내일의 밑거름 삼아 다음 단계로 성장하겠습니다.

0개의 댓글