글 작성자: 망고좋아
반응형

📖 오늘 배운 내용 - 2022.02.12

  • 그래프 페이지 mock 데이터 연결
  • json 서버 연결해서 axios 사용
  • 리액트 라이프 사이클
  • 타입스크립트의 필요성

 

📝 axios 결정 이유

  • axios는 json을 자동으로 적용해서 response 객체를 바로 반환하지만 fetch는 promise자체를 반환해서 json으로 변환을 해야 한다는 차이점이 있다.
  • axios는 data를 바로 전달하지만 fetch는 body로 json.stringify()를 통해서 서버가 이해할 수 있도록 문자열 파싱을 해야 한다.

 

📝 리액트 라이프 사이클 공부

  • 추후 추가

 

📝 로딩 창 :: suspense 사용

import React, { useCallback, useEffect, useState, Suspense } from "react";
import { Container } from "@/components/Container/style";
import DateController from "@/components/DateController";
import * as api from "@/api";
import LoadingModal from "@/components/LoadingModal";
import MonthYearBtn from "./MonthYearBtn";
import PieChartComponent from "./PieChart";
import { DateControllerWrapper } from "./style";

const LineGraph = React.lazy(() => import("./LineGraph"));

function Graph() {
  const monthButton = true;
  const yearButton = false;
  const ClickedMoth = true;
  const toDay = new Date();
  const [date, setDate] = useState(toDay);
  const [clickButtonColor, setClickButtonColor] = useState(true);
  const [checkMonth, setCheckMonth] = useState(false);
  const [graphTitle, setGraphTitle] = useState("월간");
  const [commitData, setCommitData] = useState([]);

  const getCommitsPerMonth = async () => {
    const year = date.toISOString().slice(0, 4);
    const data = await api.getCommitsTotalPerMonth(year);

    if (data.success) {
      const commitPerYear = await data.commitPerYear;

      const createData = commitPerYear.slice(1).map((commitCnt, index) => ({
        name: `${date.toISOString().slice(0, 2)}.${index + 1}`,
        commit: commitCnt,
      }));
      setCommitData(createData);
    } else {
      setCommitData([]);
    }
  };

  useEffect(() => {
    getCommitsPerMonth();
  }, [date]);

  const changeDate = (value) => {
    const newDate = new Date(date.getFullYear() + value, date.getMonth());
    setDate(newDate);
  };

  const clickLeft = () => {
    if (date.getFullYear() - 2000 <= 0) return;
    changeDate(-1);
  };

  const clickRight = () => {
    if (toDay.getFullYear() - date.getFullYear() <= 0) return;
    changeDate(1);
  };

  const goToday = () => {
    setDate(toDay);
  };

  const handleMonthBtn = useCallback(() => {
    if (monthButton) {
      setClickButtonColor(monthButton);
      setCheckMonth(false);
      setGraphTitle(ClickedMoth ? "월간" : "년간");
    }
  }, [graphTitle]);

  const handlYearBtn = useCallback(() => {
    if (!yearButton) {
      setClickButtonColor(yearButton);
      setCheckMonth(true);
      setGraphTitle(!ClickedMoth ? "월간" : "년간");
    }
  }, [graphTitle]);

  return (
    <Container>
      <Suspense fallback={<LoadingModal />}>
        <DateControllerWrapper>
          {!checkMonth && (
            <DateController
              date={date}
              clickLeft={clickLeft}
              clickRight={clickRight}
              goToday={goToday}
              month={false}
            />
          )}
        </DateControllerWrapper>

        <MonthYearBtn
          isClick={clickButtonColor}
          handlYearBtn={handlYearBtn}
          handleMonthBtn={handleMonthBtn}
        />
        <LineGraph graphTitle={graphTitle} commitData={commitData} />
        <PieChartComponent />
      </Suspense>
    </Container>
  );
}

