React의 장점:
컴포넌트(Header, Main, Footer)를 기반으로 UI를 표현함
Header.js, Main.js, Footer.js처럼 각각의 컴포넌트를 각각의 .js파일로 표현가능 ➡️ 유지보수가 용이!
기존의 html 코드들:

리엑트:


화면 업데이트 구현이 쉬움
업데이트란? 사용자의 행동(클릭, 드래그)에 따라 웹페이지가 스스로 모습을 바꾸어 상호작용하는 것 (예: 버튼을 클릭하면 화면이 바뀜)
업데이트 구현이 쉬운 이유: 선언형 프로그래밍(과정은 생략하고 목적을 작성하는 방식)이므로 업데이트를 위한 복잡한 동작을 직접 정의할 필요 없이 특정 변수의 값을 바꾸는 것만으로도 화면을 업데이트 시킬 수 있음

리엑트의 방식:

랜더링이란? UI요소를 화면에 그려내는 것
화면 업데이트가 빠르게 처리됨

업데이트 과정:
(1) HTML과 CSS를 각각 DOM과 CSS Object Model로 변환

(2) DOM(요소들의 위치, 배치, 모양에 관한 모든 정보)과 CSS Object Model(요소들의 스타일과 관한 모든 정보)을 웹페이지의 설계도라고 할 수 있는 Render Tree로 변환
(3) Render Tree에 포함된 요소의 배치를 잡는 작업인 Layout 과정 수행
(4) 요소를 실제로 화면에 그려내는 Painting 수행
업데이트 발생 시점: JavaScript가 DOM을 수정했을 때!
이 때, 다른 작업들에 비해 특히 Layout과 Painting을 다시 하는 것은 굉장히 오래 걸리는 작업이므로 따로 Reflow(Layout을 다시 한다), Repaint(Painting을 다시 한다)의 용어가 존재함
✨ DOM을 여러번 수정하면 지연시간이 지나치게 늘어나서 worst case가 되므로 동시에 발생하는 업데이트들을 모아서 한번에 DOM을 수정해야 하는데, 이 과정을 React는 자동으로 해결해줌!
내부적으로 Virtual DOM이라는 가상의 DOM을 사용해서 업데이트가 발생하면 실제 DOM을 수정하기 전에 가상의 DOM에 먼저 반영해봄

