리팩토링을 시작하게 된 이유
- 스타일로는 컴포넌트 대로 분리를 했지만 비즈니스 로직이 분리되지 않아 후에 유지보수 하기 매우 불편할 거라고 생각함
- 또한, 가능하다면 기능별로 하나씩 custom hook을 만들어 추상화 하면 보기 편한 구조가 될 거라고 생각함
- 그래서 각 도메인 별로 폴더를 구성 한 후 최대한 분리할 수 있는 만큼 분리해보고자 함
Home Component 분리 전
import { useQuery } from "@tanstack/react-query";
import React from "react";
import { useNavigate } from "react-router-dom";
import { CommonComponent, CommonSubComponent, Container, PrimaryButton, SubTitle, Title, WeatherContent } from "../components/Commons";
import { weatherAPIDefault ,IWeatherInfo } from "../utils/api";
function Home()
// api로 받은 데이터
const {data:data} = useQuery<IWeatherInfo>(["api", "weather"], weatherAPIDefault);
// 데이터로 받은 지역의 온도
const temp = Math.round((data?.main.temp as number) - 273.15);
// 도시의 현재 날씨
const weather = data?.weather[0].main;
const page = useNavigate();
const goToPage = () => {
page("/check");
}
return (
<Container>
<Title>학습하러 오신 것을 환영합니다!</Title>
<SubTitle> Urkunde에서 학습 해보세요 :) </SubTitle>
<CommonComponent>
<CommonSubComponent>
<Title style={{color:'black', marginTop:'0px', marginBottom:'10px'}}>{data?.name}</Title>
<WeatherContent>{temp + "℃"}</WeatherContent>
{ weather === "Clouds" && <svg style={{width: '100px', height:'100px'}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path d="M40.996 23.2a6.008 6.008 0 0 0-5.35-6.165A12.94 12.94 0 0 0 23 7c-7.168 0-13 5.832-13 13 0 .384.02.775.06 1.18C4.902 22.106 1 26.669 1 32c0 6.065 4.935 11 11 11h27c5.514 0 10-4.486 10-10a9.956 9.956 0 0 0-8.004-9.8zM39 41H12c-4.963 0-9-4.038-9-9 0-4.651 3.631-8.588 8.267-8.962l1.091-.088-.186-1.078A11.068 11.068 0 0 1 12 20c0-6.065 4.935-11 11-11 5.393 0 9.95 3.862 10.836 9.182l.145.871.882-.036A.503.503 0 0 0 35 19c2.206 0 4 1.794 4 4 0 .272-.03.553-.091.835l-.235 1.096 1.115.109A7.965 7.965 0 0 1 47 33c0 4.411-3.589 8-8 8z"/></svg>
}
{ weather === "Clear" &&
<img style={{marginTop : '10px'}} src="https://www.svgrepo.com/show/67337/sunny-day.svg" width="70" height="70" alt="Sunny Day SVG Vector" title="Sunny Day SVG Vector"></img>
}
{
weather === 'Rain' &&
<img style={{marginTop : '20px'}} src="https://www.svgrepo.com/show/140526/raining.svg" width="70" height="70" alt="Raining SVG Vector" title="Raining SVG Vector"></img>
}
</CommonSubComponent>
<PrimaryButton
onClick={goToPage}
>
Go to Ukkunde
</PrimaryButton>
</CommonComponent>
</Container>
)
}
export default Home;
Home Component 분리 후
// HomeComponent.tsx
import { CommonComponent, CommonSubComponent, Container, SubTitle, Title } from "src/components/commons/Commons"
import PrimaryButton from "src/components/commons/PrimaryButton";
import WeatherState from "src/components/home/atoms/WeatherState";
import Temperature from "src/components/home/atoms/Temperature";
import useWeatherData from "src/hooks/useWeatherData";
export default function HomeComponent () : React.ReactElement {
const {data, temp, weather} = useWeatherData();
return (
<Container>
<Title>학습하러 오신 것을 환영합니다</Title>
<SubTitle> Urkunde에서 학습 해보세요 </SubTitle>
<CommonComponent>
<CommonSubComponent>
<Title style={{color:'black', marginTop:'0px', marginBottom:'10px'}}>{data?.name}</Title>
<Temperature temp={temp}></Temperature>
<WeatherState weather={weather as string}/>
</CommonSubComponent>
<PrimaryButton
goPage={"check"}
content={"Go to Ukkunde"}
>
</PrimaryButton>
</CommonComponent>
</Container>
)
}
Temperature.tsx