export default Graph;

  • 로딩 창 부분을 suspense로 구현했다.
  • suspense의 신세계를 경험하고 팀원분들에게 공유했다. 그리고 적용하시던 중 페이지 내에서 비동기 요청 시 로딩 컴포넌트가 동작되지 않는다는 문제점을 말씀해주셨다.
  • suspense는 렌더링 시점에서만 감지해준다.
  • 그래서 캘린더 내에서 비동기 요청을 하더라도 리 렌더링을 하지 않는다.
  • 따라서 기존에 있는 isLoding을 완전히 대체할 수 없다. 첫 페이지 로딩은 suspense를 사용하고, 페이지 내에서 비동기 처리를 하는 부분은 isLoading을 사용해서 로딩 창을 보여주는 방식으로 가기로 했다.

  • 추후 데이터 불러오기도 지원해준다고 하니까 지원해줄 때까지 저는 숨 참고 기다리겠...
  • suspense를 공부하다 빠뜨린 내용이었는데.... 지식을 공유하고 모르는 정보를 얻을 수 있었다.
  • 이것이 집단지성의 힘인가! 공유를 함으로써 나 또한 모르는 지식을 얻을 수 있어서 좋았다.

 

import React, { Suspense, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router-dom";
import LoadingModal from "@/components/LoadingModal";

const Header = lazy(() => import("./components/Header"));
const Nav = lazy(() => import("./components/Nav"));
const Login = lazy(() => import("./pages/login"));
const Main = lazy(() => import("./pages/main"));
const MonthlyCalender = lazy(() => import("./pages/monthlyCalender"));
const RankPage = lazy(() => import("./pages/rank"));
const Graph = lazy(() => import("./pages/graph"));
const MyPage = lazy(() => import("./pages/myPage"));
const Setting = lazy(() => import("./pages/setting"));
const GoalSetting = lazy(() => import("./pages/goalSetting"));
const Badge = lazy(() => import("./pages/badge"));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingModal />}>
        <Header />
        <Nav />
        <Routes>
          <Route path="/" element={<Login />} />
          <Route path="/main" element={<Main />} />
          <Route path="/calender" element={<MonthlyCalender />} />
          <Route path="/rank" element={<RankPage />} />
          <Route path="/graph" element={<Graph />} />
          <Route path="/mypage" element={<MyPage />} />
          <Route path="/setting" element={<Setting />} />
          <Route path="/goal" element={<GoalSetting />} />
          <Route path="/badge" element={<Badge />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;
  • 따라서 suspense는 앱 전체에 적용해줘서 페이지 이동 때마다 로딩 창을 한 번에 구현하기로 하고, 페이지 내에서 데이터를 요청하는 부분에서는 isLoading라는 상태를 만들어줘서 로딩 컴포넌트를 구현하기로 했다.

 

🛠 graph/index.js

import React, { useCallback, useState } from "react";
import { Container } from "@/components/Container/style";
import DateController from "@/components/DateController";
import MonthYearBtn from "./MonthYearBtn";
import PieChartComponent from "./PieChart";
import { DateControllerWrapper } from "./style";

const LineGraph = React.lazy(() => import("./LineGraph"));

function Graph() {
  const monthButton = true;
  const yearButton = false;
  const ClickedMoth = true;
  const toDay = new Date();
  const [date, setDate] = useState(toDay);
  const [clickButtonColor, setClickButtonColor] = useState(true);
  const [checkMonth, setCheckMonth] = useState(false);
  const [graphTitle, setGraphTitle] = useState("월간");

  const changeDate = (value) => {
    const newDate = new Date(date.getFullYear() + value, date.getMonth());
    setDate(newDate);
  };

  const clickLeft = () => {
    if (date.getFullYear() - 2000 <= 0) return;
    changeDate(-1);
  };

  const clickRight = () => {
    if (toDay.getFullYear() - date.getFullYear() <= 0) return;
    changeDate(1);
  };

  const goToday = () => {
    setDate(toDay);
  };

  const handleMonthBtn = useCallback(() => {
    if (monthButton) {
      setClickButtonColor(monthButton);
      setCheckMonth(false);
      setGraphTitle(ClickedMoth ? "월간" : "년간");
    }
  }, [graphTitle]);

  const handlYearBtn = useCallback(() => {
    if (!yearButton) {
      setClickButtonColor(yearButton);
      setCheckMonth(true);
      setGraphTitle(!ClickedMoth ? "월간" : "년간");
    }
  }, [graphTitle]);

  return (
    <Container>
      <DateControllerWrapper>
        {!checkMonth && (
          <DateController
            date={date}
            clickLeft={clickLeft}
            clickRight={clickRight}
            goToday={goToday}
            month={false}
          />
        )}
      </DateControllerWrapper>

      <MonthYearBtn
        isClick={clickButtonColor}
        handlYearBtn={handlYearBtn}
        handleMonthBtn={handleMonthBtn}
      />
      <LineGraph graphTitle={graphTitle} date={date} clickYear={checkMonth} />
      <PieChartComponent />
    </Container>
  );
}

export default Graph;

 

🛠 LineGraph/index.js

import React, { useEffect, useState } from "react";
import LoadingModal from "@/components/LoadingModal";
import PropTypes from "prop-types";
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
} from "recharts";
import * as api from "@/api";
import * as LineGraphs from "./style";

