프론트엔드 스터디 11주차: React 기초 2 —.
useEffect로 사이드 이펙트를 다루고, 이벤트 처리와 폼 상태 관리까지 — React로 실제 동작하는 앱을 만들기 위한 핵심 기술을 배웁니다.
<strong>학습 구간:</strong> [01:36:21 ~ 02:59:19] 사용자 입력(Form)부터 CSS
선택자 규칙까지
<strong>강의 바로가기:</strong>
---
---
<form action="/login" method="POST">
<label for="user-email">이메일</label>
<input id="user-email" name="email" type="email" />
<label for="user-pw">비밀번호</label>
<input id="user-pw" name="password" type="password" />
<button type="submit">로그인</button>
</form><!-- 검색: GET이 적합 -->
<form action="/search" method="GET">
<input name="q" type="text" placeholder="검색어 입력" />
<button type="submit">검색</button>
</form>
<!-- 로그인: POST가 적합 -->
<form action="/login" method="POST">
<input name="email" type="email" />
<input name="password" type="password" />
<button type="submit">로그인</button>
</form><strong>URL 공유가 가능합니다.</strong> GET으로 검색하면 ?q=맛집처럼 검색
조건이 URL에 담기기 때문에, 그 URL을 친구에게 보내면 똑같은 검색 결과를 볼 수 있습니다. POST는 URL에 정보가 없어서 링크 공유가 불가능합니다.
<strong>브라우저 뒤로가기·새로고침이 자연스럽습니다.</strong> GET 요청은 뒤로
가기를 누르면 이전 결과가 바로 나타납니다. 반면 POST는 "양식을 다시 제출하시겠습니까?" 라는 확인 팝업이 뜹니다.
<strong>북마크(즐겨찾기)가 가능합니다.</strong> 검색 결과나 필터링 결과를
즐겨찾기에 저장해두고 나중에 다시 열 수 있습니다. POST로는 이것이 불가능합니다.
<strong>캐싱이 됩니다.</strong> 브라우저는 GET 요청의 결과를 캐시에
저장해두어, 같은 요청을 반복할 때 더 빠르게 응답할 수 있습니다.
<form action="/register" method="POST">
<fieldset>
<legend>기본 정보</legend>
<label for="reg-name">이름</label>
<input id="reg-name" name="username" type="text" />
<label for="reg-email">이메일</label>
<input id="reg-email" name="email" type="email" />
</fieldset>
<fieldset>
<legend>보안 설정</legend>
<label for="reg-pw">비밀번호</label>
<input id="reg-pw" name="password" type="password" />
<label for="reg-pw-confirm">비밀번호 확인</label>
<input id="reg-pw-confirm" name="password_confirm" type="password" />
</fieldset>
<button type="submit">가입하기</button>
</form><!-- 1. 인라인 스타일: 태그에 직접 작성 -->
<p style="color: red; font-size: 16px;">빨간 글씨</p>
<!-- 2. 내부 스타일 시트: <head> 안에 <style> 태그 사용 -->
<head>
<style>
p {
color: blue;
}
</style>
</head>
<!-- 3. 외부 스타일 시트: 별도 .css 파일을 <link>로 연결 (가장 권장) -->
<head>
<link rel="stylesheet" href="style.css" />
</head>/* 1. 태그 선택자: 해당 태그 전부 */
p {
color: gray;
}
/* 2. 클래스 선택자: 마침표(.)로 시작 */
.highlight {
background-color: yellow;
}
/* 3. 아이디 선택자: 샵(#)으로 시작, 페이지당 하나만 */
#main-title {
font-size: 32px;
}
/* 4. 자손 결합자: 공백으로 내부 요소 선택 */
nav a {
text-decoration: none;
}
/* 5. 자식 결합자: >로 직계 자식만 선택 */
ul > li {
list-style: square;
}
/* 6. 그룹 선택자: 쉼표로 여러 선택자에 같은 스타일 */
h1,
h2,
h3 {
font-family: "Pretendard", sans-serif;
}/* 과거에는 이렇게 브라우저별로 따로 작성해야 했습니다 */
.box {
-webkit-transition: all 0.3s; /* Chrome, Safari */
-moz-transition: all 0.3s; /* Firefox */
-ms-transition: all 0.3s; /* IE, Edge */
-o-transition: all 0.3s; /* Opera */
transition: all 0.3s; /* 표준 */
}<strong>-webkit-</strong>: Chrome, Safari, Edge(Chromium 기반) 등
WebKit/Blink 엔진 브라우저
<strong>-moz-</strong>: Firefox (Gecko 엔진)
<strong>-ms-</strong>: Internet Explorer, 구 Edge
<strong>-o-</strong>: Opera (현재는 Chromium 기반이라 -webkit- 사용)
/* 개발자가 작성하는 코드 */
.box {
user-select: none;
}
/* Autoprefixer가 빌드 시 자동으로 변환 */
.box {
-webkit-user-select: none; /* Safari */
user-select: none;
}/* :root에 변수 정의 (전역) */
:root {
--color-primary: #3b82f6;
--color-gray: #6b7280;
--spacing-md: 16px;
--radius-lg: 12px;
}
/* 변수 사용 */
.button {
background-color: var(--color-primary);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
color: white;
}
.link {
color: var(--color-primary); /* 같은 변수를 재사용 */
}/* 라이트 모드 (기본) */
:root {
--bg-color: #ffffff;
--text-color: #1a1a1a;
}
/* 다크 모드 */
:root[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #f0f0f0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}/* 마우스를 올렸을 때 */
.button:hover {
background-color: #2563eb;
}
/* 클릭하는 순간 */
.button:active {
transform: scale(0.98);
}
/* 키보드 포커스가 갔을 때 (Tab 키로 이동) */
.input:focus {
outline: 2px solid #3b82f6;
border-color: #3b82f6;
}
/* 첫 번째 자식 요소 */
li:first-child {
font-weight: bold;
}
/* 마지막 자식 요소 */
li:last-child {
border-bottom: none;
}
/* 짝수 번째 요소 (표 줄무늬 배경) */
tr:nth-child(even) {
background-color: #f9fafb;
}| 의사 클래스 | 용도 | 예시 |
|---|---|---|
<strong>:hover</strong> | 마우스를 올렸을 때 | 버튼 배경색 변경 |
<strong>:focus</strong> | 포커스가 갔을 때 | input 테두리 강조 |
<strong>:active</strong> | 클릭하는 순간 | 버튼 눌림 효과 |
<strong>:first-child</strong> | 첫 번째 자식 | 목록 첫 항목 스타일 |
<strong>:last-child</strong> | 마지막 자식 | 목록 마지막 구분선 제거 |
<strong>:nth-child(n)</strong> | n번째 자식 | 표 줄무늬 배경 |
<strong>:disabled</strong> | 비활성화된 요소 | 비활성 버튼 회색 처리 |
<strong>:not()</strong> | 특정 조건 제외 | .item:not(:last-child) |
<form action="/register" method="POST">
<!-- 필수 입력 -->
<input name="username" type="text" required placeholder="이름 (필수)" />
<!-- 이메일 형식 자동 검증 -->
<input name="email" type="email" required placeholder="이메일" />
<!-- 최소/최대 글자 수 제한 -->
<input
name="password"
type="password"
required
minlength="8"
maxlength="20"
placeholder="비밀번호 (8~20자)"
/>
<!-- 정규 표현식으로 형식 지정 -->
<input
name="phone"
type="tel"
pattern="[0-9]{3}-[0-9]{4}-[0-9]{4}"
placeholder="010-1234-5678"
title="010-1234-5678 형식으로 입력하세요"
/>
<!-- 숫자 범위 제한 -->
<input name="age" type="number" min="1" max="150" placeholder="나이" />
<button type="submit">가입하기</button>
</form>| 속성 | 용도 | 예시 |
|---|---|---|
<strong>required</strong> | 필수 입력 | 빈칸이면 제출 불가 |
<strong>minlength / maxlength</strong> | 글자 수 제한 | 비밀번호 최소 8자 |
<strong>min / max</strong> | 숫자/날짜 범위 | 나이 1~150 |
<strong>pattern</strong> | 정규 표현식 매칭 | 전화번호 형식 |
<strong>type</strong> | 입력 형식 자동 검증 | email, url, number |
<label for="lang">사용 가능한 프로그래밍 언어</label>
<input
list="languages"
id="lang"
name="language"
placeholder="언어 선택 또는 입력"
/>
<datalist id="languages">
<option value="JavaScript"></option>
<option value="TypeScript"></option>
<option value="Python"></option>
<option value="Java"></option>
<option value="C++"></option>
</datalist><!-- 구식: 폼 제출 → 페이지 전체 새로고침 -->
<form action="/login" method="POST">
<input name="email" type="email" />
<button type="submit">로그인</button>
</form>// 요즘 방식: JS에서 직접 요청 → 화면은 그대로, 데이터만 교환
async function handleLogin(email, password) {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await res.json();
// 로그인 성공 시 화면 업데이트
}// fetch: 내장 API, 에러 처리를 직접 해야 함
const res = await fetch("/api/users");
if (!res.ok) throw new Error("요청 실패"); // 이걸 안 하면 400/500도 그냥 통과
const data = await res.json();
// axios: 설치 필요, 에러는 자동 throw, JSON 변환도 자동
const { data } = await axios.get("/api/users");// ky: fetch 인터페이스 그대로, 편의 기능 추가 (프론트/Edge)
import ky from "ky";
const data = await ky.get("/api/users").json(); // 에러 자동 throw, JSON 자동 파싱
// got: Node.js 서버 환경에 최적화
import got from "got";
const data = await got("/api/users").json();---
Q1. 네이버나 구글에서 검색을 해보고, 주소창 URL을 확인해보세요. 어떤 방식(GET/POST)을 사용하고 있나요? 그리고 왜 로그인 폼은 같은 방식을 쓰지 않는다고 생각하나요?
Q2. 아래 CSS 코드에서 `.btn` 텍스트는 최종적으로 무슨 색이 될까요? 그리고 왜 그 색이 적용되는지, 어떤 규칙 때문인지 설명해보세요.
>
```css
p {
color: green;
}
.btn {
color: red;
}
#submit {
color: blue;
}
```
>
```html
<p class="btn" id="submit">제출</p>
```
Post Q&A
프론트엔드 스터디 2주차: 사용자와 소통하는 폼 & CSS의 시작 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
지금 당장 읽지 않아도 됩니다. React를 배우면서 에러 처리가 필요해지거나 면접 대비가 필요할 때 참고하세요.