리팩토링 (2) - check

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

  • 이번엔 check 컴포넌트에 대해 리팩토링 해보려 한다.
  • 전과 마찬가지로 비즈니스 로직이 있는 것들 위주로 진행하려 한다.
  • 뒤로가기 버튼, 차트, 문제 만들기 버튼, Weekly Check에 있는 Circle 컴포넌트를 분리한 후 하나의 컴포넌트로 결합하는 형태이다.

컴포넌트 분리 전

import { 
    HeadTitle, CommonComponent, Container, PrimaryButton, SubTitle, Title, CircleComponent, Circle, BackCircle 
} from "../components/Commons";
import ApexChart from "react-apexcharts";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { correctNum, incorrectNum, weeklyCheck } from "../utils/storage";
import { useRecoilState, useRecoilValue } from "recoil";
import moment from "moment";
import { useInterval } from 'react-use';

interface IWeekInfo {
    id : number
    date : string,
    state : boolean
}

function Check() {
    const[seconds, setSeconds] = useState(moment().format('HH:mm:ss'));
    const dayOfWeek = new Date(moment().format('YYYY-MM-DD')).getDay();
    
    // week state
    const [week, setWeek] = useRecoilState<IWeekInfo[]>(weeklyCheck);

    useInterval(()=>{
        setSeconds(moment().format('HH:mm:ss'));
    }, 1000);

    useEffect(() => {
        (seconds === '00:00:00' && dayOfWeek === 1) && setWeek(
            (week) => {
                let arr = [
                    {id: 0, date: '일', state: false},
                    {id: 1, date: '월', state: false},
                    {id: 2, date: '화', state: false}, 
                    {id: 3, date: '수', state: false}, 
                    {id: 4, date: '목', state: false}, 
                    {id: 5, date: '금', state: false}, 
                    {id: 6, date: '토', state: false}, 
                ]
                return arr;
            }
        );
    },[seconds]);

    // 그래프에 표시되는 맞는 갯수, 틀린 갯수 표시
    const correctQuestion = useRecoilValue(correctNum);
    const incorrectQuestion = useRecoilValue(incorrectNum);

    // Weekly Check에서 원을 클릭 시 체크 된 원으로 변경
    const handleOnCircleClick = (id : number) => {     
        setWeek((oldWeek) => {
            // 기존 week의 id과 인자로 받은 요일의 id가 같으면 oldWeek 해당 요일의 인덱스를 반환
            const index = oldWeek.findIndex(day => day.id === id);
            // 해당 요일에 대해 state가 변한 새로운 object 생성
            const newDay = {id, date: week[index].date, state: !week[index].state};
            // 새로운 Week를 리턴
            const newWeek = [...oldWeek.slice(0, index), newDay, ...oldWeek.slice(index+1)];
            /* 생각해 봐야할 점 */
            // id 0~6의 state가 모두 true라면 모든 week의 state를 초기화
            return newWeek;
        })
    }

    // 문제 만들기 페이지로 이동
    const createQuestion = useNavigate();
    const handleCreateQuestion = () => {
        createQuestion('/create');
    }
    
    // 뒤로가기 버튼
    const moveBack = useNavigate();
    const handleBackCircleClick = () => {
        moveBack('/');
    }
    return (
        <Container>
            <BackCircle
                onClick = {handleBackCircleClick}
            >
                <img style={{marginRight:'2px', width: '50%'}} src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNzUycHQiIGhlaWdodD0iNzUycHQiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDc1MiA3NTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiA8ZGVmcz4KICA8Y2xpcFBhdGggaWQ9ImEiPgogICA8cGF0aCBkPSJtMjQzIDEzOS4yMWgyNjZ2NDczLjU4aC0yNjZ6Ii8+CiAgPC9jbGlwUGF0aD4KIDwvZGVmcz4KIDxnIGNsaXAtcGF0aD0idXJsKCNhKSI+CiAgPHBhdGggZD0ibTI1MS41OCAzNTQuNzMgMjA3LjAyLTIwNy4wMmMxMi4wNTEtMTEuMzQ0IDMwLjQ4NC0xMS4zNDQgNDEuODI4IDBzMTEuMzQ0IDI5Ljc3NyAwIDQxLjgyOGwtMTg2LjQ1IDE4Ni40NSAxODYuNDUgMTg2LjQ1YzExLjM0NCAxMS4zNDQgMTEuMzQ0IDMwLjQ4NCAwIDQxLjgyOHMtMjkuNzc3IDExLjM0NC00MS44MjggMGwtMjA3LjAyLTIwNy43MmMtMTEuMzQ0LTExLjM0NC0xMS4zNDQtMjkuNzc3IDAtNDEuODI4eiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiA8L2c+Cjwvc3ZnPgo=" />
            </BackCircle>
            <Title>CHECK PAGE</Title>
            <SubTitle>체크한 결과를 확인 하며, 매주 체크했다면 기록해보세요 :) </SubTitle>
            <CommonComponent style={{paddingTop: '20px'}}>
                <HeadTitle>Today Result</HeadTitle>
                <ApexChart
                    style={{marginLeft: '50px', marginTop : '40px'}}
                    type="donut"
                    width="100%"
                    series={[correctQuestion,incorrectQuestion]}
                    options={{
                        colors : ['#3D6FF3', '#F20000'],
                        series: [incorrectQuestion,incorrectQuestion],
                        labels: ['O', 'X'],
                        plotOptions:{
                            pie:{
                                donut:{
                                    labels:{
                                        show: true,
                                        total:{
                                            show:true,
                                            showAlways:true,
                                            fontSize:"24px",
                                            color:"#2787AB"
                                        }
                                    }
                                }
                            }
                        }
                    }}
                />
                <PrimaryButton
                    onClick={handleCreateQuestion}
                >
                    문제 만들기
                </PrimaryButton>
            </CommonComponent>
            <CommonComponent style={{padding:'5px 0px',borderRadius:'28px', marginTop:'30px', height: '200px'}}>
                    <HeadTitle>Weekly Check</HeadTitle>
                    <CircleComponent>
                        {week.map(day => 
                          day.state === true ? 
                            (<Circle
                                style={{color:'white', backgroundColor:'#3AA84C'}} 
                                onClick={() => handleOnCircleClick(day.id)}
                                key={day.id}>
                                    <svg id="SvgjsSvg1012" width="30" height="30" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlnsXlink="http://www.w3.org/1999/xlink"><defs id="SvgjsDefs1013"></defs><g id="SvgjsG1014"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="30" height="30"><rect width="256" height="256" fill="none"></rect><polyline fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="24" points="216 72.005 104 184 48 128.005"></polyline></svg></g></svg>
                                    </Circle>) : 
                            ( 
                                <Circle
                                    onClick={() => handleOnCircleClick(day.id)} 
                                    key={day.id}
                                >
                                    {day.date}
                                </Circle>
                            )
                        )}
                    </CircleComponent>
            </CommonComponent>
        </Container>
    )
}