function LineGraph({ graphTitle, date, clickYear }) {
  const [commitData, setCommitData] = useState([]);
  const [loading, setLoading] = useState(false);

  if (clickYear) {
    const getRecentThreeYearCommitsCount = async () => {
      setLoading(true);
      const data = await api.getRecentThreeYear();
      if (data.success) {
        const createData = data.recentThreeYear.map((it) => ({
          name: it[0],
          commit: it[1],
        }));
        setCommitData(createData);
      } else {
        setCommitData([]);
      }
      setLoading(false);
    };

    useEffect(() => {
      getRecentThreeYearCommitsCount();
    }, [clickYear]);
  } else {
    const getCommitsPerMonth = async () => {
      setLoading(true);
      const year = date.toISOString().slice(0, 4);
      const data = await api.getCommitsTotalPerMonth(year);
      if (data.success) {
        const commitPerYear = await data.commitPerYear;

        const createData = commitPerYear.slice(1).map((commitCnt, index) => ({
          name: `${year.slice(2, 4)}.${index + 1}`,
          commit: commitCnt,
        }));
        setCommitData(createData);
      } else {
        setCommitData([]);
      }
      setLoading(false);
    };

    useEffect(() => {
      getCommitsPerMonth();
    }, [date]);
  }

  return (
    <LineGraphs.Container>
      {loading ? (
        <LoadingModal />
      ) : (
        <>
          {commitData.length ? (
            <>
              <LineGraphs.Title>{graphTitle} 커밋 추이</LineGraphs.Title>
              <LineGraphs.Wrapper>
                <LineChart width={350} height={280} data={commitData}>
                  <CartesianGrid strokeDasharray="3 3" />
                  <XAxis dataKey="name" />
                  <YAxis />
                  <Tooltip />
                  <Line
                    type="monotone"
                    dataKey="commit"
                    stroke="#6ABD8C"
                    activeDot={{ r: 2 }}
                    isAnimationActive={false}
                  />
                </LineChart>
              </LineGraphs.Wrapper>
            </>
          ) : (
            <LineGraphs.NoData>데이터가 없습니다.</LineGraphs.NoData>
          )}
        </>
      )}
    </LineGraphs.Container>
  );
}

LineGraph.propTypes = {
  graphTitle: PropTypes.string,
  date: PropTypes.instanceOf(Date).isRequired,
  clickYear: PropTypes.bool.isRequired,
};

LineGraph.defaultProps = {
  graphTitle: "월간" || "연간",
};

export default LineGraph;

 

[React] 리액트 React.lazy와 Suspense란?

🎯 리액트 React.lazy와 Suspense란? 📝 React.lazy const SomeComponent = React.lazy(() => import('./SomeComponent')); const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyC..

lakelouise.tistory.com

 

📝 hasOwnProperty

  • hasOwnProperty를 사용하면 객체가 어떤 key값을 가지고 있는지 확인할 수 있다.
