[TIL] FE-06 - React, TypeScript, env, lifecycle, side effect

W L·2026년 8월 4일

TIL

목록 보기
7/32

1. 오늘 학습한 주제

  • React
  • TypeScript
  • env
  • State
  • Lifecycle
  • Hook
  • side-effect

2. 배운 내용

TypeScript

  • type이 있는 script
let userName: string = "skrr";
let age: number = 28;
let isMarriage: boolean = false;
  • 타입은 컴파일 시점에만 존재. JS로 변환한 뒤에 실행됨.

Array

let ary: string[] = ["skrr", "skrr2"];

interface

객체의 타입을 미리 선언해두고 변수 타입으로 사용.
?는 옵셔널 프로퍼티(있어도 되고 없어도 됨).

interface User {
    email : string;
    password : string;
    address?: string;
}

const user: User = {
    email : "tpfh801@gmail.com",
    password : "12345678"
}

함수

function showMessage(name: string) : string {
    return `${name}님 환영합니다.`;
}

API 연동 환경 구성

json-server로 목 API 띄우기 (db.json)

  • json-server : 로컬 개발용 데이터베이스
json
{
    "comment": [
        { "writer": "임정섭", "comment": "..." },
        { "writer": "박선아", "comment": "..." }
    ]
}

최상위 key가 그대로 리소스 경로가 된다.

api.get('/comment')  // db.json의 "comment" 배열 반환

환경변수로 엔드포인트 분리 (.env)

REACT_APP_BACKEND_ENDPOINT=http://localhost:5000

서버 주소를 코드 밖으로 뺀다. 주소가 바뀌어도 이 한 줄만 수정.

process.env.REACT_APP_BACKEND_ENDPOINT  // "http://localhost:5000"
  • CRA는 이름이 REACT_APP_으로 시작해야 인식됨 (Vite는 VITE_)
  • 빌드 시점에 값이 박히므로 수정 후 개발 서버 재시작 필요
  • 브라우저에 그대로 노출됨 → 비밀키 금지

axios 인스턴스 생성 (api/axios.js)

import axios from 'axios';

const endPoint = process.env.REACT_APP_BACKEND_ENDPOINT;
const api = axios.create({
    baseURL : endPoint
});

export default api;

baseURL을 미리 심어둔 인스턴스를 만들어 export.

api.get('/comment')  // → http://localhost:5000/comment

공통 헤더나 인터셉터도 여기 한 곳에서 관리 가능

Button Component

같은 인터페이스(title, onClick)를 styled-components와 MUI 두 방식으로 실습했다.

props로 title/onClick 전달

// ButtonPage.jsx (부모)
<MaterialButton
    title='글 작성하기'
    onClick={(e) => saveHandler()}/>

부모가 표시할 값(title)과 실행할 함수(onClick)를 함께 내려준다.

// MaterialButton.jsx (자식)
<Button onClick={props.onClick}>{props.title}</Button>

styled-components (Button.jsx)

npm install styled-components
import Styled from "styled-components";

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

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

Styled.태그명 + 백틱 안에 CSS를 쓰면 스타일이 입혀진 컴포넌트가 만들어진다. 클래스명을 직접 짓거나 css 파일을 따로 관리할 필요가 없다.

MUI (MaterialButton.jsx)

npm install @mui/material @emotion/react @emotion/styled
import Button from '@mui/material/Button';

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

이미 디자인된 컴포넌트를 가져다 쓴다. 직접 CSS를 작성하지 않아도 됨.

MUI 컴포넌트를 Button으로 import했기 때문에 래퍼 이름은 MaterialButton으로 구분. 한 파일에서 둘 다 쓰려면 import { Button as MuiButton }처럼 별칭이 필요하다.

상태와 렌더링 (CapacityPage)

입장/퇴장 버튼으로 인원수를 증감시키는 실습. components/styled/Button.jsx 사용.

State와 생명주기

State = 컴포넌트가 가지는 상태/데이터.

클래스 컴포넌트의 생명주기:

출생          인생         사망
Mounting  →  Updating  →  Unmounting

State는 직접 수정할 수 없다(하면 안 된다). 변경 함수를 통해야 React가 변경을 인지하고 재렌더링한다.

Hooks

use로 시작하는 함수 == Hook.

  • state 관련
  • lifecycle 관련
  • 최적화 관련

클래스 기반으로 설계하지 않아도(function component) state와 lifecycle 관리가 가능해졌다.

useState — 변경 시 재렌더링되는 값

// let cnt = 0;  → 값이 바뀌어도 화면이 갱신되지 않음
let [cnt, setCnt] = useState(0);

일반 변수는 렌더링될 때마다 초기화되고, 값이 바뀌어도 React가 감지하지 못한다. useState로 관리해야 setCnt 호출 시 재렌더링이 일어난다.

const upCntHandler = (e) => {
    setCnt( cnt => cnt + 1 );
}

useEffect — 렌더링 이후 실행되는 작업

useEffect(이펙트 함수, 의존성 배열);

렌더링 이후 side-effect로 sync를 맞추는 용도. 두 번째 인자의 값이 바뀔 때마다 함수가 다시 실행된다.

useEffect( () => {
    console.log(`debug >>>> side effect render cnt ${cnt}`);
}, [cnt]);
의존성 배열실행 시점
[]최초 1회만 (마운트 시)
[cnt]최초 + cnt가 바뀔 때마다
생략렌더링될 때마다

의존성 배열로 실행 시점을 조절해 클래스 컴포넌트의 생명주기(Mounting/Updating/Unmounting)에 해당하는 작업을 처리할 수 있다.

실습 중 이슈

버튼 클릭 시마다 컴포넌트 함수 전체가 재실행됨
컴포넌트는 렌더링될 때마다 처음부터 다시 실행되는 함수다. 상태가 바뀌면 React가 CapacityPage()를 통째로 다시 호출해 새 JSX를 받아온다.

첫 클릭에 cnt 1이 찍힘

3. 문제와 해결

JSON 문법

[
    "comment"
    {
        "writer"  : "임정섭",
        "comment" : "강사님과 함께하는 즐거운 React..."
    }
]

문법 이해를 못했다.

  • 수정
{
    "comment": [
        {
            "writer": "임정섭",
            "comment": "강사님과 함께하는 즐거운 React..."
        }
    ]
}

props 객체

const Comment = (data) => {
    ... {data.writer} ... {data.comment}
}
  • 매개변수 이름을 data로 지었다고 해서 그 안의 data 속성이 꺼내지는 게 아니라, 그냥 props 객체 전체에 data라는 이름표를 붙인 것뿐임. 그래서 data.writer는 props.data.writer가 아니라 props.writer를 찾는 셈이고, 그런 건 없으니 undefined → 화면에 아무것도 안 보이는 문제 발생

  • 해결

1. 그냥 props 쓰기

const Comment = (props) => {
    ... {props.data.writer} ... {props.data.comment}
}

2. 구조분해

const Comment = ({ data }) => {
    ... {data.writer} ...   // 기존 코드 그대로 동작
}
profile
한량

0개의 댓글