Vite라는 기본 설정이 적용된 React App 생성 가능한 프론트엔드 개발툴 사용)npm create vite@latest 명령어로 vite 설치
node-modules 폴더가 없으므로 npm i 명령어로 package.json에는 기재되어있지만 내 로컬에는 설치되지 않은 기타 라이브러리들을 한꺼번에 설치해주기
public 폴더: 코드가 아닌 .svg, .jpg, .png 등의 이미지 파일, 폰트, 동영상 등의 정적인 파일들을 보관하는 저장소npm run dev 명령어를 통해 vite가 React app을 작동시키게 함 ➡️ h를 눌러서 도움말도 확인하고, 제공된 url로 접속해서 하나씩 확인해보기// main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx' // App.jsx 파일을 확인하면, HTML 태그들을 return하는 함수를 포함한다는 것을 알 수 있음!
// 화면에 표시되는 내용들은 App 파일에서 수정 가능
import './index.css'
// .createRoot 메소드: 인수로 전달받은 HTML 요소를 React의 root로 만들어줌
// .render: App 컴포넌트를 렌더링함
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App /> // </>는 컴포넌트를 나타내는 문법
</React.StrictMode>,
)
r + enter: restart the serveru + enter: show server urlo + enter: open in browserc + enter: clear consoleq + enter: quitmodule.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
"no-unused-vars": "off", // 실제로 사용하지 않는 코드가 있을 때 오류를 표시하는 기능을 끄기
"react/prop-types": "off" // 아직 배우지 않은 부분의 기능 끄기
},
}
HTML 태그들을 return하는 함수로, 반드시 첫글자를 대문자로 작성해야 컴포넌트로 인정받을 수 있으므로 주의해야 함!
main.jsx의 ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)
함수 안에 있는 App 컴포넌트는 이후에 생성되는 모든 컴포넌트들의 조상이며, 이런 컴포넌트를 root 컴포넌트라고도 부름!
이후 생성되는 모든 컴포넌트들은 src > components 폴더를 생성한 후 해당 폴더에 아래와 같이 새 .jsx 파일을 생성하고 export로 내보내기
function Header() {
return (
<header>
<h1>Header</h1>
</header>
)
}
export default Header;
이를 App.jsx 파일에서 불러와서, 아래와 같이 App 컴포넌트 내에 추가해주는 식으로 연결해줌
import './App.css'
import Header from './components/Header' // 불러옴
// React에서는 확장자 생략 가능 (ES모듈시스템과는 좀다름)
function App() {
return (
<>
<Header/> // 추가됨
<h1>Hey React!</h1>
</>
)
}
export default App
확장된 자바스크립트의 문법으로, JavaScript와 HTML을 혼용해서 사용할 수 있음 (HTML return, 랜더링 등)
function Main(){
const number = 10;
return(
<main>
<h1>main</h1>
<h2>{number % 2 === 0 ? "짝수" : "홀수"}</h2>
</main>
);
}
export default Main;
✨ JSX 주의사항
{} 내에는 자바스크립트 표현식 (삼항연산자, 어떤 값, 어떤 변수)만을 넣을 수 있음. if문, for문은 사용이 불가함.객체.아이디로 하나의 값을 넣어줘야 함)<img>태그같은 몇몇 태그들은 단독으로도 사용이 가능했으나, jsx에서는 반드시 <img></img>로 닫아줘야 함<head>, <body>, <div> 등의 다양한 최상위태그가 화면을 부분부분으로 구분하며 하나의 파일 안에 존재하는 반면, jsx 파일에서는 최상위태그는 하나여야 한다. 만약 최상위태그가 꼭 필요하지 않은 파일이라면 <></>처럼 빈 태그로라도 설정해야 한다. // 방법 1
function Main(){
const user = {
name : "김민지",
isLogin : true,
}
return(
<>
{user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}
</>
);
}
조건문 ? 조건문이 true일 때 실행될 명령문 : 조건문이 false일 때 실행될 명령문// 방법 2
function Main(){
const user = {
name : "김민지",
isLogin : true,
}
if (user.isLogin) {
return <div>로그아웃</div>
} else {
return <div>로그인</div>
}
}
function Main(){
const user = {
name : "김민지",
isLogin : true,
}
if (user.isLogin) {
return <div style = {{
backgroundColor:"beige", // 카멜표기법으로 표기해야함
borderBottom: "5px dotted pink" // CSS랑 다름!!!
}}>로그아웃</div>
} else {
return <div>로그인</div>
}
}
export default Main;
.logout {
background-color: beige;
border-bottom: 5px dotted pink;
}
import "./Main.css";
function Main() {
const user = {
name: "김민지",
isLogin: true,
};
if (user.isLogin) {
return <div className="logout">로그아웃</div>;
} else {
return <div>로그인</div>;
}
}
export default Main;
데이터를 전달하는 기능을 수행하며, 마치 함수를 호출하듯 작동함