const getUsersReposLanguage = async () => {
    const res = await api.getReposLanguage();
    const languageCountObj = {};

    if (res.success) {
      const reposLanguageArray = res.data;
      reposLanguageArray.forEach((it) => {
        if (
          Object.prototype.hasOwnProperty.call(languageCountObj, it.language)
        ) {
          languageCountObj[it.language] += 1;
        } else {
          languageCountObj[it.language] = 1;
        }
      });

      const languageCountArray = [];
      const language = Object.keys(languageCountObj);
      const values = Object.values(languageCountObj);

      for (let i = 0; i < language.length; i += 1) {
        languageCountArray.push({
          name: language[i],
          value: values[i],
        });
      }

      languageCountArray.sort((a, b) => b.count - a.count);
      console.log(languageCountArray);
    }
  };
 

Object.prototype.hasOwnProperty() - JavaScript | MDN

hasOwnProperty() 메소드는 객체가 특정 프로퍼티를 가지고 있는지를  나타내는 불리언 값을 반환한다.

developer.mozilla.org

 

[Javascript] 객체에 해당 key값이 존재하는지 확인하는 방법

예전에 자바스크립트에서 한 객체가 특정한 키 값을 가지고 있는지 확인 한 뒤 처리 해야하는 로직이 있었는데, 그때 마다 내가 작성한 코드가 비효율적이란 생각을 떨치기 위해 공부하고 정리

velog.io

 

📝 hasOwnProperty eslint 오류 해결

 

[React] Do not access Object.prototype method 'hasOwnProperty' from target object. 에러 해결 방법

🎯 Do not access Object.prototype method 'hasOwnProperty' from target object. 에러 해결 방법 hasOwnProperty를 사용하려고 했는데 eslint 오류가 발생했다. 📝 해결 방법 (Object.prototype.hasOwnProperty..

lakelouise.tistory.com

 

💡 오늘 깨달은 것

  • 예전에는 컴퓨터가 느려서 복잡한 연산은 백에서 처리했지만, 요즘은 컴퓨터가 좋아져서 데이터 원형을 프론트에 어떻게든 빨리 던지고 프론트에서 복잡한 연산을 수행해서 데이터를 뿌려준다.
  • map, reduce는 브라우저마다 동작 방식이 다르기 때문에 사용자마다 다른 결과가 보일 수 있다. 이런 경우는 백에서 연산해서 프론트에 데이터를 넘겨주는 것이 좋다.
  • 어제오늘부터 타입스크립트의 필요성을 드디어 느끼게 되었다!!! 이전에는 타입스크립트가 좋다 좋다 그러고 많은 기업에서 사용하고 있다 보니 기초만 살짝 배우고 직접적으로 필요성을 느끼지 못했다.
  • 하지만 이번 리액트 프로젝트를 하면서 타입스크립트의 필요성을 절실히 느끼게 되었고 왜 많은 회사나 개발자들이 TS를 좋아하고 사용하는지 알 거 같다.
  • 차량 이동 중 공부 및 아이디어를 얻기 위해 다른 팀원분들의 코드를 태블릿으로 읽었다. proptypes를 적용하기 전에는 받아오는 props를 뭐지? 자료형이 뭐지? 이러면서 props를 따라 올라가고 그랬다.
  • 하지만 proptypes 적용 이후에는 이게 어떤 형태로 넘어와서 이렇게 뿌려주고 동작하는구나라는 것을 눈으로 코드가 읽히니까 이해하기 수월했다. 이러한 부분을 타입스크립트가 도와주니까 많은 개발자와 회사가 원하는구나....!!! 개발 시 실수도 줄여주고!!! 개발자를 위한 언어이다.
  • 이전에 김병철 코치님이 새로운 기술이 나온 배경에는 필요와 이유가 있다면서 이유를 찾아가며 공부하고 익히라고 했는데 뒤늦게 격하게 공감하게 되었다.
  • 이전까지는 취업을 위해 커리어를 위해 타입스크립트를 공부했지만, 이제는 내 필요에 의해 공부하게 될 테니 흥미가 생기고 재밌게 공부할 수 있을 거 같다!!

 

📌 참고

반응형