혼자 적어보는 노트

프로그래머스 데브코스 TIL - Day 48 본문

스터디

프로그래머스 데브코스 TIL - Day 48

jinist 2022. 5. 25. 19:29

✅ 오늘의 학습

📌 React (1)

 

- CRA / JSX

- 컴포넌트 이론

- useEffect

- useRef

- pagination

 


 

재사용성이 좋은 컴포넌트 만들기?

 

UI를 추상적으로 바라보기!

추상적으로 바라보려면 ? 공통점을 찾아보자

-> 공통적으로 활용할 수 있는 부분을 찾아보고 공통점에 맞게 컴포넌트를 구성해보기.

❗❗ 다만 너무 추상적으로 바라보면 이후에 구분하기 더 어려울 수 있다. 

 

강의에서 아토믹 디자인 패턴이라는 키워드를 던져주셔서 검색해보고,

강사님의 글을 읽어보았다.

 

아토믹 디자인 패턴

 

이름에서 유추할 수 있듯 가장 작은단위의 컴포넌트를 원자로 설정하고,
이를 바탕으로 상위 컴포넌트를 만들어서 코드 재사용을 최대화 하는 방법론이다.

 

 

작은 단위의 컴포넌트

Paragraph.js

import PropTypes from "prop-types";

function Paragraph({ children, size = 20, color = "black" }) {
  return <p style={{ fontSize: size, color }}>{children}</p>;
}

Paragraph.propTypes = {
  children: PropTypes.node.isRequired,
  size: PropTypes.number,
};

export default Paragraph;

P태그만을 다루는 컴포넌트이다.

상위 컴포넌트에서 사용 시 아래와 같이 사용할 수 있다.

<Paragraph size="16px" color="#61DAFB">
  Edit <code>src/App.js</code> and save to reload.
</Paragraph>

이전에 내가 만들었던 컴포넌트들을 떠올려보면 작은 단위의 컴포넌트들은 만들지 않았었는데
작은 단위로 컴포넌트를 만들고 props를 통해 데이터를 전달하면서
블록처럼 사용한다는 개념에 대해 이해가 되었다.

 

useEffect

  useEffect(() => {
    const handleScroll = () => {
      console.log(window.scrollY);
    };
    document.addEventListener("scroll", handleScroll);
    return () => {
      document.removeEventListener("scroll", handleScroll);
    };
  });

전역적으로 이벤트를 잡았을 경우 컴포넌트가 언마운트 될 때

이벤트를 지워주어야 한다. 깜빡하지 말기!

 

 

useRef의 사용 용도

 

1. DOM에 직접 접근할 때 사용

예를 들어 useRef로 하위 컴포넌트의 input 연결할 경우

import Input from "./components/Input";
import { useRef } from "react";

function App() {
  const inputRef = useRef();

  return (
    <div className="App">
      <Input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </div>
  );
}

export default App;

inputRef를 생성하고 Input 컴포넌트에 값을 전달한다.

 

import React from "react";

const Input = React.forwardRef((_, ref) => {
  return (
    <>
      <input ref={ref} />
    </>
  );
});

export default Input;

 

React.forwardRef를 사용하여 두번째 인자를 통해 내부의 요소에 ref를 지정할 수 있다. (첫 번째 인자는 props)

예전에 하위컴포넌트에 접근하기 위해 알아보던 중 키워드를 잘못 검색해서 시간을 할애한 적이 있었는데

강의 내부에서 다뤄주셔서 좋았다.

 

2. 지역 변수로 사용

useState는 값이 변경될 때 다시 렌더링 되지만

useRef는 값이 변경되어도 다시 렌더링을 하지 않는 점을 이용하여 활용할 수 있다.

 

useRef를 지역변수로 활용해 본 적은 없었는데

보통 setTimeout, setInterval 을 통해 만들어진 id나 scroll 위치 등에 사용된다고 한다.

import { useRef, useState } from "react";

const AutoCounter = () => {
  const [count, setCount] = useState(0);
  const intervalId = useRef();

  const handleStart = () => {
    intervalId.current = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
  };
  const handleStop = () => {
    clearInterval(intervalId.current);
  };

  return (
    <>
      <div>{count}</div>
      <button onClick={handleStart}>Start</button>
      <button onClick={handleStop}>Stop</button>
    </>
  );
};

export default AutoCounter;

 

pagination 만들기

강의 내용의 사용 사례에서 pagination을 다뤄주었다.

 

pagination.js

const Pagination = ({ defaultPage = 0, limit = 10, total = 100, onChange }) => {
  const [page, setPage] = useState(defaultPage);

  const totalPage = Math.ceil(total / limit);

  const handleChangePage = (newPage) => {
    onChange(newPage);
    setPage(newPage);
  };
  return (
    <div>
      <button onClick={() => page !== 0 && handleChangePage(page - 1)}>이전</button>
      {Array.from(new Array(totalPage), (_, i) => i)
        .map((i) => (
          <button
            key={i}
            style={{ backgroundColor: page === i ? "red" : undefined }}
            onClick={() => handleChangePage(i)}
          >
            {i + 1}
          </button>
        ))}
      <button onClick={() => page + 1 !== limit && handleChangePage(page + 1)}>다음</button>
    </div>
  );
};

export default Pagination;

Pagination 컴포넌트에 props를 통해

처음에 위치할 페이지인 defaultPage,  출력할 article의 개수인 limit, article의 총 개수인 total을 전달한다.

 

해당 컴포넌트 안에서 클릭한 페이지를 page state로 관리를 하고

버튼들을 통해 페이지 변경을 핸들링하게 처리를 했다.

 

function App() {
  const articles = new Array(100).fill().map((_, i) => ({ id: i, title: `${i}번 게시물` }));
  const [page, setPage] = useState(0);

  const limit = 10;
  const offset = page * limit;

  return (
    <div className="App">
      <Pagination ref={inputRef} defaultPage={0} limit={10} total={100} onChange={setPage} />
      <Board articles={articles.slice(offset, offset + limit)} />
    </div>
  );
}

export default App;

보통 나는 하위 컴포넌트에 함수를 전달할 때는 하나의 함수를 만들어서

그 내부에서 setState를 했었는데 직접적으로 setState 자체를 전달하는 방법을 사용해도 된다는 것을 알게 되었다.

 

pagination에서 나타나는 숫자 변경

 

[pagination.js]

{Array.from(new Array(totalPage), (_, i) => i)
        .filter((i) => {
          if (page < 3) {
            return i < 5;
          } else if (page > totalPage - 3) {
            return i >= totalPage - 5;
          }
          return i >= page - 2 && i <= page + 2;
        })
        .map((i) => (
        /// 생략

강의에서는 위와 같이 받은 데이터를 고차함수를 연결해서 사용함으로써 데이터를 나누었는데,

위와 같은 방식을 내가 많이 활용하지 못했어서 다음에는 이렇게 가공을 해보는 연습을 해야겠다고 생각했다.

 


✍ 느낀 점

 

기다리고 기다리던 리액트 강의 시작!!

앞전에 vanillaJS와 vue를 하다가 다시 리액트를 하려니 처음에 조금 어색했지만

강의를 보며 컴포넌트들을 구성해보니 기억들이 조금씩 찾아왔다...

이전에 리액트를 먼저 접했었지만 이번 강의에서 몰랐던 부분들도 알게되었고,

잊을 뻔한 기억들도 다시 상기시켜주는 강의 시간이었다.

좋아하는 파트인 만큼 열심히 머릿속에 집어넣어야겠다.

Comments