import styled from "styled-components"
export const WeatherContent = styled.div`
display: flex;
justify-content: center;
font-size: 45px;
`
interface TemperatureProps {
temp : number;
}
export default function Temperature ({ temp } : TemperatureProps) : React.ReactElement {
return(
<WeatherContent>{temp + "℃"}</WeatherContent>
)
}
- api에서 가져오는 온도만 상위 컴포넌트인 HomeComponent에서 Props로 받아와 해당 프롭은 interface로 타입을 지정
- styled-components를 통해 명확하게 표현
WeatherState.tsx

import React from "react"
interface WeatherProps {
weather : string;
}
export default function WeatherState ({weather} : WeatherProps) : React.ReactElement {
return (
<>
{ weather === "Clouds" &&
<svg style={{width: '100px', height:'100px'}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path d="M40.996 23.2a6.008 6.008 0 0 0-5.35-6.165A12.94 12.94 0 0 0 23 7c-7.168 0-13 5.832-13 13 0 .384.02.775.06 1.18C4.902 22.106 1 26.669 1 32c0 6.065 4.935 11 11 11h27c5.514 0 10-4.486 10-10a9.956 9.956 0 0 0-8.004-9.8zM39 41H12c-4.963 0-9-4.038-9-9 0-4.651 3.631-8.588 8.267-8.962l1.091-.088-.186-1.078A11.068 11.068 0 0 1 12 20c0-6.065 4.935-11 11-11 5.393 0 9.95 3.862 10.836 9.182l.145.871.882-.036A.503.503 0 0 0 35 19c2.206 0 4 1.794 4 4 0 .272-.03.553-.091.835l-.235 1.096 1.115.109A7.965 7.965 0 0 1 47 33c0 4.411-3.589 8-8 8z"/></svg>
}
{ weather === "Clear" &&
<img style={{marginTop : '10px'}} src="https://www.svgrepo.com/show/67337/sunny-day.svg" width="70" height="70" alt="Sunny Day SVG Vector" title="Sunny Day SVG Vector"></img>
}
{
weather === 'Rain' &&
<img style={{marginTop : '20px'}} src="https://www.svgrepo.com/show/140526/raining.svg" width="70" height="70" alt="Raining SVG Vector" title="Raining SVG Vector"></img>
}
</>
)
}
- 마찬가지로 api에서 받아오는 데이터를 상위 컴포넌트인 HomeComponent에서 Props로 받아 조건에 따라 다른 이미지를 보여주는 컴포넌트
import { useNavigate } from "react-router-dom";
import styled from "styled-components"
export const Button = styled.button`
margin : 50px 0px;
width: 18%;
border: 0px;
height: 35px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.15), 0 1px 3px rgba(0, 0, 0, 0.08);
background-color: #3C73CF;
color: white;
`
export interface ButtonProps {
goPage : string;
content : string;
children : React.ReactNode;
}
export default function PrimaryButton({children, goPage, content} : ButtonProps) : React.ReactElement {
const movePage = useNavigate();
const goPages = () => {
movePage(goPage);
}
return(
<Button
onClick = {goPages}
>
{content}
</Button>
)
}
- 받는 props들에 대해 interface를 통해 타입 설정
- 상위 컴포넌트로 부터 Button에 보여지는 content을 string으로 받음
- 마찬가지로 이동하는 페이지 경로(goPage)를 string으로 받아 해당 페이지를 이동
- styled-component로 ui 구성
useWeatherData.ts
import { useQuery } from "@tanstack/react-query";
import { weatherAPIDefault, IWeatherInfo } from "src/utils/api";
export default function useWeatherData() {
// api로 받은 데이터
const {data} = useQuery<IWeatherInfo>(["api", "weather"], weatherAPIDefault);
// 데이터로 받은 지역의 온도
const temp = Math.round((data?.main.temp as number) - 273.15);
// 도시의 현재 날씨
const weather = data?.weather[0].main as string;
return {
data,
temp,
weather
}
}
- data?.name, temp, weather를 묶어서 추상화(custom hook)하여 표현하면 좀 더 보기 좋은 구조가 될 거같아 만든 custom hook
- api로 부터 받은 데이터, 온도와, 날씨를 리턴