// 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확장자로 바꿔준다.
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)
컴포넌트의 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;
`
const { mutate } = useMutation((data : {email : string; docId : string}) =>deleteWishListItem(data),
{
onSuccess: () => queryClient.invalidateQueries(['wishList'])
})
<CardBtn onClick={()=>mutate({email, docId : info.docId})}>삭제하기</CardBtn>
mutate는 첫번째 인수가 변수이기 때문에 복수의 변수가 필요할 경우 객체형태로 전달해야한다.
날짜 데이터 전체를 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,
}
}
components 폴더안에 파일들 중에 공통점을 가진 파일들이 있어 보였다.
부산프로젝트는
1. 로그인, 회원가입 페이지 / 비로그인시 홈화면
2. 날짜, 카카오맵을 이용한 랜딩페이지 / 로그인시 홈화면
3. 축제, 식당, wishToGoList 데이터를 기반으로한 카드들이 모여있는 페이지
크게 3가지 기능을 가진 페이지로 나눌 수 있다.
📂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
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를 통해 공통적으로 넘겨 줄 수 있다는 것을 알게 되었다.
//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가 나타나도록 하면 아래의 순서로 동작한다.
suspense : true 옵션을 통해 isLoading를 매 번 적어 줄 필요없이 일괄적으로 처리 할 수 있게 되었다.