export default Check

컴포넌트 분리 후

import { 
    HeadTitle, CommonComponent, Container, SubTitle, Title, CircleComponent 
} from "src/components/commons/Commons";
import { useEffect } from "react";
import Chart from "src/components/check/atoms/Chart";
import PrimaryButton from "src/components/commons/PrimaryButton";
import BackButton from "src/components/commons/BackButton";
import Circle from "src/components/check/atoms/Circle";
import useTimer from "src/hooks/useTimer";

export default function CheckComponent() {
    // 현재 시간, 현재 날짜, 초기화 되기 전 week, state가 모두 false인 newWeek를 저장해놓은 customHook
    const {seconds, dayOfWeek, setWeek, newWeek} = useTimer();

    // 브라우저가 mount 됬을 때 일 -> 월로 넘어가는 00:00:00이면 모든 Circle State 초기화
    useEffect(() => {
        (seconds === '00:00:00' && dayOfWeek === 1) && setWeek(
            (week) => {
                return newWeek;
            }
        );
    },[seconds]);

    return (
        <Container>
            <BackButton url={"/"} />
            <Title>CHECK PAGE</Title>
            <SubTitle>체크한 결과를 확인 하며, 매주 체크했다면 기록해보세요 :) </SubTitle>
            <CommonComponent style={{paddingTop: '20px'}}>
                <HeadTitle>Today Result</HeadTitle>
                <Chart/>
                <PrimaryButton
                    goPage={"/create"}
                    content={"문제 만들기"}
                >
                </PrimaryButton>
            </CommonComponent>
            <CommonComponent style={{padding:'5px 0px',borderRadius:'28px', marginTop:'30px', height: '200px'}}>
                <HeadTitle>Weekly Check</HeadTitle>
                <CircleComponent>
                    <Circle/>
                </CircleComponent>
            </CommonComponent>
        </Container>
    )
}
  • chart, circle, primarybutton, backbutton 컴포넌트 분해
  • 시간이 지나면 Circle 들이 초기화 되는 로직을 커스텀 훅(useTimer)을 통해 추상화

Today Result

BackButton, 문제 만들기 버튼은 저번 리팩토링때 한 코드를 재활용하는 형태

Chart.tsx

import ApexChart from "react-apexcharts";
import { useRecoilValue } from "recoil";
import { correctNum, incorrectNum } from "src/utils/storage";


export default function Chart() {
    // 그래프에 표시되는 맞는 갯수, 틀린 갯수 표시
    const correctQuestion = useRecoilValue(correctNum);
    const incorrectQuestion = useRecoilValue(incorrectNum);
    return(
        <ApexChart
            style={{marginLeft: '50px', marginTop : '40px'}}
            type="donut"
            width="100%"
            series={[correctQuestion,incorrectQuestion]}
            options={{
                colors : ['#3D6FF3', '#F20000'],
                series: [incorrectQuestion,incorrectQuestion],
                labels: ['O', 'X'],
                plotOptions:{
                    pie:{
                        donut:{
                            labels:{
                                show: true,
                                total:{
                                    show:true,
                                    showAlways:true,
                                    fontSize:"24px",
                                    color:"#2787AB"
                                }
                            }
                        }
                    }
                }
            }}
        />
    )
}
  • 차트는 맞는 갯수, 틀린 갯수를 보여주는 그래프의 기능을 한다고 생각하여 다음과 같이 코드를 작성
  • 맞는 갯수와 틀린 갯수의 상태는 다른 페이지에서도 다루는 상태이기 때문에 useRecoilValue를 통해 전역으로 관리 중인 데이터를 가지고 와 활용했다.

