[LG CNS AM 6기] 6일차 TIL : React Hooks와 UI 라이브러리, Axios 연동

윤현일·2026년 8월 4일

LGCNS

목록 보기
14/43

1. 오늘의 한 줄 요약

useState와 useEffect로 React의 상태와 생명주기를 관리하고, MUI·styled-components로 재사용 가능한 버튼을 만든 뒤 Axios 공통 인스턴스로 서버 데이터를 요청하는 방법을 학습했다.

2. 배운 내용

핵심 개념

React Hook

Hook은 함수형 컴포넌트에서 상태와 생명주기 등의 React 기능을 사용할 수 있도록 제공하는 함수다.

import {
  useState,
  useEffect,
} from "react";

대표적인 Hook으로 다음 두 가지가 있다.

  • useState: 컴포넌트의 상태를 관리한다.
  • useEffect: 렌더링 이후 실행할 작업을 관리한다.

Hook은 컴포넌트의 최상위 영역에서 호출해야 한다. 조건문이나 반복문 내부에서 호출하면 안 된다.

일반 변수와 state의 차이

일반 변수의 값을 변경해도 React는 화면을 다시 렌더링하지 않는다.

let count = 0;

const increase = () => {
  count += 1;
};

화면과 함께 변경돼야 하는 값은 state로 관리해야 한다.

const [count, setCount] = useState(0);

useState(0)은 두 값을 반환한다.

  • count: 현재 상태
  • setCount: 상태를 변경하는 함수
setCount() 호출
    ↓
state 변경
    ↓
컴포넌트 다시 렌더링
    ↓
변경된 값이 화면에 표시

이전 state를 기준으로 변경하기

기존 상태를 기준으로 새로운 값을 계산할 때는 함수형 업데이트를 사용할 수 있다.

const increaseHandler = () => {
  setCount((previousCount) => previousCount + 1);
};

const decreaseHandler = () => {
  setCount((previousCount) => previousCount - 1);
};

다음 코드보다 안전한 방법이다.

setCount(count + 1);

React는 상태 변경을 모아서 처리할 수 있기 때문에 이전 상태를 기반으로 계산할 때는 함수형 업데이트를 사용하는 것이 좋다.

state 변경 직후의 값

상태 변경 함수는 현재 실행 중인 함수의 변수를 즉시 변경하지 않는다.

const increaseHandler = () => {
  setCount((previousCount) => previousCount + 1);

  console.log(count);
};

위 로그에는 변경되기 전 값이 출력될 수 있다. 현재 이벤트 핸들러가 실행되는 동안의 count는 기존 렌더링 시점의 값이기 때문이다.

변경된 값을 확인하려면 useEffect를 사용할 수 있다.

useEffect(() => {
  console.log(`변경된 인원: ${count}`);
}, [count]);

useEffect

useEffect는 컴포넌트가 렌더링된 이후 실행해야 하는 작업을 처리한다.

useEffect(() => {
  console.log("컴포넌트 렌더링 이후 실행");
});

대표적인 사용 사례는 다음과 같다.

  • API 요청
  • state 변경 감지
  • 타이머 등록
  • 이벤트 리스너 등록
  • 외부 시스템과 동기화

두 번째 인수인 의존성 배열에 따라 실행 시점이 달라진다.

// 렌더링될 때마다 실행
useEffect(() => {
  console.log("렌더링");
});
// 컴포넌트가 처음 렌더링된 후 한 번 실행
useEffect(() => {
  console.log("최초 실행");
}, []);
// count가 변경될 때 실행
useEffect(() => {
  console.log(count);
}, [count]);

현재 인원 변경을 감지하려면 빈 배열이 아닌 [count]를 전달해야 한다.

입장 인원 컴포넌트

CapacityPage에서 입장 인원을 state로 관리했다.

const CapacityPage = () => {
  const [count, setCount] = useState(0);

  const increaseHandler = () => {
    setCount((previousCount) => previousCount + 1);
  };

  const decreaseHandler = () => {
    setCount((previousCount) => previousCount - 1);
  };

  useEffect(() => {
    console.log(`현재 인원: ${count}`);
  }, [count]);

  return (
    <div>
      <p>입장 인원: {count}</p>

      <Button
        title="입장"
        onClick={increaseHandler}
      />

      <Button
        title="퇴장"
        onClick={decreaseHandler}
      />
    </div>
  );
};

count가 변경될 때마다 컴포넌트가 다시 렌더링되어 화면의 입장 인원도 변경된다.

