학습일지/K-Digital Traing

[KDT] AIaaS 마스터클래스 8주차 - 이벤트 핸들링과 상태(state)

tierr 2025. 5. 15. 17:49

1. 이벤트 핸들링

사용자가 웹 페이지에서 무언가 행동을 했을 때(클릭, 입력, 키보드 입력 등)
이에 반응하여 특정 동작(함수)을 수행하는 것

 

예를 들어:

  • 버튼을 클릭하면 알림창이 뜨고, 입력창에 뭔가 입력하면 상태(state)가 바뀌는 것 등

1-1. 이벤트 핸들링 HTML vs React

HTML 방식

<button onclick="alert('clicked')">Click</button>
 

React 방식

<button onClick={() => alert('clicked')}>Click</button>
 

🚨 차이점

항목 HTML React
이벤트 이름 소문자 (onclick) 카멜 표기법 (onClick)
함수 전달 방식 문자열 함수 (function)

1-2. React의 이벤트 핸들링 방식

✅ 함수 따로 선언해서 핸들링하기

function MyButton() {
  const handleClick = () => {
    alert('버튼을 눌렀습니다!');
  };

  return <button onClick={handleClick}>클릭</button>;
}​
 
👉 handleClick 함수는 버튼이 클릭될 때만 실행된다.

👉 onClick={handleClick} 이렇게 함수 이름만 전달하는 게 중요하다.

 

✅ 이벤트 객체 받기 (event 객체)

이벤트 발생 시, React는 SyntheticEvent라는 객체를 핸들러에 전달한다.

function InputBox() {
  const handleChange = (event) => {
    console.log(event.target.value);
  };

  return <input type="text" onChange={handleChange} />;
}​
 

➡️ event.target.value를 통해 입력된 값을 가져올 수 있다.


1-3. 자주 쓰이는 이벤트 종류

이벤트 종류 설명 예시 속성
클릭 이벤트 클릭할 때 실행 onClick
변경 이벤트 입력값이 바뀔 때 실행 onChange
제출 이벤트 폼이 제출될 때 실행 onSubmit
마우스 이벤트 마우스 올림/내림 등 onMouseEnter, onMouseLeave
키보드 이벤트 키보드 입력 감지 onKeyDown, onKeyUp

1-4. 예제: 입력 값 받아서 출력
import { useState } from 'react';

function GreetingForm() {
  const [name, setName] = useState('');

  const handleChange = (e) => {
    setName(e.target.value);
  };

  const handleSubmit = () => {
    alert(`안녕하세요, ${name}님!`);
  };

  return (
    <div>
      <input type="text" onChange={handleChange} />
      <button onClick={handleSubmit}>인사하기</button>
    </div>
  );
}

 

잘못된 예시: onChange={handleChange()}처럼 함수 호출을 직접 전달하면, 함수가 즉시 실행

올바른 예시: onChange={handleChange}처럼 함수 참조를 전달하여, 이벤트 발생 시에만 함수가 실행되도록 해야 함


1-5. 마무리

  • React에서 이벤트는 onClick, onChange 같은 카멜 표기법 사용
  • 함수는 문자열이 아니라 실제 함수(또는 화살표 함수)를 전달
  • 이벤트 핸들러에서 event 객체를 받아 필요한 정보에 접근 가능

2. 리스트와 키

2-1. 배열 (Array)와 리스트 (List)

배열(Array)

JavaScript의 기본 자료형 중 하나로, 여러 개의 값을 순서대로 저장하는 구조
const fruits = ['사과', '바나나', '포도'];​
 
 

리스트(List)

React에서 배열의 각 요소를 화면에 렌더링할 때 사용하는 표현
즉, 배열을 .map() 등을 이용해서 JSX 요소들의 나열로 바꾼 것이 리스트라고 생각하면 된다

 

예) React에서 배열을 렌더링하기

const fruits = ['사과', '바나나', '포도'];

function FruitList() {
  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}

 

참고)  .map() 함수

더보기
더보기

.map() 함수란

배열의 각 요소에 대해 콜백 함수를 실행하고, 그 결과로 새로운 배열을 반환하는 함수

 

✅ 기본 문법

const newArray = originalArray.map((item, index, array) => {
  return 변환된_값;
});