Weekly Check

Circle.tsx

import { useRecoilState } from "recoil";
import { weeklyCheck } from "src/utils/storage";
import styled from "styled-components";

export const CircleComponent = styled.div`
    display: flex;
    justify-content: center;
    align-items: center;
    width: 75%;
    height: 75%;
    border-radius: 50%;
    background-color : white;
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.15), 0 1px 3px rgba(0, 0, 0, 0.08);
    background-color: rgba(0,0,0,0.1);
`;

export interface IWeekInfo {
    id : number
    date : string,
    state : boolean
}

export default function Circle() {

    // week state(월~일)
    const [week, setWeek] = useRecoilState<IWeekInfo[]>(weeklyCheck);

    // Weekly Check에서 원을 클릭 시 체크 된 원으로 변경
    const handleOnCircleClick = (id : number) => {     
        setWeek((oldWeek) => {
            // 기존 week의 id과 인자로 받은 요일의 id가 같으면 oldWeek 해당 요일의 인덱스를 반환
            const index = oldWeek.findIndex(day => day.id === id);
            // 해당 요일에 대해 state가 변한 새로운 object 생성
            const newDay = {id, date: week[index].date, state: !week[index].state};
            // 새로운 Week를 리턴
            const newWeek = [...oldWeek.slice(0, index), newDay, ...oldWeek.slice(index+1)];
            return newWeek;
        })
    }
    return (
        <>
            {week.map(day => 
                day.state === true ? 
                (   <CircleComponent
                        style={{color:'white', backgroundColor:'#3AA84C'}} 
                        onClick={() => handleOnCircleClick(day.id)}
                        key={day.id}>
                            <svg id="SvgjsSvg1012" width="30" height="30" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlnsXlink="http://www.w3.org/1999/xlink"><defs id="SvgjsDefs1013"></defs><g id="SvgjsG1014"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="30" height="30"><rect width="256" height="256" fill="none"></rect><polyline fill="none" stroke="white" strokeLinecap="round" strokeLinejoin="round" strokeWidth="24" points="216 72.005 104 184 48 128.005"></polyline></svg></g></svg>
                    </CircleComponent>) : 
                ( 
                    <CircleComponent
                        onClick={() => handleOnCircleClick(day.id)} 
                        key={day.id}
                    >
                        {day.date}
                    </CircleComponent>
                )
            )}
        </>
    )
}
  • map 함수를 통해 state가 true인 것은 체크된 Circle 컴포넌트 false면 일반 Circle 컴포넌트를 보여주도록 설계했었다.
  • SRP(단일 책임 원칙)를 고려해보았을 때 Circle 컴포넌트의 역할은 state의 상태를 변경하는 것 하나라고 생각했다.
  • 그래서 더 이상 분해되지 않는다고 생각하여 현재의 Circle 컴포넌트를 구성하게 되었다.

useTimer.ts

import { useSetRecoilState} from "recoil";
import moment from "moment";
import { useInterval } from 'react-use';
import { useState } from "react";
import {IWeekInfo} from "src/components/check/atoms/Circle";
import { weeklyCheck } from "src/utils/storage";

// Weekly check Circle 들을 초기화 하기 위한 custom hook
export default function useTimer() {

    // 현재 시간을 위한 seconds, 1초마다 바뀌는 시간을 저장하기 위한 setSeconds
    const[seconds, setSeconds] = useState(moment().format('HH:mm:ss'));

    // 현재 날짜의 요일을 반환하기 위한 변수
    const dayOfWeek = new Date(moment().format('YYYY-MM-DD')).getDay();
    
    // 초기화 되기 전 weekState
    const setWeek = useSetRecoilState<IWeekInfo[]>(weeklyCheck);

    // 실시간으로 seconds를 업데이트 하기위한 useInterval 함수
    useInterval(()=>{
        setSeconds(moment().format('HH:mm:ss'));
    }, 1000);

    // 일 -> 월로 바뀌면 새로운 weekState를 저장하기 위한 배열
    let newWeek = [
        {id: 0, date: '일', state: false},
        {id: 1, date: '월', state: false},
        {id: 2, date: '화', state: false}, 
        {id: 3, date: '수', state: false}, 
        {id: 4, date: '목', state: false}, 
        {id: 5, date: '금', state: false}, 
        {id: 6, date: '토', state: false}, 
    ];

    return {
        seconds,
        dayOfWeek,
        setWeek,
        newWeek
    }
}
  • 시간이 지나면 circle들이 초기화 되는 로직을 custom hook으로 추상화 해보았다.
  • seconds, dayOfweek, setWeek, newWeek를 반환

0개의 댓글