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>;
}
👉 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 |
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다운 방식
- 하지만 파일 업로드, 오래된 폼 라이브러리 연동처럼 복잡한 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로 전달 |
-
동기화 문제
여러 컴포넌트에서 상태를 변경할 수 있기 때문에, 상태 변경이 제대로 동기화되지 않으면 의도치 않은 오류가 발생할 수 있음 -
복잡성 증가
상태 관리 로직이 복잡해질 수 있음. 특히 상태가 많아질수록 이를 관리하는 코드가 많아지기 때문에 체계적인 관리가 필요 -
성능 문제
공유 상태를 너무 자주 업데이트하거나 너무 많은 컴포넌트가 구독하게 되면 렌더링 성능에 영향을 미칠 수 있음
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 API와 Redux는 리액트 생태계에서 자주 사용되는 방식이며,
상태 끌어올리기는 기본적으로 두 컴포넌트 사이에서 상태를 공유할 때 자주 쓰임
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) 리뷰로 작성 되었습니다.
'학습일지 > K-Digital Traing' 카테고리의 다른 글
| [KDT] AIaaS 마스터클래스 9주차 - Python 입문 (0) | 2025.05.19 |
|---|---|
| [KDT] AIaaS 마스터클래스 8주차 - 리액트 상태관리와 Redux (1) | 2025.05.16 |
| [KDT] AIaaS 마스터클래스 8주차 - 리액트 Hook (1) | 2025.05.14 |
| [KDT] AIaaS 마스터클래스 8주차 - React 학습 (0) | 2025.05.12 |
| [KDT] AIaaS 마스터클래스 7주차 - NC Dinos 홈페이지의 버그 고쳐보기 (0) | 2025.05.07 |