퇴장 인원이 음수가 되지 않게 하려면 조건을 추가할 수 있다.

const decreaseHandler = () => {
  setCount((previousCount) =>
    Math.max(0, previousCount - 1),
  );
};

이벤트 함수를 Props로 전달하기

부모 컴포넌트에서 만든 이벤트 함수를 자식 컴포넌트에 Props로 전달할 수 있다.

<MaterialButton
  title="글 작성하기"
  onClick={saveHandler}
/>

자식 컴포넌트는 전달받은 함수를 버튼 이벤트에 연결한다.

const MaterialButton = ({
  title,
  onClick,
}) => {
  return (
    <Button onClick={onClick}>
      {title}
    </Button>
  );
};

다음처럼 불필요하게 함수를 한 번 더 감싸지 않아도 된다.

// 동작하지만 불필요하게 감싼 형태
onClick={() => saveHandler()}

// 함수를 바로 전달
onClick={saveHandler}

이벤트 처리 로직은 부모가 관리하고, 자식 버튼은 화면과 클릭 연결에 집중할 수 있다.

MUI

MUI는 React에서 사용할 수 있는 UI 컴포넌트 라이브러리다.

현재 프로젝트에는 다음 패키지가 설치되어 있다.

npm install @mui/material @emotion/react @emotion/styled

MUI의 버튼을 가져와 사용할 수 있다.

import Button from "@mui/material/Button";

const MaterialButton = ({
  title,
  onClick,
}) => {
  return (
    <Button
      variant="contained"
      onClick={onClick}
    >
      {title}
    </Button>
  );
};

MUI를 사용하면 버튼, 입력창, 모달 등의 UI를 직접 처음부터 만들지 않고 재사용할 수 있다.

styled-components

styled-components는 JavaScript 파일 안에서 CSS를 작성하는 CSS-in-JS 라이브러리다.

import styled from "styled-components";

const StyledButton = styled.button`
  padding: 8px 16px;
  font-size: 16px;
  border-radius: 8px;
  cursor: pointer;
`;

만든 스타일 컴포넌트는 일반 React 컴포넌트처럼 사용한다.

const Button = ({
  title,
  onClick,
}) => {
  return (
    <StyledButton onClick={onClick}>
      {title}
    </StyledButton>
  );
};

외부 CSS 파일 없이 컴포넌트와 스타일을 하나의 파일에서 관리할 수 있다.

MUI
→ 미리 만들어진 디자인 시스템 사용

styled-components
→ 필요한 디자인을 직접 컴포넌트로 작성

Axios 공통 인스턴스

Axios 요청을 보낼 때마다 서버 주소를 반복해서 작성하지 않도록 공통 인스턴스를 만들 수 있다.

import axios from "axios";

const endPoint =
  process.env.REACT_APP_BACKEND_ENDPOINT;

const api = axios.create({
  baseURL: endPoint,
});

export default api;

페이지에서는 공통 인스턴스를 가져와 상대 경로만 작성한다.

import api from "../../api/axios";

const response = await api.get("/comment");
환경변수의 서버 주소
        +
/comment
        ↓
최종 요청 URL

공통 인스턴스를 사용하면 다음 설정을 한곳에서 관리할 수 있다.

  • baseURL
  • 요청 제한 시간
  • 공통 헤더
  • 인증 토큰
  • 요청·응답 인터셉터

React 환경변수

Create React App에서 브라우저 코드로 사용할 환경변수는 REACT_APP_으로 시작해야 한다.

REACT_APP_BACKEND_ENDPOINT=http://localhost:3001

JavaScript에서는 다음과 같이 사용한다.

process.env.REACT_APP_BACKEND_ENDPOINT

환경변수를 변경하면 개발 서버를 다시 실행해야 적용된다.

프론트엔드 환경변수는 브라우저 번들에 포함될 수 있으므로 비밀번호나 API 비밀 키를 저장하면 안 된다.

Axios 데이터와 state

일반 변수에 Axios 응답을 할당해도 화면이 자동으로 다시 렌더링되지 않는다.

let comments = [];

const loadData = async () => {
  const response = await api.get("/comment");

  comments = response.data;
};

서버 응답을 화면에 표시하려면 state로 관리해야 한다.

const [comments, setComments] =
  useState([]);

컴포넌트가 처음 렌더링됐을 때 데이터를 요청한다.