// App.jsx
import './App.css'
import Header from './components/Header' // 확장자 생략 가능
import Main from './components/Main'
import Footer from './components/Footer'
import Button from './components/Button'
function App() {
// 만약 전달해야 할 props가 늘어난다면??
const buttonProps = {
text: "메일",
color: "red",
}
return (
<>
<Button {...buttonProps}/>
<Button text={"카페"}/>
<Button text={"블로그"}/>
</>
)
}
export default App
// Button.jsx
function Button(props) {
return <button style={{
color: props.color
}}>{props.text} - {props.color.toUpperCase()}</button>
}
Button.defaultProps = {
color: "black"
}
export default Button;
// 표현방식만 변경:
function Button({text, color}) {
return <button style={{
color: color
}}>{text} - {color.toUpperCase()}</button>
}
Button.defaultProps = {
color: "black"
}
export default Button;
✨ React 19 버전부터는 defaultProps의 사용이 중지됨
대체방안:
const App = ( {data = '기본값} ) => { ... }

function Button({text, color, children}) {
return <button onClick={() => {
console.log(text)
}}
style={{
color: color
}}>{text} - {color.toUpperCase()}
{children}
</button>
}
Button.defaultProps = {
color: "black"
}
export default Button;
// 함수가 길 경우
function Button({text, color, children}) {
function onClickButton() {
console.log(text)
}
return <button
onClick={onClickButton} // click
onMouseEnter={onClickButton} // 마우스 hover
style={{
color: color
}}>{text} - {color.toUpperCase()}
{children}
</button>
}
Button.defaultProps = {
color: "black"
}
export default Button;
function onClickButton(e) { // e : 합성이벤트객체
console.log(text)
}

useState()는 2개의 값을 배열 형태로 묶어서 반환함 → 초기값: [undefined, f]
state라고 받아옴)로, useState(상태값)의 괄호 안 상태값에 넣은 값이 그대로 저장됨setState로 받아옴)useState() 함수 설명import './App.css'
// 함수 컴포넌트에서 state 생성하기 위해서 react에서 제공하는 내장함수 호출하기
import { useState } from 'react';
function App() {
// const state = useState(); 이렇게 단일변수에 할당받기보다는,
const [state, setState] = useState(0); // 이렇게 구조분해로 할당받는 것이 일반적임
// 버튼을 하나 만들어서 사용자가 해당 버튼을 클릭할 때마다 state의 값 증가시키기
return (
<>
<h1>{state}</h1>
<button onClick={()=>{
setState(state + 1);
}}>
+
</button>
</>
)
}
export default App
// state -> count로 바꿔서 명명
import './App.css'
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
// 전구 상태 표시하기
const [light, setLight] = useState("OFF");
return (
<>
<div>
<h1>{light}</h1>
<button onClick = {() => {
setLight(light === "ON" ? "OFF" : "ON")
}}>
{light === "ON" ? "스위치 끄기" : "스위치 켜기"}
</button>
</div>
<div>
<h1>{count}</h1>
<button onClick={()=>{
setCount(count + 1);
}}>
+
</button>
</div>
</>
)
}
export default App
import './App.css'
import { useState } from 'react';
function Bulb({light}) { // App 컴포넌트의 자식컴포넌트임
console.log(light) // 리렌더링 될때마다 출력됨
// 아무 상관없어보이는 App컴포넌트의 + 버튼을 눌렀을 때에도 함께 리렌더링됨 (비효율적인 코드임!!)
return <div>{light === "ON" ?
<h1 style={{backgroundColor: 'orange'}}>ON</h1>
:
<h1 style={{backgroundColor: "grey"}}>OFF</h1>}</div>
}
function App() {
const [count, setCount] = useState(0);
// 전구 상태 표시하기
const [light, setLight] = useState("OFF");
return (
<>
<div>
<Bulb light = {light}/>
<button onClick = {() => {
setLight(light === "ON" ? "OFF" : "ON")
}}>
{light === "ON" ? "스위치 끄기" : "스위치 켜기"}
</button>
</div>
<div>
<h1>{count}</h1>
<button onClick={()=>{
setCount(count + 1);
}}>
+
</button>
</div>
</>
)
}
export default App
// 개선된 코드:
import './App.css'
import { useState } from 'react';
function Bulb() {
const [light, setLight] = useState("OFF");
console.log(light);
return(
<div>
{light === "ON" ?
<h1 style={{backgroundColor: 'orange'}}>ON</h1> :
<h1 style={{backgroundColor: "grey"}}>OFF</h1>
}
<button onClick = {() => {
setLight(light === "ON" ? "OFF" : "ON");
}}>
{light === "ON" ? "스위치 끄기" : "스위치 켜기"}
</button>
</div>
)
}
function Count() {
const [count, setCount] = useState(0);
return(
<div>
<h1>{count}</h1>
<button onClick={()=>{
setCount(count + 1);
}}>
+
</button>
</div>
)
}
function App() {
return (
<>
<Bulb />
<Count />
</>
)
}
export default App
Bulb와 Count 함수를 별도의 jsx 파일로 생성해서 export하고, App.jsx 파일에서 import해서 사용할 수 있음// initial code
import {useState} from "react";
function Register() {
const [name, setName] = useState() // 초기값 설정: useState()
const [birth, setBirth] = useState()
const [country, setCountry] = useState()
const [bio, setBio] = useState()
const onChangeName = (e) => {
setName(e.target.value)
}
const onChangeBirth = (e) => {
setBirth(e.target.value)
}
const onChangeCountry = (e) => {
setCountry(e.target.value)
}
const onChangeBio = (e) => {
setBio(e.target.value)
}
return ( // 이름, 생일, 국적,
<>
<h1>Register</h1>
<div>
<div>
<input
placeholder="이름"
onChange = {onChangeName}
value = {name}
/>
</div>
<div>
<input
type="date"
onChange = {onChangeBirth}
value={birth}
/>
</div>
<div>
<select value = {country} onChange = {onChangeCountry}>
<option value="">선택없음</option>
<option value="us">미국</option>
<option value="ch">중국</option>
<option value="ge">독일</option>
<option value="kr">한국</option>
</select>
{country}
</div>
<div>
<textarea value={bio} onChange={onChangeBio}/>
{bio}
</div>
</div>
</>
)
}
export default Register;
[input, setInput]을 사용해서 하나로 통합// updated compact code
import {useState} from "react";
function Register() {
const [input, setInput] = useState({
name : "",
birth : "",
country : "",
bio : ""
});
// input을 간단한 객체 형태로 변경함
// input.name, input.birth, ... 로 불러와야 함
console.log(input)
const onChangeName = (e) => {
setInput({
...input,
// 해당 함수와 관련없는 값들인 birth, country, bio는 그대로 유지되도록 spread 형태로 작성
name: e.target.value // name만 수정
})
}
const onChangeBirth = (e) => {
setInput({
...input,
birth: e.target.value
})
}
const onChangeCountry = (e) => {
setInput({
...input,
country: e.target.value
}) }
const onChangeBio = (e) => {
setInput({
...input,
bio: e.target.value
}) }
return (
<>
<h1>Register</h1>
<div>
<div>
<input
placeholder="이름"
onChange = {onChangeName}
value = {input.name}
/>
</div>
<div>
<input
type="date"
onChange = {onChangeBirth}
value={input.birth}
/>
</div>
<div>
<select value = {input.country} onChange = {onChangeCountry}>
<option value="">선택없음</option>
<option value="us">미국</option>
<option value="ch">중국</option>
<option value="ge">독일</option>
<option value="kr">한국</option>
</select>
</div>
<div>
<textarea value={input.bio} onChange={onChangeBio}/>
</div>
</div>
</>
)
}
export default Register;
onChange 함수 하나로 통합import {useState} from "react";
function Register() {
const [input, setInput] = useState({
name : "",
birth : "",
country : "",
bio : ""
});
console.log(input)
const onChange = (e) => {
setInput({
...input,
[e.target.name]: e.target.value,
// 변수 e.target.name을 []로 감싸서 객체 생성 시 key로 사용!
// e.target은 return문의 각 html 자식태그들(input, input, select, textarea)이고,
// .name은 각 태그 내에 name=""로 설정해준 정보
});
};
return (
<>
<h1>Register</h1>
<div>
<div>
<input
name = "name"
placeholder="이름"
onChange = {onChange}
value = {input.name}
/>
</div>
<div>
<input
name="birth"
type="date"
onChange = {onChange}
value={input.birth}
/>
</div>
<div>
<select name="country" value = {input.country} onChange = {onChange}>
<option value="">선택없음</option>
<option value="us">미국</option>
<option value="ch">중국</option>
<option value="ge">독일</option>
<option value="kr">한국</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange}/>
</div>
</div>
</>
)
}
export default Register;
import {useState, useRef} from "react";
function Register() {
const [input, setInput] = useState({
name : "",
birth : "",
country : "",
bio : ""
});
const countRef = useRef(0); // 최선의 방법!
// 그냥 let count = 0; 으로 선언해서 사용하면 안되는 이유?
// 컴포넌트가 리랜더링 될때마다 초기화되므로!
const inputRef = useRef();
// console.log(countRef.current);
console.log(input)
const onChange = (e) => {
countRef.current ++;
console.log(countRef.current);
setInput({
...input,
[e.target.name]: e.target.value,
});
};
const onSubmit = () => {
if (input.name === ""){
// 이름을 입력하는 DOM 요소 포커스
inputRef.current.focus();
}
}
return (
<>
<h1>Register</h1>
<div>
<div>
<input
ref = {inputRef}
name = "name"
placeholder="이름"
onChange = {onChange}
value = {input.name}
/>
</div>
<div>
<input
name="birth"
type="date"
onChange = {onChange}
value={input.birth}
/>
</div>
<div>
<select name="country" value = {input.country} onChange = {onChange}>
<option value="">선택없음</option>
<option value="us">미국</option>
<option value="ch">중국</option>
<option value="ge">독일</option>
<option value="kr">한국</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange}/>
</div>
<button onClick={onSubmit}>
제출
</button>
</div>
</>
)
}
export default Register;
useState는 State 기능을 낚아채오는 Hook이고, useRef는 Reference 기능을 낚아채오는 Hook임✨ React Hooks 규칙
use 붙이기src 폴더에서 새 폴더로 hooks 폴더 생성 > hooks 폴더 내부에 새 파일로 use 붙인 함수 옮기고 export, import문 작성import { useState } from "react";
function useInput() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value)
}
return [input, onChange]
}
function HookExam() {
const [input, onChange] = useInput();
return (
<div>
<input value = {input} onChange={onChange} />
</div>
)
}
export default HookExam;
LifeCycle 제어: Mount 단계에서 서버에서 데이터를 불러오거나 Update 단계에서 변경된 값을 콘솔에 출력하고, UnMount 단계에서 컴포넌트가 사용하던 메모리를 정리하는 등의 동작들을 수행하는 것
useEffect란? 리엑트 컴포넌트의 side effect를 제어하는 새로운 react Hook
useEffect(()=>{}, [])에서 []를 deps(dependency array)라고 부름
// App.jsx만 수정
import './App.css'
import Viewer from './components/Viewer'
import Controller from './components/Controller'
import {useState, useEffect } from "react"
function App() {
const [count, setCount] = useState(0);
const [input, setInput] = useState("");
useEffect(() => {
console.log(`count: ${count} / input: ${input}`); // count의 값이 바뀔 때마다 콘솔에 출력
}, [count, input]);
// []로 전달한 배열의 값이 바뀌게 될 때마다
// sideEffect로서 ()=>{}로 전달한 함수의 동작을 실행함
// 만약 input의 값이 바뀔때마다도 useEffect함수를 실행하고 싶다면? []안에 input도 추가!
const onClickButton = (value) => {
setCount(count + value) // value는 버튼의 -1, +10 등의 값들!
}
return (
<div className = "App">
<h1>Simple Counter</h1>
<section>
<input value={input} onChange={(e)=>{
setInput(e.target.value);
}} />
</section>
<section>
<Viewer count = {count}/>
</section>
<section>
<Controller onClickButton = {onClickButton}/>
</section>
</div>
);
}
export default App

