리팩토링 (1) - home

jiny·2022년 8월 17일
0
post-thumbnail

리팩토링을 시작하게 된 이유

  • 스타일로는 컴포넌트 대로 분리를 했지만 비즈니스 로직이 분리되지 않아 후에 유지보수 하기 매우 불편할 거라고 생각함
  • 또한, 가능하다면 기능별로 하나씩 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로 받아 조건에 따라 다른 이미지를 보여주는 컴포넌트

PrimaryButton.tsx

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로 부터 받은 데이터, 온도와, 날씨를 리턴

0개의 댓글