let, const, varJavaScript에서는 주로 let, const를 사용한다.
let age = 20;
const name = "Kim";
let: 값 변경 가능const: 재할당 불가능var: 과거 문법. 함수 스코프와 호이스팅 문제 때문에 일반적으로 사용하지 않는다.let a = 10;
a = 20; // 가능
const b = 10;
b = 20; // Error
기본적으로 const를 사용하고, 값이 바뀌어야 할 때만 let을 사용하는 방식이 일반적이다.
JavaScript의 기본 자료형은 다음과 같다.
let num = 10; // number
let str = "hello"; // string
let bool = true; // boolean
let empty = null; // null
let value; // undefined
let big = 123n; // bigint
let id = Symbol("id"); // symbol
Java와 다르게 정수와 실수를 별도의 타입으로 구분하지 않고 대부분 number로 처리한다.
typeof 10; // "number"
typeof 3.14; // "number"
JavaScript는 동적 타입 언어이다.
변수를 선언할 때 타입을 지정하지 않고, 실행 중에 다른 타입의 값을 넣을 수도 있다.
let value = 10;
value = "hello";
value = true;
Java에서는 변수의 타입이 고정되지만, JavaScript와 Python은 변수에 저장되는 값에 따라 타입이 결정된다.
문자열은 ', ", 백틱 `을 사용할 수 있다.
const name = "Kim";
const age = 20;
백틱을 사용하면 문자열 안에 변수를 쉽게 넣을 수 있다.
const message = `이름은 ${name}, 나이는 ${age}살입니다.`;
이를 Template Literal이라고 한다.
JavaScript 배열은 서로 다른 타입의 값을 함께 저장할 수 있다.
const arr = [1, "hello", true];
주요 사용법:
arr.push(10);
arr.pop();
arr[0];
arr.length;
실제 웹 개발에서는 배열 자체보다 map, filter, find 같은 배열 메서드를 매우 자주 사용한다.
JavaScript에서 객체는 key-value 형태의 데이터를 저장한다.
const user = {
name: "Kim",
age: 20
};
값 접근:
user.name;
user["age"];
값 변경:
user.age = 21;
Java의 객체보다 Python의 dict와 비슷하게 느껴질 수 있지만, JavaScript에서는 객체가 언어 전체에서 매우 중요한 역할을 한다.
일반 함수:
function add(a, b) {
return a + b;
}
함수 표현식:
const add = function(a, b) {
return a + b;
};
화살표 함수:
const add = (a, b) => {
return a + b;
};
한 줄이면 더 줄일 수 있다.
const add = (a, b) => a + b;
웹 개발, 특히 React에서는 화살표 함수를 매우 자주 사용한다.
Java와 거의 비슷하다.
if (age >= 20) {
console.log("성인");
} else {
console.log("미성년자");
}
반복문:
for (let i = 0; i < 5; i++) {
console.log(i);
}
배열 순회에는 다음 방식도 자주 사용한다.
for (const value of arr) {
console.log(value);
}
배열이나 객체에서 값을 쉽게 꺼낼 수 있는 문법이다.
const user = {
name: "Kim",
age: 20
};
const { name, age } = user;
배열에서도 사용할 수 있다.
const arr = [10, 20];
const [a, b] = arr;
React에서 매우 많이 등장한다.
const [count, setCount] = useState(0);
...배열이나 객체의 값을 펼칠 때 사용한다.
const a = [1, 2];
const b = [...a, 3, 4];
객체:
const user = {
name: "Kim",
age: 20
};
const newUser = {
...user,
age: 21
};
React에서 상태를 변경할 때 특히 자주 사용한다.
let → 재할당 가능
const → 재할당 불가능
var → 가급적 사용하지 않음
JavaScript는 동적 타입 언어
배열 → []
객체 → {}
함수는 값처럼 변수에 저장 가능
() => {} → 화살표 함수
const { a, b } = obj
→ 구조 분해 할당
...arr / ...obj
→ Spread 문법
null vs undefined둘 다 “값이 없음”을 나타내지만 의미가 다르다.
let a;
console.log(a); // undefined
let b = null;
console.log(b); // null
undefined: 값이 아직 할당되지 않음null: 개발자가 의도적으로 “값 없음”을 넣음실무에서는 API 응답, 초기 상태값, optional 값 처리할 때 자주 만난다.
== vs ===1 == "1" // true
1 === "1" // false
==는 비교 전에 타입을 자동 변환한다.
===는 타입과 값이 모두 같은지 비교한다.
그래서 실무에서는 거의 항상:
if (a === b) {
}
처럼 ===를 사용한다.
JavaScript는 상황에 따라 타입을 자동으로 바꾼다.
"5" + 1 // "51"
"5" - 1 // 4
+는 문자열 연결로 동작할 수 있지만, -는 숫자 연산만 가능해서 숫자로 변환된다.
이런 암묵적 변환 때문에 JS에서 예상하지 못한 결과가 생길 수 있다.
JS에서는 boolean이 아니어도 조건문에서 참/거짓으로 판단된다.
Falsy 값:
false
0
""
null
undefined
NaN
이외 대부분의 값은 Truthy다.
if ("hello") {
console.log("실행됨");
}
특히 주의할 것:
[] // truthy
{} // truthy
빈 배열과 빈 객체도 true 취급된다.
NaNNaN은 Not a Number라는 뜻이다.
Number("hello"); // NaN
특이하게:
NaN === NaN // false
그래서 확인할 때는:
Number.isNaN(value);
을 사용한다.
typeof타입 확인:
typeof 10 // "number"
typeof "hello" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof {} // "object"
그런데 유명한 예외가 있다.
typeof null // "object"
이건 JS 초기 설계에서 생긴 오래된 버그성 동작이다.
undefined
→ 값이 아직 없음
null
→ 의도적으로 값 없음
==
→ 타입 변환 후 비교
===
→ 타입 + 값 비교
→ 실무에서 기본 사용
Truthy / Falsy
→ 조건문에서 값 자체가 true/false처럼 평가됨
[]와 {}는 truthy
NaN
→ 숫자로 변환할 수 없는 값
typeof null === "object"
→ JS의 오래된 특이사항
Scope는 변수에 접근할 수 있는 범위다.
const a = 10;
function test() {
const b = 20;
console.log(a); // 가능
console.log(b); // 가능
}
console.log(b); // 불가능
JS에는 크게 다음 범위가 있다.
Global Scope
Function Scope
Block Scope
let, const는 {} 단위로 범위가 나뉜다.
if (true) {
const a = 10;
}
console.log(a); // Error
반면 var는 block scope가 아니라 function scope를 따른다.
if (true) {
var a = 10;
}
console.log(a); // 10
이것도 var를 잘 쓰지 않는 이유 중 하나다.
현재 범위에 변수가 없으면 바깥 범위로 올라가며 찾는다.
const a = 10;
function outer() {
const b = 20;
function inner() {
console.log(a);
console.log(b);
}
inner();
}
inner()는 자신의 scope → outer → global 순서로 변수를 찾는다.
이 구조를 Scope Chain이라고 한다.
JS는 함수가 어디에서 호출됐는지가 아니라, 어디에서 선언됐는지를 기준으로 바깥 scope를 결정한다.
const x = 10;
function test() {
console.log(x);
}
function run() {
const x = 20;
test();
}
run(); // 10
test()가 run() 안에서 호출됐지만, 선언된 위치가 global이므로 x = 10을 본다.
이걸 Lexical Scope라고 한다.
JS는 코드를 실행할 때 Execution Context(실행 컨텍스트)를 만든다.
쉽게 말하면:
현재 실행 중인 코드의 변수, 함수, scope 정보를 관리하는 실행 환경
함수가 호출될 때마다 새로운 실행 컨텍스트가 생성된다.
function a() {
b();
}
function b() {
console.log("hello");
}
a();
개념적으로:
Global Execution Context
↓
a() Execution Context
↓
b() Execution Context
가 만들어진다.
Execution Context는 Call Stack에 쌓인다.
function first() {
second();
}
function second() {
console.log("hello");
}
first();
실행 흐름:
Global
↓
first()
↓
second()
↓
second 종료
↓
first 종료
Stack이기 때문에 LIFO(Last In First Out) 구조다.
이 Call Stack 개념은 나중에 싱글 스레드와 Event Loop를 이해할 때 핵심이 된다.
Scope
→ 변수에 접근할 수 있는 범위
let / const
→ block scope
var
→ function scope
Scope Chain
→ 변수를 현재 scope부터 바깥으로 찾아감
Lexical Scope
→ 함수가 선언된 위치 기준으로 scope 결정
Execution Context
→ 현재 실행 중인 코드의 환경 정보
Call Stack
→ 실행 컨텍스트가 쌓이는 구조
→ LIFO
JavaScript에서 함수는 변수에 저장하거나, 다른 함수에 전달하거나, 반환할 수 있다.
const add = (a, b) => a + b;
즉 함수도 하나의 값처럼 다룬다.
다른 함수에 인자로 전달되는 함수를 callback이라고 한다.
function run(callback) {
callback();
}
run(() => console.log("hello"));
웹 개발에서 이벤트, 비동기 처리, 배열 메서드 등에 매우 자주 사용된다.
arr.map(x => x * 2);
함수를 인자로 받거나 함수를 반환하는 함수를 고차 함수(Higher-Order Function)라고 한다.
대표적으로:
map()
filter()
reduce()
같은 배열 메서드가 있다.
Closure는 함수가 자신이 선언된 외부 Scope의 변수를 기억하는 특성이다.
function outer() {
let count = 0;
return () => {
count++;
return count;
};
}
const counter = outer();
counter(); // 1
counter(); // 2
outer() 실행은 끝났지만 내부 함수가 count를 계속 기억한다.
즉:
함수 + 함수가 선언될 당시의 외부 환경
= Closure
React의 state, event handler, hook을 이해할 때 중요하다.
thisthis는 현재 함수가 어떤 객체와 연결되어 실행되는지에 따라 값이 달라질 수 있다.
const user = {
name: "Kim",
hello() {
console.log(this.name);
}
};
user.hello(); // Kim
여기서 this는 user를 가리킨다.
this화살표 함수는 자신의 this를 새로 만들지 않는다.
바깥 Scope의 this를 그대로 사용한다.
const user = {
name: "Kim",
hello: () => {
console.log(this.name);
}
};
이 경우 this가 user를 가리킨다고 기대하면 안 된다.
그래서 객체 메서드에서는 일반 함수 문법을 사용하는 경우가 많다.
JS에서 함수는 값처럼 사용 가능
Callback
→ 다른 함수에 전달되는 함수
고차 함수
→ 함수를 인자로 받거나 반환하는 함수
Closure
→ 함수가 외부 Scope의 변수를 기억하는 특성
this
→ 함수가 어떻게 호출되었는지에 따라 결정
화살표 함수
→ 자신의 this를 만들지 않음
→ 바깥 this 사용
JS에서 객체와 배열은 값을 직접 복사하는 게 아니라 참조값을 다룬다.
const a = { name: "Kim" };
const b = a;
b.name = "Lee";
console.log(a.name); // Lee
a, b가 같은 객체를 가리키기 때문이다.
Spread를 사용하면 새 객체를 만들 수 있다.
const a = { name: "Kim", age: 20 };
const b = { ...a };
하지만 중첩 객체는 여전히 참조가 공유될 수 있다.
const a = {
user: { name: "Kim" }
};
const b = { ...a };
b.user.name = "Lee";
console.log(a.user.name); // Lee
즉 ...은 기본적으로 얕은 복사(Shallow Copy)다.
객체는 내용이 아니라 참조값을 비교한다.
{} === {} // false
반면:
const a = {};
const b = a;
a === b // true
같은 객체를 가리킬 때만 true다.
JavaScript 객체는 다른 객체를 기반으로 기능을 상속받을 수 있다.
이 연결 구조가 Prototype이다.
예를 들어 배열에서:
const arr = [1, 2, 3];
arr.map(...)
arr.filter(...)
map, filter를 우리가 직접 만든 적이 없는데 사용할 수 있는 이유는 배열이 Array.prototype의 메서드를 상속받기 때문이다.
개념적으로:
arr
↓
Array.prototype
↓
Object.prototype
이렇게 prototype chain을 따라 메서드를 찾는다.
객체에서 어떤 속성이나 메서드를 찾을 때:
현재 객체에서 찾음
↓
없으면 prototype에서 찾음
↓
또 없으면 상위 prototype에서 찾음
이 구조를 Prototype Chain이라고 한다.
JS에서도 Java처럼 class를 사용할 수 있다.
class User {
constructor(name) {
this.name = name;
}
hello() {
console.log(`hello ${this.name}`);
}
}
const user = new User("Kim");
하지만 JS의 class는 본질적으로 prototype 기반 구조를 더 익숙하게 표현한 문법이다.
class Animal {
move() {
console.log("move");
}
}
class Dog extends Animal {
bark() {
console.log("bark");
}
}
Java와 비슷하게 보이지만 내부적으로는 prototype chain을 사용한다.
객체 / 배열
→ 참조 타입
const b = a
→ 같은 객체 참조
{ ...a }
→ 새 객체 생성
→ 하지만 얕은 복사
{} === {}
→ false
→ 객체는 참조값 비교
Prototype
→ 다른 객체의 기능을 상속받는 구조
Prototype Chain
→ 속성/메서드를 상위 prototype으로 찾아감
class
→ prototype 기반 객체 시스템을 감싼 문법
JavaScript는 기본적으로 한 번에 하나의 작업만 실행한다.
작업 A 실행
→ 작업 A 종료
→ 작업 B 실행
즉 JS 코드를 실행하는 Call Stack은 하나다.
브라우저 환경에서는 오래 걸리는 작업을 브라우저가 대신 처리한다.
대표적으로:
setTimeout(...)
fetch(...)
개념적으로:
JavaScript
→ Call Stack
Browser
→ Timer
→ Network
→ DOM Event
JS 자체는 한 번에 하나만 실행하지만, 외부 작업은 브라우저가 처리할 수 있다.
비동기 작업이 끝나면 callback이 Queue에 들어간다.
Event Loop는 계속 확인한다.
Call Stack이 비었나?
↓
비었으면 Queue의 작업 실행
이 구조 덕분에 싱글 스레드에서도 비동기 처리가 가능하다.
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
결과:
A
C
B
setTimeout(..., 0)이라고 해도 바로 실행되는 것이 아니다.
callback은 Queue에서 기다리고, 현재 동기 코드가 모두 끝난 뒤 실행된다.
동기:
const a = 1;
const b = 2;
console.log(a + b);
앞 작업이 끝나야 다음 작업이 실행된다.
비동기:
fetch("/api/users");
console.log("next");
네트워크 응답을 기다리는 동안 다음 코드가 계속 실행될 수 있다.
초기 JS에서는 비동기 처리를 callback으로 많이 했다.
setTimeout(() => {
console.log("done");
}, 1000);
하지만 여러 비동기 작업이 중첩되면 코드가 복잡해진다.
a(() => {
b(() => {
c(() => {
});
});
});
이런 형태를 흔히 Callback Hell이라고 한다.
Promise는 비동기 작업의 결과를 표현하는 객체다.
상태는 3개다.
pending
→ 진행 중
fulfilled
→ 성공
rejected
→ 실패
사용 예:
fetch("/api/users")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
Promise를 더 읽기 쉽게 사용하는 문법이다.
async function getUsers() {
const response = await fetch("/api/users");
const data = await response.json();
return data;
}
await는 Promise가 처리될 때까지 해당 async 함수의 다음 실행을 잠시 멈춘다.
중요한 점은 JS 전체 스레드를 멈추는 것은 아니다.
비동기 Queue가 하나만 있는 것은 아니다.
대표적으로:
Microtask Queue
→ Promise
→ async/await 후속 작업
Task Queue
→ setTimeout
→ setInterval
→ 일부 이벤트
일반적으로 현재 코드가 끝난 후:
Call Stack
↓
Microtask Queue
↓
Task Queue
순서로 처리된다.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve()
.then(() => console.log("C"));
console.log("D");
결과:
A
D
C
B
이유:
동기 코드
→ A, D
Microtask
→ C
Task
→ B
JavaScript
→ 싱글 스레드
→ Call Stack 하나
비동기 작업
→ 브라우저 Web API가 처리
작업 완료
→ Queue로 이동
Event Loop
→ Call Stack이 비면 Queue 작업 실행
Promise
→ 비동기 작업의 상태와 결과 표현
async / await
→ Promise를 읽기 쉽게 사용하는 문법
실행 우선순위
→ 동기 코드
→ Microtask
→ Task Queue
브라우저는 HTML, CSS, JavaScript를 함께 처리한다.
HTML → 구조
CSS → 스타일
JavaScript → 동작
JavaScript는 브라우저가 제공하는 API를 통해 화면과 상호작용한다.
DOM(Document Object Model)은 HTML 문서를 JavaScript가 다룰 수 있는 객체 구조로 표현한 것이다.
<button id="btn">클릭</button>
const btn = document.querySelector("#btn");
여기서 document는 현재 HTML 문서를 나타내는 객체다.
요소 찾기:
const title = document.querySelector(".title");
내용 변경:
title.textContent = "Hello";
스타일 변경:
title.style.fontSize = "20px";
요소 생성:
const div = document.createElement("div");
div.textContent = "new";
window와 document브라우저에서 가장 상위 객체는 보통 window다.
window.alert("hello");
window.location;
document도 window 아래에 있다.
개념적으로:
window
└─ document
└─ HTML DOM
사용자의 행동이나 브라우저의 상태 변화를 Event라고 한다.
대표적인 이벤트:
click
input
change
submit
keydown
load
scroll
이벤트 등록:
btn.addEventListener("click", () => {
console.log("clicked");
});
이벤트 핸들러에는 이벤트 정보가 전달된다.
btn.addEventListener("click", (event) => {
console.log(event.target);
});
event.target은 실제 이벤트가 발생한 요소를 의미한다.
이벤트는 기본적으로 자식 요소에서 부모 요소 방향으로 전파된다.
<div id="parent">
<button id="child">Click</button>
</div>
버튼 클릭 시:
button
→ div
→ body
→ document
방향으로 이벤트가 올라간다.
이를 Event Bubbling이라고 한다.
반대로 부모에서 자식 방향으로 내려가는 단계도 있다.
document
→ body
→ div
→ button
이를 Capturing이라고 한다.
전체 흐름은:
Capturing
↓
Target
↓
Bubbling
이다.
preventDefault()브라우저의 기본 동작을 막는다.
예를 들어 form은 submit 시 기본적으로 페이지를 이동하거나 새로고침할 수 있다.
form.addEventListener("submit", (event) => {
event.preventDefault();
});
SPA나 React에서도 자주 접한다.
stopPropagation()이벤트가 부모로 전파되는 것을 막는다.
button.addEventListener("click", (event) => {
event.stopPropagation();
});
즉:
preventDefault()
→ 브라우저 기본 동작 차단
stopPropagation()
→ 이벤트 전파 차단
DOM
→ HTML을 JS에서 다룰 수 있게 객체 구조로 표현
document
→ 현재 HTML 문서
window
→ 브라우저의 최상위 객체
Event
→ 클릭, 입력, 제출 등 사용자/브라우저 동작
Event Bubbling
→ 자식 → 부모
Event Capturing
→ 부모 → 자식
preventDefault()
→ 기본 동작 차단
stopPropagation()
→ 이벤트 전파 차단
fetch브라우저에서 HTTP 요청을 보낼 때 사용하는 기본 API다.
const response = await fetch("/api/users");
const data = await response.json();
흐름은:
브라우저
→ HTTP 요청
→ 서버
→ HTTP 응답
→ JSON 변환
주로 쓰는 메서드:
GET → 조회
POST → 생성
PUT → 전체 수정
PATCH → 일부 수정
DELETE → 삭제
예:
await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Kim"
})
});
프론트와 백엔드가 데이터를 주고받을 때 매우 자주 사용한다.
JS 객체:
const user = {
name: "Kim",
age: 20
};
JSON 문자열로 변환:
JSON.stringify(user);
JSON 문자열을 객체로 변환:
JSON.parse(json);
fetch에서 주의할 점fetch는 404, 500 응답이 와도 Promise 자체는 보통 reject되지 않는다.
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error("요청 실패");
}
즉:
네트워크 자체 실패
→ reject
HTTP 404 / 500
→ 응답은 정상적으로 도착
→ response.ok 확인 필요
브라우저는 보안을 위해 다른 출처의 리소스 접근을 제한한다.
Origin은 보통 다음 3개로 결정된다.
protocol + host + port
예:
https://example.com:443
이 중 하나라도 다르면 다른 Origin으로 본다.
CORS는 서버가 브라우저에게:
"이 Origin에서 오는 요청은 허용해도 된다"
라고 알려주는 방식이다.
예를 들어:
Frontend
http://localhost:3000
Backend
http://localhost:8080
포트가 다르므로 서로 다른 Origin이다.
서버가 적절한 CORS 헤더를 주지 않으면 브라우저가 요청을 차단할 수 있다.
중요한 점:
CORS는 브라우저의 보안 정책이다.
서버끼리 통신할 때는 일반적으로 CORS 문제가 발생하지 않는다.
브라우저에 저장되는 작은 데이터다.
서버가:
Set-Cookie
헤더를 보내면 브라우저가 저장하고 이후 요청에 자동으로 포함할 수 있다.
주로:
로그인 상태
세션 ID
사용자 설정
등에 사용한다.
Session 방식은 로그인 정보를 주로 서버에서 관리한다.
로그인
↓
서버가 Session 생성
↓
Session ID 발급
↓
브라우저 Cookie에 저장
↓
이후 요청마다 Session ID 전달
서버는 Session ID를 보고 사용자를 식별한다.
JWT 방식에서는 사용자 정보를 포함한 토큰을 클라이언트에 전달한다.
로그인
↓
서버가 JWT 발급
↓
클라이언트가 저장
↓
요청마다 JWT 전달
주로:
Authorization: Bearer <token>
형태로 전달한다.
핵심 차이는 상태를 어디서 관리하느냐다.
Session
→ 서버가 로그인 상태 관리
JWT
→ 토큰 자체에 인증 정보 포함
단, JWT라고 해서 무조건 더 좋거나 더 최신 방식인 것은 아니다.
서비스 구조와 보안 요구사항에 따라 선택한다.
fetch
→ 브라우저 HTTP 요청 API
JSON.stringify()
→ JS 객체 → JSON 문자열
JSON.parse()
→ JSON 문자열 → JS 객체
response.ok
→ HTTP 요청 성공 여부 확인
Same-Origin Policy
→ 브라우저가 다른 출처 접근 제한
CORS
→ 서버가 다른 Origin의 요청을 허용하는 방식
Cookie
→ 브라우저에 저장되는 데이터
Session
→ 서버 중심 인증 상태 관리
JWT
→ 토큰 기반 인증
Node.js는 브라우저 밖에서도 JavaScript를 실행할 수 있게 해주는 런타임 환경이다.
JavaScript = 언어
Browser
→ JavaScript + Browser API
Node.js
→ JavaScript + Node API
즉 Node.js가 있으면 서버, CLI, 빌드 도구 등을 JS로 만들 수 있다.
npm은 Node.js 패키지 관리자다.
npm install axios
외부 라이브러리를 설치하고 관리할 수 있다.
대표 명령:
npm install
→ 의존성 설치
npm install 패키지명
→ 패키지 추가
npm run dev
→ package.json에 정의된 dev 명령 실행
package.json프로젝트 정보와 의존성을 관리하는 파일이다.
{
"name": "my-app",
"scripts": {
"dev": "vite"
},
"dependencies": {
"react": "^19.0.0"
}
}
주요 역할:
프로젝트 정보
의존성 목록
실행 명령어
빌드 설정
node_modulesnpm으로 설치한 라이브러리들이 들어가는 폴더다.
node_modules/
보통 용량이 크기 때문에 Git에는 올리지 않는다.
대신 package.json과 lock 파일을 공유하고:
npm install
로 다시 설치한다.
코드를 여러 파일로 나누고 필요한 것만 가져오는 방식이다.
export function add(a, b) {
return a + b;
}
다른 파일:
import { add } from "./math.js";
이런 방식을 ES Module(ESM)이라고 한다.
Node.js에서는 과거에 CommonJS 방식도 많이 사용했다.
const express = require("express");
내보내기:
module.exports = something;
현재는 ES Module 방식도 많이 사용한다.
import express from "express";
웹 프로젝트는 파일이 많아지기 때문에 여러 파일과 의존성을 정리해 브라우저가 사용할 수 있게 묶는 과정이 필요할 수 있다.
이 역할을 하는 도구가 Bundler다.
대표적으로:
Webpack
Rollup
esbuild
Vite는 현대 프론트엔드 개발에서 많이 사용하는 개발 서버 + 빌드 도구다.
npm create vite@latest
npm run dev
주요 역할:
개발 서버 실행
빠른 코드 변경 반영
모듈 처리
프로덕션 빌드
React 프로젝트 만들 때 자주 보게 된다.
대략 이렇게 연결된다.
JavaScript 코드
↓
npm으로 라이브러리 관리
↓
package.json에 의존성 기록
↓
Vite로 개발 서버 실행
↓
빌드
↓
브라우저에서 실행
Node.js
→ 브라우저 밖에서 JS 실행
npm
→ JS 패키지 관리자
package.json
→ 프로젝트 설정 + 의존성 + 실행 명령
node_modules
→ 설치된 패키지 저장
ES Module
→ import / export
CommonJS
→ require / module.exports
Bundler
→ 여러 파일과 의존성을 웹 실행 형태로 묶음
Vite
→ 개발 서버 + 빌드 도구
React는 새로운 언어가 아니라 JavaScript를 기반으로 UI를 만드는 라이브러리다. 그래서 아래 JS 개념을 잘 알면 React 코드가 훨씬 쉽게 읽힌다.
React에서 매우 자주 등장한다.
const user = {
name: "Kim",
age: 20
};
const { name, age } = user;
배열 구조 분해:
const [a, b] = [10, 20];
React의 useState도 같은 문법이다.
const [count, setCount] = useState(0);
React에서는 기존 객체를 직접 수정하기보다 새 객체를 만드는 방식을 많이 사용한다.
const user = {
name: "Kim",
age: 20
};
const newUser = {
...user,
age: 21
};
배열도 동일하다.
const newArr = [...arr, 4];
핵심은:
기존 값 직접 변경 X
→ 새로운 객체/배열 생성
이걸 불변성(immutability)이라고 한다.
map배열을 다른 배열로 변환한다.
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
React에서는 리스트 렌더링에 매우 자주 사용한다.
users.map(user => (
<div>{user.name}</div>
));
filter조건에 맞는 값만 남긴다.
const nums = [1, 2, 3, 4];
const even = nums.filter(n => n % 2 === 0);
삭제 처리에도 자주 사용한다.
const newUsers = users.filter(user => user.id !== id);
find조건에 맞는 첫 번째 값을 찾는다.
const user = users.find(user => user.id === 3);
API 응답이나 상태에서 특정 데이터를 찾을 때 자주 사용한다.
삼항 연산자:
const message = login ? "로그인됨" : "로그인 필요";
React:
{login ? <Main /> : <Login />}
AND 연산자도 자주 사용한다.
{login && <Profile />}
login이 truthy일 때만 <Profile />이 렌더링된다.
값이 없을 수도 있을 때 안전하게 접근한다.
user.profile.name
profile이 undefined면 에러가 발생한다.
그래서:
user?.profile?.name
처럼 사용할 수 있다.
API 데이터를 다룰 때 특히 자주 등장한다.
null 또는 undefined일 때 기본값을 사용한다.
const name = user.name ?? "Unknown";
||와 차이가 있다.
0 || 100 // 100
0 ?? 100 // 0
??는 null, undefined만 비어 있는 값으로 판단한다.
React 이벤트도 결국 함수를 전달하는 구조다.
function handleClick() {
console.log("click");
}
<button onClick={handleClick}>
Click
</button>
즉:
onClick
→ callback 함수 전달
이다.
React의 함수 컴포넌트와 Hook에서는 closure가 중요하다.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
console.log(count);
}
handleClick은 선언될 당시의 count 값을 참조한다.
그래서 React에서 흔히 말하는:
stale closure
문제도 JS의 closure 특성과 관련되어 있다.
API 호출:
async function getUsers() {
const response = await fetch("/api/users");
const data = await response.json();
return data;
}
React에서는 이런 비동기 작업을 상태와 연결한다.
개념적으로:
API 요청
↓
응답
↓
state 변경
↓
컴포넌트 다시 렌더링
React를 이해하려면 Promise, async/await가 중요한 이유다.
React에서 특히 중요한 JS
구조 분해 할당
→ const { name } = user
Spread
→ ...obj, ...arr
불변성
→ 기존 값 수정 대신 새 값 생성
map
→ 배열 변환 / 리스트 렌더링
filter
→ 조건에 맞는 값만 유지
find
→ 특정 값 검색
?.
→ 안전한 속성 접근
??
→ null/undefined 기본값 처리
Callback
→ 이벤트 처리
Closure
→ Hook과 상태 이해에 중요
async/await
→ API 통신에 필수