import './App.css'
import Viewer from './components/Viewer'
import Controller from './components/Controller'
import {useState, useEffect, useRef } from "react"
// useRef: 브라우저가 불려온건지 아닌지 체크
import Even from './components/Even'
function App() {
const [count, setCount] = useState(0);
const [input, setInput] = useState("");
const isMount = useRef(false);
// 1. Mount
useEffect(()=>{
console.log("Mount")
}, [])
// []가 빈 배열인 경우, 오로지 처음 랜더링 될 때만 실행됨 (=Mount)
// 2. Update
// useEffect(()=>{
// console.log("Update")
// })
// []를 생략하는 경우, Mount 될 때 + Update 될 때마다 실행됨
// 예: 버튼을 누를 때마다, input 태그 안의 text를 수정할 때마다 Update가 출력될 것
// 만약 Update 될 때마다'만' 실행하고 싶다면??
useEffect(()=>{
if (!isMount.current){
isMount.current = true;
return;
}
console.log("Update")
})
// 3. UnMount
const onClickButton = (value) => {
setCount(count + value)
}
return (
<div className = "App">
<h1>Simple Counter</h1>
<section>
<input value={input} onChange={(e)=>{
setInput(e.target.value);
}} />
</section>
<section>
<Viewer count = {count}/>
{count % 2 === 0 ? <Even /> : null}
</section>
<section>
<Controller onClickButton = {onClickButton}/>
</section>
</div>
);
}
export default App
import {useEffect} from "react";
function Even() {
useEffect(()=>{
return () => {
console.log("UnMount"); // Even component가 사라질 때마다 출력됨
};
}, []);
return <div>Even</div>
}
export default Even;
React 개발자 도구 다운로드 링크 :
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko
각 컴포넌트들, 각 컴포넌트가 받고 있는 props들, 사용하고 있는 hooks 들 등을 확인 가능