useEffect(() => {
  const loadComments = async () => {
    try {
      const response =
        await api.get("/comment");

      setComments(response.data);
    } catch (error) {
      console.error(error);
    }
  };

  loadComments();
}, []);

state가 변경되면 댓글 목록도 다시 렌더링된다.

return (
  <div>
    {comments.map((comment, index) => (
      <Comment
        key={`${comment.writer}-${index}`}
        data={comment}
      />
    ))}
  </div>
);

가능하면 서버 데이터에 고유한 id를 추가하고 이를 key로 사용하는 것이 좋다.

로컬 Mock 데이터

db.json에 댓글 목록을 저장해 로컬 API 데이터처럼 사용할 수 있다.

{
  "comment": [
    {
      "writer": "hy",
      "comment": "hihi"
    }
  ]
}

이 데이터를 API로 제공하면 Axios에서 다음 경로로 요청할 수 있다.

api.get("/comment");

백엔드 서버가 준비되지 않은 상황에서도 프론트엔드의 API 요청과 렌더링을 연습할 수 있다.

새롭게 알게 된 점

  • 일반 변수의 값이 변경돼도 React 화면은 다시 렌더링되지 않는다.
  • 화면에 표시되는 동적인 값은 useState로 관리해야 한다.
  • 이전 상태를 기준으로 변경할 때는 함수형 업데이트를 사용할 수 있다.
  • useEffect의 의존성 배열에 따라 실행 시점이 달라진다.
  • 부모의 이벤트 함수를 자식 컴포넌트에 Props로 전달할 수 있다.
  • MUI와 styled-components는 서로 다른 방식으로 재사용 가능한 UI를 만든다.
  • Axios 인스턴스를 사용하면 서버 주소와 공통 설정을 한곳에서 관리할 수 있다.
  • Axios 응답을 화면에 표시하려면 일반 변수가 아닌 state에 저장해야 한다.

헷갈렸던 점

  • setCount() 호출 직후 로그에서 이전 값이 출력되는 이유가 헷갈렸다.
  • useEffect(() => {}, [])와 useEffect(() => {}, [count])의 차이가 헷갈렸다.
  • 이벤트 함수를 호출한 결과와 함수 자체를 Props로 전달하는 문법이 혼동됐다.
  • MUI 컴포넌트와 styled-components로 만든 컴포넌트의 차이가 헷갈렸다.
  • Axios 응답을 일반 변수에 저장해도 화면이 변경될 것으로 생각했다.

3. 실습 / 적용

직접 해본 것

  • useState로 입장 인원을 관리했다.
  • 입장·퇴장 버튼을 눌러 state를 증가·감소시켰다.
  • useEffect로 컴포넌트 렌더링 이후의 작업을 확인했다.
  • styled-components로 공통 버튼을 만들었다.
  • MUI의 Button을 감싸 재사용 가능한 버튼 컴포넌트를 만들었다.
  • 부모 컴포넌트의 클릭 이벤트 함수를 버튼 Props로 전달했다.
  • 환경변수를 사용해 Axios 공통 baseURL을 설정했다.
  • 로컬 댓글 데이터를 컴포넌트 목록으로 렌더링했다.

결과

일반 변수가 아닌 state를 사용해 값의 변경과 화면 렌더링을 연결할 수 있었다. 또한 서로 다른 UI 라이브러리로 공통 버튼을 만들고, 공통 Axios 인스턴스를 통해 API를 호출하는 구조를 이해했다.

4. 문제와 해결

막힌 부분

입장·퇴장 버튼을 눌러 일반 변수를 변경해도 화면이 갱신되지 않았다. setCount() 직후 로그에는 이전 값이 출력됐으며, 댓글 API 응답을 일반 변수에 저장했을 때도 목록이 다시 렌더링되지 않았다.

해결 방법

화면에 영향을 주는 값은 useState로 관리하고 setter 함수로 변경했다.

setCount(
  (previousCount) => previousCount + 1,
);

변경된 상태를 확인해야 할 때는 해당 값을 useEffect의 의존성 배열에 추가했다.

useEffect(() => {
  console.log(count);
}, [count]);

Axios 응답도 state에 저장해 컴포넌트가 다시 렌더링되도록 구성했다.

const response =
  await api.get("/comment");

setComments(response.data);

목록을 렌더링할 때는 각 컴포넌트에 key를 지정했다.

5. 다음에 할 일

  • Axios로 댓글을 불러와 useState에 저장하기
  • 로딩·성공·실패 상태를 나누어 화면에 표시하기
profile
개발자

0개의 댓글