파라미터 설명
item 현재 요소
index 현재 요소의 인덱스
array 원래 배열 자체
 

✅ 간단 예제

const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);

console.log(doubled); // [2, 4, 6]

 

✅ React에서 사용하는 예제

const fruits = ['사과', '바나나', '포도'];

function FruitList() {
  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}

여기서 map()을 사용해 fruits 배열의 값을 <li> 태그로 바꾸고 있다.

 

✅ 객체 배열을 map으로 처리하는 예제

const users = [
  { id: 1, name: '한글' },
  { id: 2, name: '영어' },
];

function UserList() {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

여기서는 key={user.id}처럼 고유한 값을 key로 설정해서 더 안전하게 렌더링하고 있다.

 

✅ 정리

map 특징 설명
원본 배열 유지 기존 배열을 변경하지 않고 새 배열을 만듦
콜백함수 사용 각 요소를 원하는 방식으로 변환 가능
React에서 필수 JSX 요소를 반복 렌더링할 때 자주 사용

2-2. key란?

React가 리스트를 렌더링할 때, 각 요소를 고유하게 식별할 수 있도록 해주는 속성

 

📌 왜 필요한가?

React는 렌더링 성능을 높이기 위해 가상 DOM에서 변경된 항목만 업데이트하려고 하는 상황을 가정해보자.
이때 key가 있어야 어떤 항목이 변경/추가/삭제되었는지 정확하게 판단할 수 있다.

 

✅ 좋은 key란?

좋음 나쁨
고유한 ID 인덱스 사용(index)
// 추천 (고유한 id가 있다면!)
[
  { id: 1, name: '사과' },
  { id: 2, name: '바나나' },
].map((item) => (
  <li key={item.id}>{item.name}</li>
))

// 가능하지만 추천X (배열 변경 시 문제 생길 수 있음)
fruits.map((fruit, index) => (
  <li key={index}>{fruit}</li>
))

 

✅ 정리

용어 의미
배열 JS에서 값들을 순서대로 저장하는 자료 구조
리스트 React에서 배열을 .map()으로 JSX 요소로 변환한 것
key React가 리스트 요소를 구분할 수 있도록 부여하는 고유한 값

3. 폼 (Form)

form은 사용자 입력을 받는 HTML 요소들의 집합이고, React에서는 이를 상태(state)와 함께 관리한다.

 

3-1. HTML 폼 vs React 폼 비교

기본 HTML 폼 구조

 

<form>
  <input type="text" />
  <button type="submit">제출</button>
</form>​

 

React에서의 폼 사용법

React에서는 사용자의 입력 값을 실시간으로 관리하려면 useState 훅을 사용해서 상태를 업데이트해야 한다.

// 예제: 이름을 입력받고 제출 시 경고창 띄우기
import { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  // 입력 값 변경 시 호출
  const handleChange = (e) => {
    setName(e.target.value);
  };

  // 폼 제출 시 호출
  const handleSubmit = (e) => {
    e.preventDefault(); // 기본 제출 동작(새로고침) 막기
    alert(`입력한 이름: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={name} onChange={handleChange} />
      <button type="submit">제출</button>
    </form>
  );
}

 

참고) 여러 개 입력값 처리하는 예제

더보기
더보기
function LoginForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('제출된 데이터:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" value={formData.username} onChange={handleChange} />
      <input name="password" type="password" value={formData.password} onChange={handleChange} />
      <button type="submit">로그인</button>
    </form>
  );
}​
 
👉 이런 방식은 입력 필드가 많을 때 유용해.

3-2. 폼에서 자주 사용하는 이벤트

이벤트 설명
onChange 입력 값이 바뀔 때마다 발생
ex) <input type="text" onChange={(e) => setValue(e.target.value)} />
onSubmit 폼이 제출될 때 발생 
ex) <form onSubmit={(e) => handleSubmit(e)} />
onBlur 사용자가 입력 필드를 벗어날 때 발생 (유효성 검사 등)
ex) <input type="text" onBlur={() => validateInput()} />

3-3. 제어 컴포넌트와 비제어 컴포넌트란?

React에서는 입력값을 다룰 때 제어 컴포넌트비제어 컴포넌트의 두 가지 방식이 있다.
이 둘의 차이를 이해하는 건 폼 처리의 핵심 개념 중 하나

 

✅ 제어 컴포넌트 (Controlled Component)

입력값을 React의 상태(state)가 직접 관리하는 방식
  • useState로 상태 선언
  • value 속성과 onChange 핸들러 사용
  • 항상 React가 입력값을 기억하고 있음
import { useState } from 'react';

function ControlledInput() {
  const [text, setText] = useState('');

  return (
    <input
      type="text"
      value={text}
      onChange={(e) => setText(e.target.value)}
    />
  );
}

 

👉 사용자가 입력한 값은 text라는 state에 저장됨.
👉 이 컴포넌트는 완전히 React에 의해 제어됨.


✅ 비제어 컴포넌트 (Uncontrolled Component)

입력값을 DOM 자체가 관리하는 방식으로, React는 나중에 필요할 때만 값 접근한다.
  • 상태(state)를 사용하지 않음
  • 대신 ref를 사용해서 DOM에서 직접 값 추출
  • 초기 렌더링만 React가 관여함
import { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null);

  const handleClick = () => {
    alert(`입력한 값: ${inputRef.current.value}`);
  };

  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>확인</button>
    </>
  );
}

👉 React는 직접 값의 변경을 추적하지 않음.
👉 클릭 시 DOM에서 ref를 통해 값을 가져옴.


3-4. 제어 컴포넌트 vs 비제어 컴포넌트 비교

✅ 비교 요약

항목 제어 컴포넌트 비제어 컴포넌트
값 관리 React state로 관리 DOM 내부에서 자체 관리
value 사용 여부 사용함 사용하지 않음
상태 추적 실시간으로 추적 가능 직접 접근(필요 시 ref 사용)
초기값 설정 state로 설정 defaultValue 속성 사용
대표 Hook useState useRef
장점 데이터 흐름이 명확, 검증·조건 처리 쉬움 단순한 경우 코드 간결, 퍼포먼스 좋을 수 있음
 

✅ 언제 써야 할까?

상황 추천 방식
실시간 검증, 조건부 렌더링 필요 제어 컴포넌트
외부 라이브러리 사용, 성능 최적화, 초기값만 중요 비제어 컴포넌트

 

✅ 5. value 속성 vs defaultValue

속성 설명
value 제어 컴포넌트 (state와 연결)
defaultValue 비제어 컴포넌트 (초기값만 설정됨)
React에서는 일반적으로 value와 onChange를 함께 사용해서 상태를 직접 제어하는 방식(제어 컴포넌트)을 많이 쓴다.
 
 

✅ 결론

  • 대부분의 경우 제어 컴포넌트가 더 직관적이고 React다운 방식
  • 하지만 파일 업로드, 오래된 폼 라이브러리 연동처럼 복잡한 DOM 조작이 필요한 경우 비제어 컴포넌트도 유용함

3-5. 폼(Form) 마무리

  • 폼(Form) = 사용자 입력을 받는 UI 구성
  • React에서는 useState로 상태를 저장하고, onChange로 실시간 변경 반영
  • onSubmit으로 폼 제출 처리
  • 여러 입력 필드를 관리할 땐 name 속성을 기준으로 상태 업데이트

4. Shared State

4-1. Shared State (공유 상태) 란?

둘 이상의 컴포넌트가 같은 데이터를 사용해야 할 때, 이 데이터를 공통 부모 컴포넌트에 두고 자식에게 전달하는 것
즉, 컴포넌트 간에 "상태(state)를 공유"하고 싶을 때 사용하는 패턴이다.

 

✅ 왜 필요한가?

React는 단방향 데이터 흐름(one-way data flow) 구조를 가지고 있어서 자식 → 부모로 직접 데이터를 넘길 수는 없다.
이럴 때 상태를 부모에 올려서 필요한 컴포넌트에 props로 내려주는 방식이 필요하다.

 

✅ 예제: 두 개의 입력창이 같은 값을 공유할 때

import { useState } from 'react';

function Parent() {
  const [text, setText] = useState('');

  return (
    <>
      <InputA text={text} setText={setText} />
      <InputB text={text} setText={setText} />
    </>
  );
}

function InputA({ text, setText }) {
  return (
    <input value={text} onChange={(e) => setText(e.target.value)} />
  );
}

function InputB({ text, setText }) {
  return (
    <input value={text} onChange={(e) => setText(e.target.value)} />
  );
}

결과:

  • 둘 중 하나의 인풋을 수정하면 두 컴포넌트 모두 값이 함께 바뀜
  • 이게 바로 공통 상태 = Shared State

✅ 정리

 

개념 설명
Shared State 여러 컴포넌트가 같은 상태 값을 공유하는 것
왜 필요한가? React는 상향 데이터 흐름이 없기 때문에 부모에서 상태를 관리해야 함
구현 방법 상태를 부모 컴포넌트에 만들고, 자식 컴포넌트에 props로 전달
⚠️  Shared State 관리 시 주의할 점
  • 동기화 문제
    여러 컴포넌트에서 상태를 변경할 수 있기 때문에, 상태 변경이 제대로 동기화되지 않으면 의도치 않은 오류가 발생할 수 있음
  • 복잡성 증가
    상태 관리 로직이 복잡해질 수 있음. 특히 상태가 많아질수록 이를 관리하는 코드가 많아지기 때문에 체계적인 관리가 필요
  • 성능 문제
    공유 상태를 너무 자주 업데이트하거나 너무 많은 컴포넌트가 구독하게 되면 렌더링 성능에 영향을 미칠 수 있음

4-2. Shared State 구현 방식 ( State Lifting vs Context API vs Redux )

1. State Lifting (상태 끌어올리기)

둘 이상의 자식 컴포넌트가 같은 데이터를 필요로 할 때, 공통 부모로 상태를 올려서 관리하는 방식
[Parent]
 ├── [ChildA] ← 상태 필요
 └── [ChildB] ← 상태 필요​
 
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <ChildA count={count} />
      <ChildB setCount={setCount} />
    </>
  );
} 

✅ 장점

  • 매우 간단하고 기본적인 공유 방식
  • 외부 도구 없이 가능

❌ 단점

  • 트리 깊어지면 props 전달이 복잡해짐 (prop drilling 발생)
  • 중첩 컴포넌트가 많아질수록 유지보수 어려움

2. Context API

전역 상태 관리를 React에서 지원하는 방식
Provider로 데이터를 감싸고, 하위 컴포넌트에서 useContext로 접근
<UserContext.Provider value={user}>
  └── [App]
      ├── [Header]
      └── [Sidebar]
const UserContext = React.createContext();
const App = () => (
  <UserContext.Provider value={{ name: '정인' }}>
    <Profile />
  </UserContext.Provider>
);

function Profile() {
  const user = useContext(UserContext);
  return <p>{user.name}님 안녕하세요</p>;
}
 

✅ 장점

  • prop drilling 없이 트리 하위에 데이터 전달 가능
  • 내장 기능 (추가 설치 불필요)

❌ 단점

  • 상태가 커지고 많아지면 유지보수 어려움
  • 여러 context가 섞이면 복잡함
  • 업데이트될 때 하위 컴포넌트가 모두 리렌더링될 수 있음 (성능 이슈)

3. Redux (Redux Toolkit)

앱 전체에서 사용할 수 있는 단일 전역 상태 저장소(store)
상태를 액션 → 리듀서 → 스토어 방식으로 변경

 

✅ 장점

  • 상태 변경 흐름이 명확하고 구조적
  • 컴포넌트 간의 복잡한 상태 공유가 쉬움
  • Redux DevTools 등 디버깅 도구 뛰어남
  • 성능 최적화 (useSelector, memo) 용이

❌ 단점

  • 초기 설정이 복잡할 수 있음 (RTK로 많이 단순해졌지만)
  • 규모가 작은 프로젝트에는 오버엔지니어링이 될 수 있음

4-3. 구현 방식 비교 결과 요약

✅ 비교 요약

항목 State Lifting Context API Redux
설치 필요 ❌ 없음 ❌ 없음 ✅ 필요 (@reduxjs/toolkit)
사용 난이도 ⭐ 매우 쉬움 ⭐⭐ 쉬움 ⭐⭐⭐ 중~어려움
상태 사용 범위 컴포넌트 간 전역 전역
성능 최적화 수동 관리 어려움 용이 (selector, memo 등)
디버깅 도구 없음 없음 있음 (DevTools)
언제 사용하나 작은 규모, 형제끼리 로그인, 테마 등 전역 상태 대규모 상태/복잡한 앱

✅ 선택 기준

상황 추천 방법
두 자식끼리 간단한 값 공유 State Lifting
로그인 정보, 테마처럼 앱 전역에서 필요한 값 Context API
복잡한 상태, 다양한 컴포넌트에서 읽고 수정 Redux (Redux Toolkit)

 

Context APIRedux는 리액트 생태계에서 자주 사용되는 방식이며,

상태 끌어올리기는 기본적으로 두 컴포넌트 사이에서 상태를 공유할 때 자주 쓰임


5. 합성 (Composition) & 상속 (Inheritance)

React는 전통적인 객체지향 프로그래밍과 달리, 상속보다는 합성을 권장하는 구조임

5-1. 합성(Composition)이란?

여러 컴포넌트를 조합해서 하나의 컴포넌트를 구성하는 방식
즉, 컴포넌트를 다른 컴포넌트의 children 또는 props로 넘겨서 유연하게 재사용하는 구조
function WelcomeMessage(props) {
  return <div className="welcome">{props.children}</div>;
}

function App() {
  return (
    <WelcomeMessage>
      <p>안녕하세요! 정인님 :)</p>
    </WelcomeMessage>
  );
}
 

✅ 여기서 WelcomeMessage는 children을 받아서 내부에 보여줌
👉 이런 방식이 바로 합성

 

📌 다른 예제 (props를 통한 컴포넌트 조합)

function Dialog({ title, message }) {
  return (
    <div className="dialog">
      <h1>{title}</h1>
      <p>{message}</p>
    </div>
  );
}

function WelcomeDialog() {
  return <Dialog title="환영합니다" message="정인님 어서오세요!" />;
} 

✅ Dialog라는 범용 컴포넌트를 만들고, 그걸 조합해서 WelcomeDialog를 만든 형태도 합성의 한 예


5-2. 상속(Inheritance)이란?

하나의 클래스가 다른 클래스를 확장(extends)하여 재사용하는 전통적인 OOP 방식.

 

★ React에서도 class 컴포넌트를 사용할 수 있지만, React는 상속을 지양하고, 합성을 사용하라고 권장

 

⚠️  왜 React는 상속을 피하라고 할까?

이유 설명
유연하지 않음 상속은 고정된 계층 구조라 변경이 어렵고 재사용성이 떨어짐
깊은 계층 문제 상속이 깊어질수록 유지보수가 어려워짐
합성이 더 간단 props와 children으로 컴포넌트를 쉽게 조립 가능

비교 요약

항목 합성 (Composition) 상속 (Inheritance)
방식 컴포넌트를 props나 children으로 조립 class를 확장하여 기능 추가
React 권장 ✅ Yes ❌ No (지양함)
재사용성 매우 높음 구조 변경에 약함
예시 <MyComponent>{child}</MyComponent> class B extends A

결론

  • React는 "구성(조립형) UI"를 지향함 → 합성이 자연스럽고 강력함
  • 클래스 상속보다는 props, children, hook, HOC, render props 등을 통해 재사용

5-3. React 컴포넌트 재사용 패턴 : Containment (포함) 패턴

하나의 컴포넌트가 children props를 통해 다양한 내용을 포함할 수 있도록 만드는 패턴
function Container(props) {
  return <div className="container">{props.children}</div>;
}

function App() {
  return (
    <Container>
      <h1>안녕하세요</h1>
      <p>정인님, 오늘도 좋은 하루!</p>
    </Container>
  );
}

✅ Container는 어떤 자식 요소가 오든 그걸 감싸는 역할을 함
✅ <Container>가 무엇을 감쌀지 모를 때 유용


5-4. React 컴포넌트 재사용 패턴 : Specialization (특수화) 패턴

범용 컴포넌트를 구체적인 역할로 확장하여 더 특정한 기능을 하는 컴포넌트를 만드는 방식
function Dialog({ title, message }) {
  return (
    <div className="dialog">
      <h1>{title}</h1>
      <p>{message}</p>
    </div>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="환영합니다"
      message="정인님, 가입을 축하합니다!"
    />
  );
}

✅ Dialog는 재사용 가능한 범용 컴포넌트
✅ WelcomeDialog는 그걸 기반으로 **특수한 목적(Welcome)**에 맞게 확장한 것


5-4. Containment (포함) 패턴 vs Specialization (특수화) 패턴

✅ 비교 요약

항목 Containment (포함) Specialization (특수화)
개념 다양한 자식 요소를 감쌈 범용 컴포넌트를 구체화함
핵심 기술 props.children 사용 props로 특정 기능만 전달
유연성 자식 요소가 자유로움 역할이 고정되어 있음
예시 <Container>{...}</Container> <WelcomeDialog />는 <Dialog /> 확장
사용 시점 내부 콘텐츠가 다양할 때 명확한 기능 차이를 구분하고 싶을 때
 

✅ 실제로는 두 패턴을 섞어 쓰는 경우도 많다

function Dialog({ title, children }) {
  return (
    <div className="dialog">
      <h1>{title}</h1>
      <div>{children}</div>
    </div>
  );
}

function SignUpDialog() {
  return (
    <Dialog title="회원가입">
      <input placeholder="이름을 입력하세요" />
      <button>가입</button>
    </Dialog>
  );
}
  • Dialog는 children을 받는 ➝ Containment
  • SignUpDialog는 Dialog를 구체화 ➝ Specialization

✅ 결론

상황 추천 패턴
내부 콘텐츠가 다양할 수 있음 Containment
재사용 가능한 틀을 구체화함 Specialization

6. 리액트 스타일링

React에서 스타일을 적용하는 다양한 방법을 정리해보자.
React는 HTML/CSS와 다르게 컴포넌트 기반 구조이기 때문에, 스타일링도 그에 맞게 여러 가지 방식이 있다.


6-1. 인라인 스타일링 (Inline styles)

 

JSX 요소에 직접 style 속성을 객체 형태로 전달

 

function Box() {
  const style = {
    backgroundColor: 'skyblue',
    padding: '20px',
    borderRadius: '10px',
  };

  return <div style={style}>안녕하세요!</div>;
}

📌 특징

  • 간단하고 빠르게 적용 가능
  • CSS 속성은 카멜 표기법(background-color → backgroundColor)
  • 단점: :hover, media query, 클래스 재사용 불가

6-2. 클래스 스타일링 (CSS 파일 import)

 

일반 CSS 파일을 만들어 컴포넌트에 className으로 적용

 

/* styles.css */
.box {
  background-color: skyblue;
  padding: 20px;
  border-radius: 10px;
}
import './styles.css';

function Box() {
  return <div className="box">안녕하세요!</div>;
}

📌 특징

  • 전통적인 방식, 익숙함
  • class 대신 className 사용
  • 전역 CSS라서 클래스명이 겹칠 위험 있음

6-3. CSS Modules (파일별로 고립된 스타일)

 

.module.css 확장자를 사용해서 CSS를 컴포넌트 단위로 스코프 분리

 

/* Box.module.css */
.box {
  background-color: skyblue;
  padding: 20px;
}​
import styles from './Box.module.css';

function Box() {
  return <div className={styles.box}>안녕하세요!</div>;
}

📌 특징

  • 클래스명이 자동으로 고유화됨 (box_abc123)
  • 컴포넌트 단위의 CSS 분리 가능
  • 충돌 위험 없음 → 권장 방식 중 하나

6-4. Styled-components (CSS-in-JS)

 

styled-components 라이브러리를 활용해 JS 내부에서 CSS 작성

 

참고) 설치 방법
npm install styled-components​
 
활용 예제
import styled from 'styled-components';

const Box = styled.div`
  background-color: skyblue;
  padding: 20px;
  border-radius: 10px;
`;

function App() {
  return <Box>안녕하세요!</Box>;
}

📌 특징

  • 자바스크립트 안에서 CSS를 작성
  • props를 활용한 동적 스타일 가능
  • 컴포넌트화된 CSS, 유지보수에 유리
  • 단점: 런타임 성능 이슈가 있을 수 있음

6-5. Tailwind CSS (유틸리티-first CSS 프레임워크)

 

미리 정의된 클래스명 조합으로 빠르게 스타일링

 

npm install -D tailwindcss
npx tailwindcss init
function Box() {
  return (
    <div className="bg-sky-400 p-5 rounded-lg text-white">
      안녕하세요!
    </div>
  );
}

📌 특징

  • 빠르게 UI를 만들 수 있음
  • 정해진 클래스만 사용 → 일관된 스타일 유지
  • 유지보수 쉽지만 초기 학습 필요

6-6.  SASS & SCSS란?

CSS를 더 편하게 작성하기 위한 CSS 전처리기(Preprocessor)

  • 변수, 중첩, 믹스인, 상속 등 프로그래밍적인 문법을 제공
  • 최종적으로는 일반 CSS로 컴파일됨

📌 SASS 와 SCSS

구분 설명
.sass 들여쓰기 기반 문법, 중괄호/세미콜론 없음, 이제 사용 거의 안함
.scss CSS와 거의 동일한 문법, 중괄호/세미콜론 사용
✅ 추천 대부분은 SCSS를 사용 — 익숙하고 오류 적음

📌  일반 CSS vs SCSS

항목 일반 CSS SCSS
변수 ❌ 없음 ✅ $color, $size 등 사용 가능
중첩 ❌ 클래스 반복 필요 ✅ 중첩으로 구조 표현 가능
코드 재사용 ❌ 어려움 ✅ mixin, extend 가능

⚖️ SCSS/SASS는?

  • 여전히 사용되긴 하지만, 요즘은 점점 CSS Modules + SCSS 혹은 Tailwind로 넘어가는 추세
  • 즉, SCSS만 쓰는 경우는 이전 스타일링 방식에 가까움

참고) SCSS 주요 문법

더보기
더보기

✅ 1. 변수

$primary-color: #4a90e2;
body {
  background: $primary-color;
}

 

✅ 2. 중첩(Nesting)

.nav {
  ul {
    list-style: none;
  }

  li {
    display: inline-block;
  }
}

 

✅ 3. 믹스인(Mixin)

@mixin rounded($radius) {
  border-radius: $radius;
}

.box {
  @include rounded(10px);
}

 

✅ 4. 상속(@extend)

%button-base {
  padding: 10px;
  font-weight: bold;
}

.btn {
  @extend %button-base;
  background: blue;
}

 

 

참고) React 프로젝트에서 SCSS 사용 방법

더보기
더보기

1️⃣ 설치

npm install sass

 

2️⃣ 파일 생성

// styles/App.scss
$main-color: #0c2444;

.container {
  background-color: $main-color;
  padding: 20px;

  .title {
    color: white;
    font-size: 1.5rem;
  }
}

 

3️⃣ 컴포넌트에서 import

import './styles/App.scss';

function App() {
  return (
    <div className="container">
      <h1 className="title">안녕하세요!</h1>
    </div>
  );
}

6-7. 각 리액트 스타일링을 비교해보기

스타일링 방식 방법 특징 스타일
충돌 방지
재사용성 추천 용도
Inline Style JSX 내 style={{}} 객체 빠르고 간단, 동적 스타일에 유리,
CSS 제한 있음
X 낮음 아주 간단한 테스트 컴포넌트
CSS 파일 .css + className 익숙하고 쉬움,
전역 클래스 충돌 가능성
X 중간 빠른 프로토타입,
소규모 프로젝트
CSS Modules .module.css / .module.scss 클래스명 고유화,
컴포넌트 단위 스타일링
O 높음 팀 협업, 컴포넌트 기반 구조
SCSS (SASS) .scss 작성,
CSS 확장 문법 사용
변수, 중첩, 믹스인 가능,
구조화 쉬움
X 높음 커스터마이징 많은 프로젝트
Styled-components JS 내 styled.div 등 사용 동적/조건부 스타일,
컴포넌트와 스타일 통합
O 높음 디자인 시스템 구축 프로젝트
Tailwind CSS className에 유틸리티 클래스 나열 빠름, 일관성,
반응형/다크모드 내장
O 낮음 빠른 UI 제작, 실무 대세,
팀 개발 환경
 

✅ 결론

  • 빠르게 만들고 싶다 → Tailwind CSS
  • 컴포넌트 단위로 깔끔하게 분리하고 싶다 → CSS Modules
  • CSS도 코드처럼 다루고 싶다 → styled-components

참고)  2025년 기준, 스타일링 대세 TOP 3

더보기
더보기
순위 스타일 방식 설명
1️⃣ Tailwind CSS 빠르고 일관된 UI 개발이 가능. 대형 기업/스타트업에서 빠르게 성장 중
2️⃣ CSS Modules CRA, Vite, Next.js 등 기본 제공. 안정적이고 충돌 없음
3️⃣ Styled-components 동적 스타일링/컴포넌트화에 유리. 대형 프로젝트나 디자이너 협업에 적합

 

🥇 1. Tailwind CSS (대세 중 대세)

  • 장점:
    • 빠른 UI 제작
    • 클래스명으로만 스타일 제어 → 유지보수 쉬움
    • 다크모드, 반응형, hover 등 내장됨
  • 단점:
    • 클래스가 길어짐 (처음엔 보기 복잡)
    • CSS 문법 대신 유틸리티 학습 필요

 스타트업, 토이 프로젝트, 팀 개발에서 인기 폭발


🥈 2. CSS Modules

  • 장점:
    • CSS 충돌 방지 (클래스 자동 유니크 처리)
    • 기존 CSS 문법 그대로 사용 가능
    • 도입이 쉽고 가볍다
  • 단점:
    • 동적 스타일링은 불편함

 보수적인 팀, 적당한 규모의 프로젝트에 안정적으로 사용


🥉 3. Styled-components (CSS-in-JS)

  • 장점:
    • 컴포넌트와 스타일이 1:1 매칭
    • props 기반 동적 스타일링 용이
    • theme 지원 (디자인 시스템 연동에 적합)
  • 단점:
    • 런타임 성능 이슈 (매우 크진 않음)
    • 스타일이 JS 안에 있어 선호 안 하는 팀도 있음

 디자인 시스템, 대형 프로젝트, 프론트 중심 팀에서 여전히 많이 사용

 

수업) React 주요 개념 조사하고 학습일지 정리하기

1. 쓰로틀링(throttling)과 디바운싱(debouncing) 에 대해서 알아보기

 

쓰로틀링(Throttling)

일정 시간 간격으로만 함수를 실행하도록 속도를 제한하는 기법

  • 무조건 일정 주기로 실행됨 (중간에 몇 번 발생해도 무시)
  • 이벤트가 빈번해도 너무 자주 실행되지 않도록 제한

📌 언제 쓰나?

상황 예시
스크롤, 리사이즈 등 연속적으로 발생하는 이벤트 무한 스크롤, 윈도우 크기 조절 등
API 호출을 주기적으로 제한하고 싶을 때 초당 1회 이상 호출 금지 등

 

📌 예제 (lodash 사용)

import throttle from 'lodash/throttle';

const handleScroll = throttle(() => {
  console.log('스크롤 발생!');
}, 1000); // 1초에 한 번만 실행

디바운싱(Debouncing)

이벤트 발생이 멈춘 후 일정 시간이 지나면 한 번만 실행

  • 계속 발생하면 실행되지 않음,
  • 이벤트가 멈춘 뒤 일정 시간 후에 실행됨

📌 언제 쓰나?

상황 예시
입력 중에는 실행하지 않고, 입력이 끝난 후 한 번만 실행 검색어 자동완성, 실시간 필터링 등
불필요한 이벤트 낭비 방지 resize, keyup 등
 

📌 예제 (lodash 사용)

import debounce from 'lodash/debounce';

const handleInput = debounce((e) => {
  console.log('검색어:', e.target.value);
}, 500); // 입력 멈춘 뒤 0.5초 후 실행

  쓰로틀링 vs 디바운싱 비교

항목 Throttling (쓰로틀링) Debouncing (디바운싱)
실행 시점 일정 주기마다 실행 이벤트가 끝난 후 한 번만 실행
이벤트 중복 시 중간 이벤트 무시 마지막 이벤트만 실행
사용 예 스크롤, 리사이즈, API 주기 제한 검색창 입력, 자동완성, 창 크기 변경 후 처리
대표 함수 throttle(fn, delay) debounce(fn, delay)
 

✅ 결론

  • scroll, resize 등 계속 발생하는 이벤트 → Throttle
  • input, keyup 등 사용자 행동이 멈춘 뒤 처리 → Debounce

2. React 스타일링 방식(CSS 모듈, CSS-in-JS 등)에 대해서 정리하기

- [6. 리액트 스타일링] 항목 참고


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.