JavaScript(ES6+, 비동기)와 React(SPA, JSX, Props, State)의 핵심 이론을 먼저 학습합니다. 이어서, 2일차에 설계한 UI를 Vite로 구현하는 실습을 진행하며, useState(상태관리), useEffect(생명주기), React Router(페이지 이동)를 중점적으로 다룹니다.
Table of Content
3일차 - 프론트엔드 개발: React 기초
부제: JavaScript 핵심 문법, React 컴포넌트, Hooks를 활용한 딥페이크 탐지 웹 UI 구현
Agenda: 3일차 학습 목표 및 일정
1.
JavaScript Core (이론): ES6+ 주요 문법, 비동기 처리 (Promise, async/await).
2.
React Core (이론): SPA, JSX, Component, Props, State 개념.
3.
실습 1 (환경 구축): React 개발 환경 구축 (Vite), 기본 컴포넌트 및 Props.
4.
실습 2 (상태 관리): React Hooks (useState), 이벤트 핸들링.
5.
실습 3 (UI/생명주기/라우팅): HTML/CSS in JSX, useEffect, React Router.
"어제(2일차)는 저희가 '설계자'의 입장이었습니다. 우리 팀의 목표(SMART)를 정하고, 유스케이스(User Story)를 도출하고, 아키텍처와 UI/UX, ERD, API 명세서까지 '무엇을' 만들지 명확히 정의하는 시간을 가졌습니다."
"오늘은 드디어 '개발자'의 입장으로 돌아와, 그 설계를 '구현'하는 첫날입니다."
"오늘 8시간 동안 우리는 2일차에 그렸던 그 UI/UX 와이어프레임을, **'React'**를 사용해 실제 동작하는 웹 화면으로 만드는 작업을 진행합니다."
"오늘 우리가 8시간 동안 다룰 내용을 캔버스의 Agenda로 정리했습니다. 3일차는 프론트엔드, 즉 React에 완전히 집중합니다."
"보시는 것처럼, 오전에는 '이론'을, 오후에는 '실습'을 중심으로 진행됩니다."
1.
"먼저 **'JavaScript Core (이론)'**입니다.
React는 결국 JavaScript 라이브러리죠. 1일차에 Colab에서 맛보기로 파이썬을 다뤘다면, 3일차부터는 웹의 언어인 JavaScript를 다룹니다. 특히 현업에서 필수인 ES6+ 문법, 그리고 4일차에 NestJS와 통신할 때 반드시 필요한 '비동기 처리', 즉 Promise와 async/await 개념을 확실히 짚고 넘어가겠습니다."
2.
"다음은 **'React Core (이론)'**입니다.
JS 언어를 알았다면, 이제 '왜 React인가'를 알아야겠죠. React가 왜 **SPA(Single Page Application)**라고 불리는지, 왜 HTML 대신 **'JSX'**라는 문법을 쓰는지, 그리고 React의 핵심 사상인 'Component(컴포넌트)', 'Props(데이터 전달)', 'State(상태 관리)' 개념을 명확히 정의해 드립니다. 이 세 가지가 React의 전부라고 해도 과언이 아닙니다."
3.
"오후에는 이론을 배운 손으로 바로 실습에 들어갑니다. **'실습 1 (환경 구축)'**입니다.
요즘 가장 빠르고 표준이 된 **'Vite'**를 사용해서 React 개발 환경을 1분 만에 구축할 겁니다. 그리고 2일차에 설계한 우리 앱의 '기본 컴포넌트'를 만들고, 부모-자식 간에 데이터를 전달하는 'Props' 실습을 진행합니다."
4.
"Props가 정적인 데이터라면, **'실습 2 (상태 관리)'**에서는 동적인 데이터를 다룹니다.
바로 'React Hooks', 그중에서도 가장 중요한 **useState*입니다. 사용자가 버튼을 클릭(이벤트 핸들링)했을 때, 화면이 실시간으로 바뀌는(State 변경) 인터랙티브한 웹을 만드는 핵심 실습입니다."
5.
"마지막 **'실습 3 (UI/생명주기/라우팅)'**은 흩어져 있던 조각들을 완성하는 단계입니다.
JSX 안에서 HTML 태그로 UI 레이아웃을 잡고, CSS로 스타일을 입혀봅니다.
그리고 useEffect Hook을 사용해, 컴포넌트의 '생명주기'를 관리하는 법(예: 최초 데이터 로딩)을 실습합니다.
마지막으로 **'React Router'**를 설치해서, 2일차에 설계했던 '메인 페이지'와 '결과 페이지' 간의 **페이지 이동(라우팅)**까지 구현해 볼 것입니다."
"오늘 3일차의 성과는 명확합니다. 2일차에 PPT로 그렸던 UI 목업을, 실제 브라우저에서 동작하는 React 웹 앱으로 완성하는 것입니다.
오늘 만든 이 React 앱이, 4일차에 만들 NestJS 백엔드 서버와 통신할 '클라이언트'가 됩니다.
그럼, 첫 번째 모듈, 'JavaScript Core' 이론부터 시작하겠습니다."
모듈 1: JavaScript Core (이론)
1. Why JavaScript?
•
웹 브라우저를 제어하는 유일한 프로그래밍 언어.
•
React, NestJS(Node.js) 모두 JavaScript(TypeScript) 기반으로 동작함.
JavaScript일까요? 우리가 왜 이 언어를 다시 짚고 넘어가야 하는지, 그 이유를 먼저 명확히 하겠습니다.”
"이것이 가장 근본적인 이유입니다.
Chrome, Edge, Safari 같은 웹 브라우저는 C++ 같은 언어로 만들어졌지만, 개발자가 이 브라우저를 동적으로 제어하기 위해(예: 버튼 클릭, 서버 통신, 화면 변경) 사용할 수 있도록 '유일하게' 허용한 스크립트 언어가 바로 JavaScript입니다.
우리가 3일차에 만들 React는 결국 이 JavaScript를 사용해 브라우저의 화면(DOM)을 효율적으로 제어하는 '라이브러리'일 뿐입니다. React의 모든 문법은 순수한 JavaScript로 변환되어 실행됩니다."
"두 번째 항목이 이 과정 전체를 관통하는 핵심입니다.
'React, NestJS(Node.js) 모두 JavaScript(TypeScript) 기반으로 동작함.'"
"이게 무슨 의미일까요?
우리의 풀스택(Full-stack) 아키텍처를 다시 떠올려보죠.
•
3일차 (프론트엔드): React를 사용합니다. (JavaScript)
•
4일차 (백엔드): NestJS를 사용합니다. NestJS는 Node.js 위에서 동작하는 프레임워크죠. Node.js는 브라우저를 벗어나 서버 컴퓨터에서도 JavaScript를 실행할 수 있게 해주는 '런타임 환경'입니다.
즉, 우리는 이번 과정을 통해 'JavaScript'라는 단 하나의 언어로 **'프론트엔드(React)'**와 **'백엔드(NestJS)'**를 모두 개발하는 경험을 하게 됩니다."
"물론 현업에서는 JavaScript의 '슈퍼셋(Superset)'인 **'TypeScript(타입스크립트)'**를 더 많이 씁니다. NestJS는 타입스크립트를 기본으로 하고, React도 타입스크립트와 궁합이 아주 좋습니다. 타입스크립트는 JavaScript에 '타입(Type)'을 명시하여, 1일차에 파이썬에서 경험했던 '런타임 오류'를 '컴파일 단계'에서 미리 잡을 수 있게 도와주는, 더 안정적인 언어입니다."
"결국, 이 JavaScript(와 TypeScript)를 깊이 이해하는 것이, 3일차의 React와 4일차의 NestJS를 모두 정복하는 '열쇠'가 됩니다."
"그럼, 다음 장에서는 우리가 코드를 작성할 때 필수로 알아야 할 JavaScript의 'ES6+ 핵심 문법'부터 빠르게 짚고 넘어가겠습니다."
2. ES6+ 핵심 문법
•
let (변수), const (상수)
"다 같이 크롬 브라우저에서 F12 키를 눌러서 '개발자 도구'를 열어주세요. 그리고 'Console(콘솔)' 탭을 클릭해 주세요.
(Mac 사용자는 Cmd + Option + J 입니다.)"
"이 콘솔 창은 1일차에 썼던 Colab의 '코드 셀'처럼, 자바스크립트 코드를 즉시 실행하고 결과를 보여주는 훌륭한 테스트 환경입니다."
1. let (변수) / const (상수)
"첫 번째는 변수 선언, let과 const입니다."
"과거 자바스크립트(ES5)에서는 모든 변수를 var로 선언했습니다. var는 스코프(범위) 문제가나 재선언 문제 등 코드가 복잡해지면 버그의 원인이 되곤 했습니다."
"ES6부터는 var를 거의 쓰지 않습니다. 대신 이 두 가지만 씁니다."
•
let: '변수'입니다. 값을 '재할당' 할 수 있습니다.
•
const: '상수(Constant)'입니다. 값을 '재할당' 할 수 없습니다.
"콘솔에 저를 따라 한번 입력해 보시죠."
(터미널에 다음 명령어 한줄씩 입력)
// 1. let (변수: 재할당 가능)
let myVariable = "Hello";
console.log(myVariable);
HTML
복사
myVariable = "World";
console.log(myVariable);
HTML
복사
"엔터를 치면 'World'로 값이 바뀝니다. let은 이렇게 재할당이 자유롭습니다."
"이제 const를 테스트해 보겠습니다."
// 2. const (상수: 재할당 불가능)
const myConstant = 100;
console.log(myConstant);
myConstant = 255;
HTML
복사
"엔터를 치면, 바로 'Uncaught TypeError: Assignment to constant variable.' (상수 변수에 값을 할당하려 했습니다) 라는 에러가 발생합니다."
"결론: 이렇게 변수와 상수를 상황에 맞도록 적절하게 활용해나가야 합니다.
•
화살표 함수 (=>): 간결한 함수 표현.
"두 번째는 '화살표 함수'입니다. 이건 정말 많이 씁니다. 이름 그대로 **'화살표(=>)'**를 사용해 함수를 간결하게 표현합니다."
"콘솔에서 기존 방식과 비교해 보죠. 'add'라는 이름의, 두 숫자를 더하는 함수를 만들겠습니다."
"먼저 기존 ES5 방식입니다."
// 기존 방식 (Function 키워드)
function add_es5(a, b) {
return a + b;
}
console.log( "ES5 방식:", add_es5(5, 10) );
HTML
복사
// 화살표 함수 방식 (ES6)
const add_es6 = (a, b) => {
return a + b;
};
console.log( "ES6 방식:", add_es6(5, 10) );
HTML
복사
"결과는 15로 똑같습니다. function 키워드가 사라지고 const와 =>가 그 자리를 대신했죠."
"그런데 여기서 더 줄일 수 있습니다."
"만약 함수 본체가 'return' 한 줄뿐이라면, {}(중괄호)와 return 키워드를 동시에 생략할 수 있습니다."
// 화살표 함수 (최종 축약형)
const add_es6_short = (a, b) => a + b;
console.log( "ES6 축약형:", add_es6_short(5, 10) );
HTML
복사
"15, 똑같이 나옵니다.
React에서는 onClick 핸들러나 배열 함수(map, filter)를 쓸 때, 이 축약형 문법을 정말 많이 사용하게 됩니다."
•
비구조화 할당: const { name } = user;, const [item] = arr;
"이름은 어렵지만, '객체(Object)나 배열(Array)에서 값을 '뽑아내어' 변수에 쉽게 할당하는 문법'입니다."
"먼저 객체(Object) 예시입니다."
// 1. 객체 비구조화 할당
const user = {
username: "deepfake-admin",
email: "admin@test.com",
role: "admin"
};
// 만약 user.username과 user.email이 필요하다면,
// 기존 방식
const username_es5 = user.username;
const email_es5 = user.email;
console.log(username_es5, email_es5);
// ES6 방식 (비구조화 할당)
// user 객체에서 'username'과 'email' 키를 뽑아서
// 'username', 'email'이라는 이름의 const 상수로 바로 만듦.
const { username, email } = user;
console.log(username, email);
HTML
복사
"결과는 똑같지만, 코드가 훨씬 깔끔해졌죠? 나중에 React 컴포넌트가 props를 받을 때 function MyComponent(props) 대신 function MyComponent({ title, content }) 처럼 바로 분해해서 사용합니다."
"다음은 배열(Array) 예시입니다. 이건 모듈 4에서 배울 useState Hook과 문법이 100% 똑같습니다."
// 2. 배열 비구조화 할당
const colors = ["Red", "Green", "Blue"];
// 기존 방식
const firstColor = colors[0];
const secondColor = colors[1];
console.log(firstColor, secondColor);
// ES6 방식 (비구조화 할당)
// 배열의 '순서'대로 값을 뽑아서 변수에 할당
const [c1, c2] = colors;
console.log(c1, c2);
HTML
복사
"결과는 같습니다. 바로 이 문법 const [c1, c2] = ...이, 우리가 모듈 4에서 배울 const [count, setCount] = useState(0); 와 완벽히 동일한 자바스크립트 문법입니다."
"여기까지 React와 NestJS에서 가장 많이 사용될 ES6+ 핵심 문법 3가지를 '크롬 개발자 도구 콘솔'에서 직접 테스트해봤습니다."
"let/const (변수/상수), => (화살표 함수), 그리고 {}/[] (비구조화 할당)."
"이제 이 문법을 가지고, 다음 장에서는 4일차에 우리 API 서버와 통신할 때 사용할 '비동기 처리' 방식에 대해 알아보겠습니다."
3. 비동기 처리 (Asynchronous)
•
Problem: API 요청, 파일 읽기 등 시간이 걸리는 작업이 메인 스레드를 막는 현상.
JavaScript 기반의 개발에서 가장 중요한 개념일 수 있습니다. 바로 **'비동기(Asynchronous) 처리'**입니다."
"이 개념이 왜 중요할까요?
우리가 2일차에 설계한 아키텍처를 떠올려보죠.
3일차의 React(프론트엔드)는 4일차의 NestJS(백엔드)에게 '딥페이크 탐지해줘'라고 **'API 요청(Request)'**을 보내야 합니다.
이 'API 요청'은 네트워크를 통해 다른 컴퓨터(서버)로 갔다 오는 작업입니다. 0.1초가 걸릴 수도, 네트워크가 느리면 5초가 걸릴 수도 있죠."
"**'Problem'**이 여기서 발생합니다.
자바스크립트는 기본적으로 '싱글 스레드(Single Thread)' 기반의 언어입니다. 즉, '한 번에 하나의 작업'만 순차적으로 처리합니다."
"만약 5초가 걸리는 API 요청을 '동기(Synchronous)' 방식으로 처리한다면 어떻게 될까요?
1.
사용자가 '탐지' 버튼을 클릭합니다.
2.
React가 NestJS로 API 요청을 보냅니다.
3.
(이때 5초가 걸린다고 가정...)
4.
자바스크립트(메인 스레드)가 멈춥니다.
5.
그 5초 동안 사용자는 화면의 아무것도 클릭할 수 없고, 스크롤도 안 되고, 브라우저 전체가 '응답 없음' 상태로 얼어붙습니다.
"이것이 바로 **'메인 스레드를 막는 현상(Blocking)'**입니다. 최악의 사용자 경험(UX)이죠."
"이 문제를 해결하기 위해, 자바스크립트는 '시간이 걸리는 작업'을 '비동기(Asynchronous)' 방식으로 처리합니다.
'API 요청' 같은 오래 걸리는 작업을 '별도의 공간(백그라운드)'에 던져놓고, 자바스크립트는 일단 다음 작업(예: 로딩 스피너 표시)을 바로 실행하는 방식입니다."
"이 비동기 작업을 다루는 현대적인 방식이 두 가지 있습니다. Promise와 async/await입니다."
•
Solution 1: Promise
◦
비동기 작업의 '결과' (성공/실패)를 나타내는 객체.
Solution 1: Promise (프로미스)
"**'Promise'**는 이름 그대로 '약속'입니다.
캔버스에 적힌 대로, '비동기 작업의 **미래의 결과(성공/실패)**를 담는 객체'입니다."
"비유를 들자면, 카페의 **'진동벨'**과 똑같습니다.
1.
우리가 fetch로 API 요청(주문)을 보냅니다.
2.
자바스크립트는 '진동벨(Promise 객체)'을 즉시 반환받고, 자기 할 일(UI 렌더링)을 계속합니다. (화면이 얼지 않음)
3.
나중에 서버에서 응답(커피)이 오면,
4.
'진동벨(Promise)'이 울립니다.
이때 진동벨이 울리면(작업이 끝나면) 우리가 할 행동을 예약하는 문법이 바로 .then()과 .catch()입니다."
•
.then(callback): (슬라이드 설명) 작업이 **'성공'**했을 때(커피가 나왔을 때) 실행할 콜백 함수.
•
.catch(callback): (슬라이드 설명) 작업이 **'실패'**했을 때(주문이 취소됐을 때) 실행할 콜백 함수.
"자, F12 개발자 도구 콘솔에서 직접 테스트해 보죠.
setTimeout은 'n초 뒤에 함수 실행'이라는 대표적인 비동기 함수입니다."
(콘솔에 다음 코드 입력)
console.log("1. (동기) 코드를 시작합니다.");
// '2. (비동기)' 작업을 5초뒤에 실행하라고 '예약'함
setTimeout(() => {
console.log("2. (비동기) 5초가 지났습니다. 작업 완료!");
}, 5000); // 5초
console.log("3. (동기) 코드가 끝났습니다.");
HTML
복사
(실행)
"결과를 보세요. 1, 2, 3 순서로 찍히지 않고, '1', '3'이 먼저 찍히고, 2초 뒤에 '2'가 찍힙니다."
"이게 바로 비동기입니다. 자바스크립트가 setTimeout(5초)을 기다려주지 않고(Non-Blocking) console.log(3)을 바로 실행한 것이죠."
◦
.then(successCallback)
◦
.catch(errorCallback)
•
Solution 2: async / await (가독성)
◦
Promise를 동기 코드처럼 쉽게 작성하는 문법.
Solution 2: async / await (어싱크/어웨이트)
"그런데 Promise의 .then().then().catch() 체인은 코드가 길어지면 **'콜백 지옥'**처럼 가독성이 떨어지는 문제가 있었습니다."
"그래서 ES2017(ES8)에 등장한 것이 바로 'async / await' 문법입니다."
"이것은 새로운 기술이 아닙니다. (슬라이드 설명) Promise를 그냥 동기 코드처럼 보이게 만들어주는 **'문법적 설탕(Syntactic Sugar)'**입니다. (즉, Promise를 더 예쁘게 포장한 거죠.)"
"사용법은 두 가지만 기억하면 됩니다."
1.
async function: "이 함수는 '비동기 작업(Promise)'을 포함하고 있습니다"라고 함수 앞에 async를 붙여줍니다.
2.
await: '진동벨(Promise)'이 울릴 때까지(즉, 비동기 작업이 끝날 때까지) 그 자리에서 **'기다려라'**는 뜻입니다.
"아래 코드를 콘솔에 붙여넣어 보시죠. fetch는 웹에서 API를 요청하는 Promise 기반 함수입니다."
(콘솔에 다음 코드 선입력)
// 1. 함수 선언 (async 키워드 사용)
async function fetchData() {
console.log("2. (비동기) API 요청을 시작합니다...");
try {
// await: fetch(API 요청)가 끝날 때까지 '이 함수 안에서만' 기다림
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
// await: response.json() 변환이 끝날 때까지 기다림
const data = await response.json();
console.log("3. (비동기) 데이터를 성공적으로 가져왔습니다:", data);
} catch (error) {
console.error("오류 발생:", error);
}
console.log("4. (비동기) fetchData 함수가 끝났습니다.");
}
HTML
복사
이후 실행
// --- 실행 ---
console.log("1. (동기) 코드를 시작합니다.");
fetchData();
console.log("5. (동기) 코드가 끝났습니다.");
HTML
복사
이 코드가 1→2→5→3→4 로 실행되는 이유는 특히 await이라는 부분때문입니다.
•
await fetch(...) 라인을 만나는 순간:
◦
말씀하신 대로, fetchData 함수는 'API 요청'을 보내고 그 **응답(Response)**이 올 때까지 '일시 정지' (wait)합니다.
◦
중요: 하지만 함수 '밖'의 메인 스레드는 멈추지 않고(Non-blocking) 즉시 다음 줄로 넘어갑니다.
•
5번 로그가 먼저 찍히는 이유:
◦
메인 스레드는 fetchData가 응답을 기다리든 말든 상관없이, 자신의 다음 일인 console.log("5. (동기) 코드가 끝났습니다.");를 즉시 실행합니다.
◦
이것이 1, 2번 로그 직후에 5번 로그가 바로 찍히는 이유입니다.
•
3, 4번 로그가 늦는 이유:
◦
(잠시 후) 네트워크 응답이 도착하면, '일시 정지'했던 fetchData 함수가 깨어납니다.
◦
두 번째 await response.json()을 만납니다. 말씀하신 대로, 이번엔 응답(Response) 바디의 변환이 끝날 때까지 다시 '일시 정지'합니다.
◦
변환이 완료되면, 드디어 **3*번 로그가 찍히고, **4*번 로그가 찍히면서 함수가 완전히 종료됩니다.
만약 await가 없다면, 자바스크립트는 기본적으로 fetchData() 함수 내의 fetch(...) API 요청을 (비동기로) '실행하라고 명령'만 하고, 그 응답이 오든 말든 즉시 다음 줄로 넘어가 버립니다.
그렇게 되면 "데이터가 아직 없는데 (그 데이터를 사용하려 하거나) 응답을 해버리는" 심각한 런타임 오류가 발생합니다.
await 키워드는 바로 그 '논리적인 순서'를 제어하기 위해 사용합니다.
await는 Promise가 완료될 때까지(즉, API 응답이 오거나 파일 읽기가 끝날 때까지) **async 함수 내부의 실행 흐름을 '일시 정지'**시킵니다.
•
const response = await fetch(...) : response 변수에 실제 응답이 '도착할 때까지' 기다립니다.
•
const data = await response.json() : response를 json으로 '변환이 끝날 때까지' 기다립니다.
즉, 1번 작업(요청)이 끝나야만 2번 작업(변환)을 실행하고, 2번이 끝나야 3번(데이터 사용)을 실행할 수 있도록 보장해 줍니다.
'비동기'의 장점(메인 스레드를 막지 않음)은 그대로 살리면서도, '동기' 코드처럼(A가 끝나야 B가 실행됨) 가독성 높게 실행 순서를 제어할 수 있게 해주는 것이 바로 await입니다.
JS는 "비동기 작업을 처리하는 것" 자체를 언어의 핵심 기능으로 받아들이고 async/await처럼 매우 쉽고 직관적인 문법을 제공하기 때문에 (스레드나 락을 직접 다루는 것에 비해) 비동기 처리가 훨씬 '쉽다'고 보는 것이 정확한 이해입니다.
◦
async function fetchData() { ... }
◦
const data = await apiCall();
모듈 2: React Core Concepts (이론)
1. Why React?
•
SPA (Single Page Application): 단일 페이지로 구성, 필요한 부분만 동적으로 갱신.
•
컴포넌트 기반 (CBA): UI를 독립적이고 재사용 가능한 조각(컴포넌트)으로 분리.
첫 번째 모듈, 'JavaScript Core' 이론부터 시작하겠습니다.
1일차에 Colab에서 Python을 다뤘는데, 왜 3일차부터는 JavaScript일까요? 우리가 왜 이 언어를 다시 짚고 넘어가야 하는지, 그 이유를 먼저 명확히 하겠습니다."
"캔버스의 첫 번째 항목을 보시죠.
'웹 브라우저를 제어하는 유일한 프로그래밍 언어.'"
"이것이 가장 근본적인 이유입니다.
Chrome, Edge, Safari 같은 웹 브라우저는 C++ 같은 언어로 만들어졌지만, 개발자가 이 브라우저를 동적으로 제어하기 위해(예: 버튼 클릭, 서버 통신, 화면 변경) 사용할 수 있도록 '유일하게' 허용한 스크립트 언어가 바로 JavaScript입니다.
우리가 3일차에 만들 React는 결국 이 JavaScript를 사용해 브라우저의 화면(DOM)을 효율적으로 제어하는 '라이브러리'일 뿐입니다. React의 모든 문법은 순수한 JavaScript로 변환되어 실행됩니다."
"두 번째 항목이 이 과정 전체를 관통하는 핵심입니다.
'React, NestJS(Node.js) 모두 JavaScript(TypeScript) 기반으로 동작함.'"
"이게 무슨 의미일까요?
우리의 풀스택(Full-stack) 아키텍처를 다시 떠올려보죠.
•
3일차 (프론트엔드): React를 사용합니다. (JavaScript)
•
4일차 (백엔드): NestJS를 사용합니다. NestJS는 Node.js 위에서 동작하는 프레임워크죠. Node.js는 브라우저를 벗어나 서버 컴퓨터에서도 JavaScript를 실행할 수 있게 해주는 '런타임 환경'입니다.
즉, 우리는 이번 과정을 통해 'JavaScript'라는 단 하나의 언어로 **'프론트엔드(React)'**와 **'백엔드(NestJS)'**를 모두 개발하는 경험을 하게 됩니다."
"물론 현업에서는 JavaScript의 '슈퍼셋(Superset)'인 **'TypeScript(타입스크립트)'**를 더 많이 씁니다. NestJS는 타입스크립트를 기본으로 하고, React도 타입스크립트와 궁합이 아주 좋습니다. 타입스크립트는 JavaScript에 '타입(Type)'을 명시하여, 1일차에 파이썬에서 경험했던 '런타임 오류'를 '컴파일 단계'에서 미리 잡을 수 있게 도와주는, 더 안정적인 언어입니다."
"결국, 이 JavaScript(와 TypeScript)를 깊이 이해하는 것이, 3일차의 React와 4일차의 NestJS를 모두 정복하는 '열쇠'가 됩니다."
"그럼, 다음 장에서는 우리가 코드를 작성할 때 필수로 알아야 할 JavaScript의 'ES6+ 핵심 문법'부터 빠르게 짚고 넘어가겠습니다."
2. 개발 환경 구축
•
통합개발환경(IDE): Visual Studio Code
우선 저희 로컬 컴퓨터가 개발 컴퓨터가 되기위한 가장 첫 단계는 개발 소프트웨어를 준비해야 하는 것들입니다. 그중 통합개발환경인 VSCode를 설치하려고 합니다. 수많은 코드에디터들이 있지만 가장 많은 점유율을 가지기도 했고 플러그인에 따라 다양한 커스텀이 가능한 VSCode를 사용하겠습니다.
특히나 Python 뿐만 아니라 전통적으로 JavaScript 개발자들이 많이 사용하기 때문에 여러 기술스택을 다루는 저희는 VSCode가 적합하다고 생각합니다.
VSCode 공식 다운로드를 통해서 윈도우에 설치해주도록 합니다.
•
필수: Node.js, npm (Node Package Manager).
"우리가 1일차에 'Colab'에서 Python을 실행했듯이, 3일차부터는 '로컬 PC'에서 JavaScript를 실행해야 합니다.
•
*Node.js*는 JavaScript를 웹 브라우저 바깥, 즉 여러분의 PC(서버)에서 실행할 수 있게 해주는 **'JavaScript 런타임'**입니다. 이게 없으면 React 개발 서버를 띄울 수 없습니다.
•
*npm (Node Package Manager)**은 Node.js를 설치할 때 자동으로 함께 설치되는 **'패키지 관리자'**입니다. Python에 pip가 있다면, JavaScript/Node.js 생태계에는 npm이 있습니다. 우리는 npm 명령어를 사용해서 React, React Router 같은 라이브러리(패키지)를 설치하게 됩니다."
•
Node.js 설치
•
설치 후 버전확인
•
(만약 '명령어를 찾을 수 없다'고 나오면, 지금 바로 nodejs.org에 접속해서 LTS 버전을 설치해 주셔야 합니다.)"
•
"Node.js와 npm 버전이 모두 확인되셨다면, React 개발을 위한 '필수' 환경은 준비가 끝난 것입니다.”
•
도구: Vite (최신/빠름) 또는 Create React App (CRA, 전통적).
"다음은 '도구(Tools)' 항목입니다. **'Vite(비트)'**와 **'Create React App(CRA)'**이죠.
이것들은 'React 프로젝트 생성 도구'입니다. 우리가 2일차에 설계한 복잡한 웹 앱을 개발하려면, 'Babel'이나 'Webpack' 같은 수많은 설정(트랜스파일링, 번들링)이 필요합니다.
Vite나 CRA는 이 모든 복잡한 설정을 알아서 다 해주는 '시작 템플릿' 또는 **'스캐폴딩(Scaffolding) 도구'**라고 보시면 됩니다."
•
CRA (Create React App): 과거의 표준입니다. 안정적이지만, Webpack 기반이라 로컬 서버를 구동하거나 수정 사항을 반영하는 속도가 다소 느렸습니다.
•
Vite (비트): (프랑스어로 '빠르다'는 뜻) 현재의 '사실상 표준(De facto standard)'입니다. ESBuild라는 빠른 도구를 사용해서, CRA와 비교도 안 될 만큼 '압도적으로 빠른' 개발 서버 속도를 제공합니다.
"우리는 이번 3일차 실습에서, 당연히 현업의 표준인 **'Vite'**를 사용할 겁니다."
"좋습니다. 그럼 이제 React의 핵심 개념인 'SPA', 'JSX', '컴포넌트'가 무엇인지 이론을 마저 살펴보고,
바로 다음 모듈, **'모듈 3: 실습 1'**에서 이 'Vite'를 사용해 프로젝트를 생성하는 첫 번째 명령어를 실행해 보겠습니다."
"이번 실습 목표는 두 가지입니다.
첫째, 우리가 오전에 node -v로 점검했던 그 환경을 이용해, **'Vite'**로 React 프로젝트 환경을 직접 구축합니다.
둘째, 2일차에 설계했던 우리 앱의 '기본 컴포넌트'를 만들고, 부모-자식 간에 데이터를 전달하는 **'Props'**를 실습합니다."
우선 vite로 프로젝트를 만들어 보겠습니다.
프로젝트 생성
•
우선 프로젝트 폴더를 생성하고 (탐색기)
•
해당 폴더를 VSCode 터미널로 열기 (View → Terminal) 또는 Ctrl + `
•
npm create vite@latest 로 생성
◦
◇ Project name:
│ detect-frontend
◦
◇ Select a framework:
│ React
◦
◇ Select a variant:
│ TypeScript
◦
◇ Use rolldown-vite (Experimental)?:
│ No
◦
◇ Install with npm and start now?
│ Yes
•
http://localhost:5173/ 을 클릭하여 들어가보기
•
이렇게 간단하게 벌써 vite환경에서 실행되는 React 프로젝트 초기화면이 열리는 것을 확인
•
VSCode를 New Window로 새로 열어 해당 프로젝트 폴더를 선택하여 다시 열기
•
이렇게 준비 되면 개발 환경 구축은 완료!
•
프론트엔드 서버를 다시 실행하고 싶으면
◦
터미널을 열고 (Ctrl + `)
◦
npm run dev
•
이것이 실행되는 구조를 설명해드리면,
◦
사실 프로젝트는 단순히 index.html을 화면에 보여주는 역할을 합니다.
◦
우선 index.html을 보면 src/main.tsx가 담겨 있습니다.
◦
main.tsx를 보면 <App />이라는 것이 담겨 있습니다. 이 앱은 또한 App.tsx라는 곳에서 임포트 되었다라고 되어 있죠.
◦
App.tsx로 가보면 그 <App> 이라는 태그가 어떻게 구성되어 있는지 자세히 설명되어 있습니다. 이것은 특히 function 함수로 구현되어 있습니다. 하지만 html 태그를 반환하는 형태입니다.
◦
따라서 여기 있는 내용들이, main.tsx로 그리고 index.html로 이동하면서 그려진다는 말과 같습니다.
3. React 핵심 개념
•
JSX (JavaScript XML):
◦
JavaScript 파일 내에 HTML과 유사한 마크업을 작성하는 문법.
◦
const element = <h1>Hello</h1>;
이제 'React'라는 '라이브러리'의 핵심 사상을 알아보겠습니다."
"먼저 'Why React?'
왜 이렇게 많은 회사와 개발자들이 React를 사용할까요?"
"첫 번째 키워드는 **'SPA (Single Page Application)'**입니다.
이름 그대로 '단 하나의 페이지(HTML)로 구성된 애플리케이션'이라는 뜻입니다.
전통적인 웹사이트(Multi Page Application)는 사용자가 링크를 클릭할 때마다 서버로부터 '새로운 HTML 페이지 전체'를 받아와서 화면이 '깜빡'이며 새로고침되었습니다.
하지만 React 같은 SPA는, 처음에 '껍데기' HTML 딱 한 장만 받아옵니다. 그 후로는 사용자가 버튼을 클릭하면, 데이터(JSON)만 서버(NestJS)와 주고받고, 그 데이터를 기반으로 '화면의 필요한 부분만' JavaScript(React)가 동적으로 싹 다시 그려줍니다. 화면 깜빡임이 없죠. 우리가 쓰는 Gmail, Google Maps, Naver 지도 등이 모두 SPA 방식입니다."
"두 번째 키워드는 **'컴포넌트 기반 (CBA)'**입니다.
이것이 React의 핵심 철학입니다.
'컴포넌트'는 '부품'이란 뜻이죠. React는 'UI 전체'를 한 번에 만드는 게 아니라, 레고 블록처럼 '재사용 가능한 독립적인 부품'으로 잘게 쪼개서 만듭니다.
예를 들어, '버튼', '헤더', '사이드바'를 각각 '컴포넌트(부품)'로 만들어두고, 나중에 이 부품들을 '조립'해서 하나의 웹페이지를 완성하는 방식입니다.
이 덕분에 유지보수와 재사용성이 극도로 높아집니다."
"자, 그럼 이 '컴포넌트'를 대체 '어떤 문법'으로 만들까요?
드디어 오늘 이론의 핵심, **'3. React 핵심 개념'**의 첫 번째 주자, **'JSX'**입니다."
"JSX는 **'JavaScript XML'**의 약자입니다.
이게 정말 특이한 문법인데요, vite 앱의 App.tsx에 이 부분을 넣어보겠습니다.
const element = <h1>Hello</h1>;
HTML
복사
"이 코드는 지금 .js (또는 .jsx) 자바스크립트 파일 안에 작성된 것입니다.
그런데 <h1>Hello</h1>라는, HTML과 똑같이 생긴 마크업이 '문자열'("<h1>")도 아닌데 const 변수에 값으로 할당되고 있습니다.
그리고 {element}처럼 중괄호로 해당 “변수”를 마치 HTML태그처럼 그려내고있습니다.
React는 렌더링 중에 {element}를 만나는 순간, '아, element라는 자바스크립트 변수 값을 렌더링해야지'라고 판단합니다. 그리고 그 변수 안에는 아까 컴파일된 <h1> 객체가 들어있으니, 결국 화면에 'Hello'라는 <h1> 태그를 그리게 됩니다.
이처럼 **'자바스크립트 파일 내부에 HTML과 유사한 마크업을 직접 작성하는 문법'**을 JSX라고 부릅니다."
"왜 이렇게 할까요?
React는 'UI(화면)'를 만드는 라이브러리입니다. UI를 만들 때는 '이 버튼은 파란색이다' 같은 '로직(JS)'과 '버튼의 구조(HTML)'가 밀접하게 연관되어 있습니다.
JSX를 사용하면, document.createElement('h1') 같은 복잡한 DOM 조작 명령어를 쓰는 대신,
우리가 이미 알고 있는 HTML 마크업과 거의 똑같은 방식으로 **'선언적(Declarative)'**으로 UI의 '구조'와 '로직'을 한 파일 안에 작성할 수 있습니다."
"물론, 이 JSX 코드는 '진짜 HTML'이 아닙니다.
웹 브라우저는 const element = <h1>Hello</h1>; 같은 문법을 이해하지 못합니다. (크롬 콘솔에 쳐보면 SyntaxError가 나죠.)
우리가 npm run dev로 Vite를 실행하는 순간, Vite(내부적으로 Babel)가 이 JSX 코드를 브라우저가 이해할 수 있는 순수한 자바스크립트, 즉 React.createElement('h1', null, 'Hello') 같은 코드로 **'변환(Compile/Transform)'**해줍니다."
"우리는 이 편리한 JSX 문법을 사용해서, '컴포넌트(부품)'가 어떤 HTML 구조를 반환(return)할지 정의하게 됩니다."
"그럼, 이 JSX를 반환하는 '부품', 즉 'Component(컴포넌트)'가 무엇인지 다음 장에서 알아보겠습니다."
"Vite에서 .jsx는 JavaScript + JSX입니다.
우리가 선택한 .tsx는 TypeScript + JSX입니다.
타입스크립트를 쓰면, 우리가 API로 받아올 data의 타입(모양)을 미리 정의할 수 있습니다.
이렇게 하면, 1일차에 겪었던 런타임 오류를 **'컴파일 시점'**에 VS Code가 바로 잡아주는 장점을 이용 하게 됩니다.
우리는 안정적인 타입 시스템까지 갖추기 위해서 순수 자바스크립트보다는 TypeScript를 사용하고 있기 때문에 tsx를 사용하게 됩니다. jsx나 tsx나 모두 결국 순수 javascript로 컴파일 단계에서 번역되기 때문에 큰 차이는 없으며 오히려 NestJS는 TypeScript 기반이기 때문에 개발 통일성을 위하여 그리고 트렌드에 맞추어 TypeScript를 진행합니다.
•
Component (컴포넌트):
◦
UI를 독립적으로 나누어 재사용하는 부품 (JavaScript 함수).
◦
function Welcome(props) { ... }
이제는 '어디에(Where)' 그 JSX를 작성하는지, 즉 React의 **'핵심 사상(Core Philosophy)'**인 **'컴포넌트(Component)'**에 대해 알아보겠습니다."
'UI를 독립적으로 나누어 재사용하는 부품 (JavaScript 함수).'
이것이 React의 전부라고 해도 과언이 아닙니다.
React는 '딥페이크 탐지기'라는 거대한 UI 덩어리를 한 번에 만들지 않습니다.
대신 '레고 블록'처럼 기능별로 UI를 잘게 쪼갭니다."
•
맨 위 '헤더(Header)'
•
'이미지/음성'을 바꾸는 '탭(Tabs)'
•
파일을 올리는 '업로드 영역(Uploader)'
•
결과를 보여주는 '결과 차트(ResultChart)'
이 모든 것을 각각 **'독립적인 부품(컴포넌트)'**으로 만드는 것입니다.
그리고 App.jsx라는 최상위 컴포넌트에서 이 부품들을 '조립'해서 하나의 애플리케이션을 완성합니다."
"그럼 이 '컴포넌트'라는 부품은 대체 어떻게 만들까요?
우리는 이미 <App / >을 통해서 먼저 확인했었고, 이런 방식을 “컴포넌트” 라고 합니다.
•
따라서 *그냥 '자바스크립트 함수(JavaScript Function)'**를 정의하는 것이 컴포넌트화 시키는 것이다."라고 이해 할 수 있습니다.
"지켜야 할 규칙은 두 가지입니다."
1.
"컴포넌트(함수)의 이름은 **'반드시 대문자'**로 시작합니다. (예: Welcome, MyButton). (소문자로 시작하면 React가 HTML 태그(예: div, p)와 구분하지 못합니다.)"
2.
"이 함수는 **'반드시 UI(JSX)'를 반환(return)**해야 합니다."
•
App.tsx를 참고하여 Hello.tsx를 만들어봅시다.
•
규칙에 맞게 파일명, 함수명, 반환을 지정해주고 export까지 지정해줍니다.
function Hello() {
return <h1>Hello, Components</h1>;
}
export default Hello;
HTML
복사
•
그리고 <Hello />를 main.tsx에 붙여봅시다.
•
다음처럼 우리가 만든 헬로 컴포넌트가 페이지에 붙은 것을 확인 할 수 있습니다.
이 처럼 각 부품들을 직접 만들고 붙여나가는 것이 리액트의 핵심이고, 각 부품들을 다른 프로젝트에도 잘 이식 할 수 있도록 모듈화, 재이식 하는 것을 적극적으로 활용하는 것이 React와 JavaScript의 궁극적인 방향이라 볼 수 있습니다.
•
Props (Properties):
◦
부모 컴포넌트 -> 자식 컴포넌트로 전달되는 데이터 (읽기 전용, 변경 불가).
'컴포넌트'라는 '부품(함수)'을 만들고, 그 부품이 'JSX'를 반환한다는 것까지 이해했습니다."
"그럼 이런 의문이 생기죠.
만약 Welcome 컴포넌트를 여러 번 쓰는데, 각각 'Hello, Admin', 'Hello, User'처럼 **'다른 데이터'**를 보여주고 싶으면 어떡할까요?"
"이렇게 부모(App)가 자식(Welcome)에게 데이터를 '전달'하는 방법이 바로 다음 장에서 배울 **'Props (Properties)'**입니다."
설계한 UI를 다시 생각해 보죠. '이미지 탐지' 탭을 누르는 버튼과 '음성 탐지' 탭을 누르는 버튼이 있습니다. 둘 다 '버튼'이라는 '부품'이지만, 그 안에 들어가는 '텍스트(데이터)'는 다릅니다.
"만약 MyButton이라는 컴포넌트를 만들었는데, 이 버튼이 항상 '확인'이라는 글자만 보여준다면 재사용성이 떨어지겠죠?"
"이처럼, 우리가 만든 '부품(자식 컴포넌트)'에 '외부(부모 컴포넌트)'에서 데이터를 전달하기 위해 사용하는 것이 바로 **'Props (Properties)'**입니다."
"Props의 정의는 간단합니다.
**'부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터'**입니다."
"Props는 컴포넌트를 호출(사용)할 때, HTML 태그의 '속성(Attribute)'처럼 넘겨줍니다."
"(예시) App.tsx (부모)가 MyButton.tsx(자식)을 부른다고 가정해 보죠."
•
App.tsx
import './App.css'
import MyButton from './MyButton';
function App() {
return (
// 'title'이라는 이름으로 "이미지 탐지" 값을 전달
<MyButton title="이미지 탐지" />
);
}
export default App
HTML
복사
•
MyButton.tsx
// 1. props의 '타입'을 정의합니다. (title은 string 타입)
interface MyButtonProps {
title: string;
}
// 2. 'props' 객체를 통째로 받고, 그 타입을 'MyButtonProps'로 지정
function MyButton(props: MyButtonProps) {
// 3. props.title 로 값에 접근
return <button>{props.title}</button>;
}
export default MyButton;
HTML
복사
•
간단하게 버튼 컴포넌트를 하나 만들었으며, “이미지 탐지”라는 글씨는
•
Props를 통해 전달받습니다.
•
부모가 전달한 모든 Props를 **하나의 '객체(Object)'**로 묶어서, 자식 컴포넌트 함수의 **'첫 번째 매개변수(parameter)'**로 전달해 줍니다.
이 매개변수(전달되는변수)의 이름이 관례적으로 **props**입니다."
•
여기서 타입스크립트를 사용하는 장점도 볼 수 있는데, 이 변수의 타입을 제어 할 수 있습니다. 이 props 객체가 어떤 '타입'인지 명시해 줘야 합니다."
•
이렇게 title 부분을 숫자를 넘겨줘보면, 경고 문구가 나타나게 되고 문자열 타입이 아닌 것을 개발자에게 알려줍니다.
•
MyButton.tsx 를 이제 ES6 비구조화 문법에 맞추어 좀 더 깔끔하게 만들 수 있습니다.
// 1. props의 '타입'을 정의합니다. (title은 string 타입)
interface MyButtonProps {
title: string;
}
// 2. 'props' 객체를 매개변수 자리에서 바로 '비구조화'
function MyButton({ title }: MyButtonProps) {
// 3. 'title' 변수로 바로 접근
return <button>{title}</button>;
}
export default MyButton;
HTML
복사
"이 방식이 훨씬 더 간결하고, props.를 반복해서 쓰지 않아도 돼서 현업에서 가장 선호하는 방식입니다."
"Props에서 가장 중요한 규칙이 하나 있습니다.
바로 **'읽기 전용 (Read-Only)'**이라는 점입니다."
"부모에게서 받은 props는 자식 컴포넌트가 '절대' 직접 수정(변경)할 수 없습니다."
"(예시) 자식이 부모에게 받은 title을 바꾸려고 시도하면 안 됩니다."
// 1. props의 '타입'을 정의합니다. (title은 string 타입)
interface MyButtonProps {
title: string;
}
// 2. 'props' 객체를 매개변수 자리에서 바로 '비구조화'
function MyButton({ title }: MyButtonProps) {
title = "변경 시도"
// 3. 'title' 변수로 바로 접근
return <button>{title}</button>;
}
export default MyButton;
HTML
복사
•
물론 변경은 되지만 React의 '단방향 데이터 흐름' 원칙을 위배하는, 매우 위험한 안티-패턴(Anti-Pattern)**이기 때문에 사용을 지양해야 합니다.
•
이것이 '안티-패턴'인 이유: '부모가 모르는 변경'이기 때문입니다. 값을 넘겨준 App.tsx는 title이 변경 된 것을 모르는 상황이 일관성을 해칠 수 있기 때문입니다.
그렇다면,
'Props는 부모가 주고, 자식은 수정도 못 한다면, 사용자가 '클릭'했을 때처럼 컴포넌트 스스로 값이 바뀌어야 할 때는 대체 어떻게 하라는 거냐?'"
"예를 들어, '카운터' 버튼을 누르면 숫자가 올라가야 하고, '로그인' 버튼을 누르면 '로그인 중...'으로 글자가 바뀌어야 하죠."
"이렇게 컴포넌트가 '내부적으로' 가지는, '변경 가능한' 데이터를 다루는 방법이 바로,
React의 두 번째 핵심 개념인 **'State (상태)'**입니다.
다음 장에서는 바로 이 'State'에 대해 알아보겠습니다."
•
State (상태):
◦
컴포넌트 내부에서 관리되는 동적 데이터 (변경 가능).
◦
중요: State가 변경되면 React가 UI를 자동으로 다시 렌더링(Re-render)함.
"그런데 여기서 React의 핵심적인 질문이 나옵니다.
부모가 준 값(Props)을 자식이 바꿀 수 없다면,
1.
'카운터' 버튼을 눌렀을 때 숫자가 1, 2, 3 올라가는 건 어떻게 처리하죠?
2.
사용자가 '로그인' 버튼을 눌렀을 때, 버튼 텍스트가 '로그인 중...'으로 바뀌는 건 어떻게 하죠?
3.
사용자가 폼에 아이디를 '입력'할 때마다 그 값이 저장되는 건 어떻게 하죠?"
"이 모든 것은 **'컴포넌트 자기 자신'**이 내부적으로 관리해야 하는, '변경 가능한' 데이터입니다."
"이것이 바로 React의 두 번째 핵심 개념, **'State (상태)'**입니다."
**State(상태)**란, **'컴포넌트 내부에서 관리되는 동적 데이터 (변경 가능)'**입니다."
"Props와 State를 한 문장으로 비교하면 이렇습니다."
•
Props: 부모가 자식에게 주는 '지시'입니다. (읽기 전용)
•
State: 컴포넌트 '스스로'가 관리하는 '내부 값'입니다. (변경 가능)
"그런데 왜 1일차에 배운 let myVariable = 0; 같은 일반 변수를 쓰지 않고, 굳이 'State'라는 복잡한 개념을 쓸까요?"
"그 이유가 바로 캔버스의 '중요' 항목입니다.
'State가 변경되면 React가 UI를 자동으로 다시 렌더링(Re-render)함.'"
"이것이 React의 '마법'이자 존재 이유입니다."
"만약 우리가 let count = 0;이라고 일반 변수를 만들고, 버튼을 눌러 count = 1;로 바꾼다고 해도, React는 그 값이 바뀐 지 '모릅니다.' 당연히 화면도 '0'에서 바뀌지 않죠."
"하지만 우리가 React가 제공하는 '특별한 방식'으로 "이 count는 'State'입니다"라고 선언하면,
React는 이 count 변수를 '특별 관리' 대상에 올립니다.
그리고 나중에 우리가 이 count State를 (특별한 함수로) '0'에서 '1'로 변경하면,
React가 즉시 알아차리고, **"State가 바뀌었군! 화면을 새로고침(리렌더링)해서 최신 값 '1'을 보여줘야겠다!"**라고 자동으로 동작합니다.
"우리는 '화면을 어떻게 지우고 다시 그릴지'를 직접 코딩하는 게 아니라, 그냥 'State만 바꾸면' React가 알아서 화면을 갱신해 줍니다. 이것이 React가 '선언형'이라고 불리는 이유입니다."
"그럼 이 '특별한 방식'이 무엇일까요?
바로 **'모듈 4: 실습 2'**에서 배울 'React Hooks', 그중에서도 가장 중요한 useState Hook입니다.
이제 이론은 모두 끝났습니다.
다음 모듈 3부터는, 오늘 배운 JSX, 컴포넌트, Props, 그리고 이 State를 useState Hook으로 직접 구현해보는 실습을 시작하겠습니다."
"방금 전 Props 설명 마지막에 아주 중요한 질문이 나왔습니다.
'Props는 부모가 줘서 자식이 바꾸면 안 된다면서, 왜 title = "변경 시도" 같은 코드가 (일단은) 동작하는가?'"
"그 이유는, 그 title이 '원본 Prop'이 아니라 '복사된 지역 변수'이기 때문이라고 설명드렸습니다. 그리고 그 값은 리렌더링되면 다시 부모가 준 "초기 텍스트"로 돌아가 버리죠. (값이 유지되지 않음)"
"React에게 '이 값은 사용자가 클릭해서 바뀔 수 있는 값이고, 그 바뀐 값을 **유지(기억)**해야 해!'라고 알려줘야 합니다.
이것이 바로 모듈 2에서 이론으로 배운 **'State(상태)'**입니다."
"이 'State'를 함수형 컴포넌트에서 사용할 수 있게 해주는 도구가 바로 useState Hook입니다."
•
App.tsx
import './App.css'
import MyButton from './MyButton';
function App() {
return (
<div>
<h1>Props와 State 실습</h1>
{/* 'title'은 이제 "초기 텍스트"라는 '초기값'을
전달하는 용도로만 사용됩니다.
*/}
<MyButton title="초기 텍스트 (클릭해보세요)" />
<MyButton title="다른 버튼 (클릭해보세요)" />
</div>
);
}
export default App;
HTML
복사
다음 처럼 초기값은 부모인 App.tsx가 저 문자들을 세팅해주게 됩니다.
•
MyButton.tsx
import { useState } from "react";
// 1. props의 '타입'을 정의합니다. (title은 string 타입)
interface MyButtonProps {
title: string;
}
// 2. 'props' 객체를 매개변수 자리에서 바로 '비구조화'
function MyButton({ title }: MyButtonProps) {
const [buttonText, setButtonText] = useState(title);
const handleClick = () => {
setButtonText("클릭! (State 변경됨)");
};
// 3. 'title' 변수로 바로 접근
return (
<button onClick={handleClick}>
{buttonText}
</button>
);
}
export default MyButton;
HTML
복사
•
2. 'useState' Hook을 호출합니다.
- 초기값(initialValue): 부모가 준 'title' prop
- 'buttonText': 현재 상태 값을 저장할 변수 (읽기 전용)
- 'setButtonText': 이 값을 변경할 유일한 함수 (Setter)
•
3. 'setButtonText' 함수가 호출되면, React는
"State가 변경되었다!"는 것을 감지하고,
이 'MyButton' 컴포넌트를 '자동으로 리렌더링(Re-render)'합니다.
그리고 'buttonText' 변수에는 "클릭! (State 변경됨)"이 담기게 됩니다.
•
'props.title'이 아닌,
'state'인 'buttonText'를 화면에 렌더링합니다.
'onClick' 이벤트에 'handleClick' 함수를 연결합니다.
"이제, 이 버튼들을 **'클릭'**해 보세요."
"(클릭 시연)"
"네! 버튼의 텍스트가 **'클릭! (State 변경됨)'**으로 바뀌는 것이 보입니다.
그리고 이 값은 Prop을 직접 수정했던 '안티-패턴'과 달리, 다른 곳을 클릭하거나 해도 **사라지지 않고 '유지'**됩니다."
"이것이 Props와 State의 결정적인 차이입니다."
•
Props: 부모가 정해주는 '초기값'. (읽기 전용)
•
State: 컴포넌트가 '클릭' 같은 이벤트를 받아 스스로 변경하고 '기억'하는 '내부 값'.
"이 useState Hook을 사용해서, 우리는 이제 사용자의 입력에 '반응'하는 동적인 웹 UI를 만들 수 있게 되었습니다."
모듈 3: 실습 1 - React 프로젝트 생성 및 기본 컴포넌트
1. 실습 목표
•
React 프로젝트 환경 구축 및 컴포넌트 데이터 흐름(Props) 이해.
2. 프로젝트 생성 (Vite/CRA)
•
npm create vite@latest my-app -- --template react (Vite)
•
또는 npx create-react-app my-app (CRA)
•
npm run dev (Vite) / npm start (CRA) 실행 확인.
3. JSX 및 기본 컴포넌트
•
src/App.js 파일 수정.
•
JSX 문법으로 텍스트, 버튼 등 기본 UI 요소 추가.
4. Props 전달 실습
•
src/MyComponent.js 파일 생성 (자식 컴포넌트).
•
App.js (부모)에서 MyComponent로 데이터 전달 (Props).
◦
<MyComponent title="딥페이크 탐지기" />
•
MyComponent.js에서 props.title을 받아 화면에 출력.
모듈 4: 실습 2 - React Hooks (useState)
그런데 제가 여러분의 이해를 돕기 위해, 방금 전 이론(모듈 1, 2)을 설명하면서 이 실습 내용들을 미리 당겨서 함께 진행했습니다."
"다시 한번 복기(Review)해볼까요?"
"**모듈 3: 실습 1**에 해당하는 내용은 이미 완료했습니다.
•
'Vite 프로젝트 생성' (npm create vite) -> my-app 폴더 만들었죠.
•
'JSX 및 기본 컴포넌트' -> App.tsx 파일에 <h1> 태그도 넣어봤습니다.
•
'Props 전달 실습' -> MyButton.tsx 컴포넌트를 만들고, title이라는 Prop을 전달하는 실습까지 마쳤습니다."
"마찬가지로 **모듈 4: 실습 2**에 해당하는 내용도 방금 완료했습니다.
•
'useState Hook' -> import { useState } from 'react'로 가져왔고,
•
'이벤트 핸들링' -> const [buttonText, setButtonText] = useState(title) 코드로 onClick 이벤트를 처리해서, 버튼 텍스트가 바뀌는 것까지 day3_lab_useState.tsx [cite: 1-52] 파일로 실습을 완료했습니다."
"즉, 우리는 이미 React의 가장 핵심적인 '환경 구축', 'Props', 'State'라는 세 가지 기둥을 모두 실습한 셈입니다."
1. 실습 목표
•
React의 핵심 'State' 관리 및 이벤트 핸들링 실습.
2. useState Hook
•
함수형 컴포넌트에서 State를 관리하는 Hook.
•
import { useState } from 'react';
•
const [state, setState] = useState(initialValue);
3. 이벤트 핸들링
•
App.js에서 카운터(Counter) 예제 구현.
•
const [count, setCount] = useState(0);
•
<button onClick={() => setCount(count + 1)}>
•
버튼 클릭 시 setCount 함수가 호출되어 count State가 변경되고, UI가 자동으로 업데이트되는 것 확인.
모듈 5: 실습 3 - UI 구현 및 Hooks(useEffect), Routing
또 다른 핵심 Hook인 useEffect와 페이지 이동을 위한 React Router를 다뤄보겠습니다."
1. 실습 목표
•
HTML/CSS로 UI 레이아웃 구현, useEffect로 생명주기 관리, 페이지 이동 구현.
2. HTML/CSS in JSX
•
div, p, img, button, input 태그로 딥페이크 탐지 UI 구조화.
•
Inline 스타일: style={{ color: 'blue', fontSize: '16px' }} 적용.
•
Flexbox 레이아웃: style={{ display: 'flex', flexDirection: 'column' }} (수직 배치).
우선은 초기 기획단계에서 레이아웃을 디자인했던 부분을 React 애플리케이션 코드로 구현할 필요가 있습니다. 레이아웃과 버튼 등 필요 부분을 먼저 작업한 코드입니다.
3. useEffect Hook
•
컴포넌트의 생명주기(Mount, Update, Unmount) 관리.
•
Mount 시 1회 실행:
◦
useEffect(() => { console.log('컴포넌트 마운트됨'); }, []);
◦
(용도: 최초 데이터 로딩, API 호출).
4. React Router (페이지 이동)
•
클라이언트 사이드 라우팅 구현.
•
npm install react-router-dom
•
App.js (혹은 index.js) 설정.
•
<BrowserRouter>, <Routes>, <Route>로 페이지(Home, Result) 정의.
•
<Link to="/result"> 컴포넌트로 페이지 이동 링크 생성.
요약 및 차주 예고: 4일차 - 백엔드 (NestJS) 개발
•
3일차 성과: React로 딥페이크 탐지 웹 UI 뼈대 및 페이지 라우팅 구축 완료.
•
4일차 예고: 4일차 - 백엔드 개발 (NestJS). React와 통신할 API 서버 구축을 시작함.




























