Parent re-render 시 child 함수 전체 호출, 범위는 App 아래 전체 (Q3, Q10)
Class Component
Parent.render() → Child.render() → DOM 변경
Function Component
Parent() → Child() → DOM 변경
Parent re-render 시 child 함수 전체 호출, 범위는 App 아래 전체 (Q3, Q10)
Class Component
Parent.render() → Child.render() → DOM 변경
Function Component
Parent() → Child() → DOM 변경
bg-${color}-500
// ❌ Tailwind가 class를 발견하지 못할 수 있음 <div className={<code>bg-${color}-500} />
왜 그럴까? Tailwind는 소스 코드에서 완전한 Class Name을 찾아 필요한 CSS를 생성하기 때문이야. 따라서 이렇게 해야 해.
// ✅ 완전한 class 이름이 코드에 존재
<div className={ selected ? 'bg-red-800' : 'bg-white' } />Metadata vs Full List
two response DTO
DTO
DTO defines the shape of data transferred between the client and server, exposing only the necessary fields.
DTO가 name과 items를 보내라고 정하고, JSON으로 실제 데이터를 표현해서 Request에 담는 거야.
그리고 서버가 Response를 보낼 때는 또 다른 DTO에 따라 id, name, itemCount, completedCount를 JSON으로 보내는 거지.
딱 이것만 기억해 Client │ │ Request: { name, items } │ ▼ Server │ │ DB에 저장 │ │ Response: { id, name, itemCount, completedCount } │ ▼ Client
여기서 Request와 Response에 어떤 필드가 들어가는지를 정하는 게 DTO야.
Vite Proxy
브라우저는 9000번 포트에 요청하지만, 실제 API 처리는 4000번 포트의 Express가 한다
[lists, isLoaded]
[lists, isLoaded] Mount 후 실행 + 둘 중 하나라도 변경 시
err.status
요청 상황 문제 결과 PATCH /api/movies/유효한ID + rating: "hello" 데이터 타입이 잘못됨 400 DELETE /api/movies/존재하지않는유효한ID 영화가 DB에 없음 404 GET /api/bananas 그런 API 경로가 없음 404
Errors: Handled in One Place
app.use()는 지금 함수를 실행하라는 뜻이 아니라, 나중에 요청이 들어오면 Express가 실행할 함수로 등록하는 것이야.
GET /api/bananas
app.use('/api/movies', movieRouter);
app.use(notFound);
app.use(errorHandler);
GET /api/bananas
↓
Express가 등록된 Router 확인
↓
/api/movies 와 일치하지 않음
↓
다음 Middleware로 이동
↓
notFound(req, res, next) 실행
↓
next(new HttpError(404, ...))
↓
Express가 Error Handler 탐색
↓
errorHandler(err, req, res, next) 실행
↓
404 + JSON 응답
mongoose.connect(MONGODB_URI)
DB 연결 세팅
const { PORT = 3000, MONGODB_URI } = process.env;
process.env는 설정값 읽기
...students
여기서 ...students는 기존 배열의 요소를 펼쳐 넣는 Spread Syntax야.
Decides how long the state lasts:app 전체를 감싸면: 전체 session 동안 유지한 화면(screen)만 감싸면: 그 화면을 떠나면 사라진다.
Provider Component가 useState()로 State를 관리하고 있다면, 그 Provider Component가 Mount된 상태인 동안 State가 유지된다는 뜻이야.
Decides who can see the state: provider 안에 있는 component만 접근할 수 있다
① Decides who can see the state
Provider가 감싸는 범위에 있는 하위 컴포넌트만 그 Provider가 제공한 Context 값을 읽을 수 있다는 뜻이야.
Provider 밖의 컴포넌트가 useContext()를 호출하면 해당 Provider의 값을 읽지 못하고, 다른 상위 Provider가 없다면 createContext(defaultValue)의 기본값을 사용해.
Roster
학생 명단을 가져와 화면에 보여주는 사람
RosterContext
그 명단을 다른 컴포넌트가 읽을 수 있게 해주는 통로
RosterProvider
학생 명단을 가지고 있는 사람
[students]
Dependency Array
localStorage.setItem
그 문자열을 브라우저 저장 공간의 'roster'라는 이름으로 저장해.
JSON.stringify
JavaScript 배열을 JSON 문자열로 바꿔.
() =>
Lazy Initialization 기억나?
초기값을 계산하는 함수를 useState에 전달해서 처음 State를 만들 때만 그 계산을 하도록 하는 방식이야.
[]
③ ?? []
이것도 새로운 문법이지? 값 ?? 기본값
은 왼쪽 값이 null 또는 undefined이면 오른쪽 값을 사용한다는 뜻이야.
JSON.parse
JSON.parse('[{"id":"1","name":"Joe","year":2026}]')
를 실행하면: [ { id: "1", name: "Joe", year: 2026 } ]
localStorage.getItem('roster')
① localStorage.getItem('roster') localStorage는 브라우저가 데이터를 저장할 수 있는 공간이야.
예를 들어 예전에 학생 목록을 저장했다고 해 보자. roster → '[{"id":"1","name":"Joe","year":2026}]'
localStorage.getItem('roster')는 이 저장된 문자열을 가져오는 거야. 저장된 값이 없으면 null을 반환해.
2. Provider component
function ThemeProvider({ children }) { const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
{children}
```</ThemeContext.Provider>```
); }
1. Context provider
<ThemeContext.Provider value={theme}> <Roster /> </ThemeContext.Provider>
: component가 읽는 context value가 바뀌었을 때. props가 바뀌지 않았어도 re-render된다
어떤 컴포넌트가 useContext()로 Context 값을 사용하고 있다면,
Props가 그대로여도 해당 Context 값이 바뀌었을 때 그 컴포넌트가 다시 렌더링된다.
context는 state variable 묶음에 대한 key다
const UserContext = createContext(null);
function App() {
const [name, setName] = useState("Joe"); const [age, setAge] = useState(20);
return (
<UserContext.Provider value={{ name, age, setName }}>
<Profile />
```</UserContext.Provider>```
); }
여기서 두 종류의 {}가 등장해. value={{ name, age, setName }}
바깥 {} → JSX에서 JavaScript 표현식을 전달
안쪽 {} → name, age, setName이 들어 있는 객체 생성
paint한 뒤에
① Render Clock() 함수 실행, JSX 계산
② DOM Update 화면의 시간 텍스트 변경
③ Browser Paint 변경된 화면을 실제로 그림
④ useEffect 실행 타이머 설정 또는 브라우저 제목 갱
effect 하나가 method 한 쌍을 대체한다
예전 Class에서는 브라우저 제목을 바꾸려고 두 번 작성해야 했어. componentDidMount() { document.title = this.state.now.toLocaleTimeString(); }
componentDidUpdate() { document.title = this.state.now.toLocaleTimeString(); }
왜? - 처음 Mount했을 때도 제목을 설정해야 하고 - 시간이 바뀔 때도 제목을 수정해야 하니까 그런데 Hooks에서는: useEffect(() => { document.title = now.toLocaleTimeString(); }, [now]);
이 코드 한 번으로 두 상황을 처리할 수 있어.
[stateVar]
function Clock() { const [now, setNow] = useState(new Date());
// 타이머 담당
useEffect(() => { const timerId = setInterval(() => { setNow(new Date()); }, 1000);
return () => clearInterval(timerId);
}, []);
// 브라우저 제목 담당
useEffect(() => { document.title = now.toLocaleTimeString(); }, [now]);
return
{now.toLocaleTimeString()}
; }code가 timing이 아니라 purpose 기준으로 묶인다
function Clock() { const [now, setNow] = useState(new Date());
// 타이머 담당 useEffect(() => { const timerId = setInterval(() => { setNow(new Date()); }, 1000);
return () => clearInterval(timerId);
}, []);
// 브라우저 제목 담당 useEffect(() => { document.title = now.toLocaleTimeString(); }, [now]);
return
{now.toLocaleTimeString()}
; }다시 실행되기 전
① State 변경 → Update 요청 ↓ ② React가 컴포넌트 함수 다시 호출 (Render) ↓ ③ React가 DOM 업데이트 ↓ ④ 이전 Effect의 Cleanup 실행 (필요하다면) ↓ ⑤ 새로운 Effect 실행
즉, Cleanup은 Render 중에 실행되는 게 아니야!
cleanup,
useEffect(() => { console.log("Effect 실행:", now);
return () => { console.log("Cleanup 실행:", now); }; }, [now]);
now가 바뀌면 실행 순서는 다음과 같아. 시점 실행 순서 처음 Mount Effect 실행: 12:00:00
1초 후 Update Cleanup 실행: 12:00:00 → Effect 실행: 12:00:01
다시 1초 후 Update Cleanup 실행: 12:00:01 → Effect 실행: 12:00:02
Unmount Cleanup 실행: 12:00:02
return () => clearInterval(timerId);
ide 클릭 ↓ visible = false ↓ Clock Unmount ↓ 첫 번째 Effect Cleanup 실행 ↓ clearInterval(timerId) ↓ 타이머 중지
각 render는 자기만의 count를 가진다.
함수가 다시 실행될 때마다 새로운 count 지역 변수가 생긴다
now
이 Effect는 now에 의존해. 처음 Mount한 후 실행하고, 이후 now가 바뀐 Render에서는 다시 실행해 줘
useState(() => loadFromStorage())
첫 번째 Render (Mount) → React가 초기화 함수 실행 → loadFromStorage() 호출 → 100을 초기 State로 저장
두 번째 Render (Update) → 기존 State 사용 → loadFromStorage() 실행하지 않음
세 번째 Render (Update) → 기존 State 사용 → loadFromStorage() 실행하지 않음
useState(loadFromStorage())
첫 번째 Render → loadFromStorage() 실행 → useState(100)
두 번째 Render → loadFromStorage() 또 실행 → useState(100)
세 번째 Render → loadFromStorage() 또 실행 → useState(100)
React는 초기값 100을 첫 Mount에서만 사용하지만, JavaScript는 렌더링할 때마다 loadFromStorage()를 실행해 버려. 즉, 불필요한 계산이 반복되는 문제가 있어.
React가 넘겨주는 latest value 사용. stale 문제 없음
이전 State를 바탕으로 새 State를 계산할 때는 setCount(c => c + 1)을 사용하면 오래된 렌더링의 count 값에 의존하지 않아.
render 동안은 constant다
그 렌더링에서 받은 count는 바뀌지 않는다
각 hook call은 그 list의 다음 slot을 읽거나 쓴다
React는 Hook 호출 순서로 저장 칸을 찾기 때문에, 매번 Render할 때 Hook이 같은 순서로 실행되어야 한다.
click handler나 timer에서 useState를호출하면 error다.
function Counter() {
function handleClick() {
const [count, setCount] = useState(0); // ❌
}
return <button onClick={handleClick}>Click</button>;
}
control.addEventListener("click", controller.processGoHome);
element.addEventListener("이벤트 종류", 실행할 함수);
부분 의미 element 이벤트를 감지할 HTML 요소
addEventListener 이벤트 리스너 등록
"click" 어떤 이벤트를 기다릴지
실행할 함수 이벤트 발생 시 실행할 Callback
++++ control.addEventListener("click", controller.processGoHome());-> totally different = exectues as soon as it is regeistered not clicked
Getting Nodes
HTML에 이미 존재하는 요소를 JavaScript에서 가져오는 거야.
<div id="container">Hello
World
</div>코드 기능 결과 document.getElementById("container") id로 찾기 요소 하나 또는 null
document.getElementsByClassName("text") class로 찾기 HTMLCollection
document.querySelector(".text") CSS 선택자로 찾기 첫 번째 일치 요소
document.querySelectorAll(".text") CSS 선택자로 전부 찾기 NodeList
“Also beware, inside an event handler function, this may not be what you think it is.” (Slide 11에서도 반복된 강조)
obj.method() → this = obj
element.addEventListener("click", obj.method) → 일반 함수라면 클릭 시 this = element
element.addEventListener("click", () => obj.method()) → method 내부에서 this = obj
wrapper 객체
Wrapper = 실제 DOM 요소를 감싸서 jQuery의 편리한 기능을 사용할 수 있게 해주는 객체.
jQuery
“Always be aware of what type an element is.” (교수님 강조)
기능 Native DOM (a) jQuery (b)
내용 변경 a.innerHTML = "Hi" b.html("Hi")
이벤트 등록 a.addEventListener("click", foo) b.on("click", foo)
CSS 변경 a.style.color = "red" b.css("color", "red")
요소 삭제 a.remove() b.remove()
Event Handling
방법 A. addEventListener() button.addEventListener("click", callback);
방법 B. onclick 속성 button.onclick = callback;
방법 C. HTML에 이벤트 지정
<button onclick="callback()">Click</button>
Slide 13: 응답 가능한 Events (전체 목록)
4-2. 이벤트 종류 (Slide 13) 이벤트 언제 발생? click 클릭 load 로딩 완료 unload 문서에서 벗어나는 과정 change 값 변경이 확정될 때 mouseover 마우스가 요소 안으로 들어올 때 mouseout 마우스가 요소 밖으로 나갈 때 mousedown 마우스 버튼 누름 mouseup 마우스 버튼 뗌
$.getJSON(jsonFilePath, function (json) {
$.getJSON()은 jQuery에서 JSON 데이터를 비동기적으로 가져오는 메서드야.
jsonFilePath는 파일 경로.
function(json) { ... }는 데이터를 성공적으로 가져왔을 때 실행할 Callback이야.
json에는 가져온 JSON 데이터가 들어와.
function foo(jsonFilePath) {
foo("./data.json");
Slide 5: Make new nodes
document.createElement("div") 새로운 div 생성 X document.createTextNode("Hi") 텍스트 노드 생성 X parent.appendChild(child) 부모에게 자식 추가 O parent.removeChild(child) 부모에게서 자식 삭제 O child.remove() 자기 자신 삭제 X child.parentNode 부모 노드 참조 X
lide 4: Updating the DOM
코드 종류 하는 일 element.innerHTML = "Hi" 속성 HTML 내용 수정 element.textContent = "Hi" 속성 텍스트 내용 수정 element.setAttribute("id", "box") 메서드 HTML 속성 지정 element.style.color = "red" 속성 글자 색상 수정 element.classList.add("active") 메서드 class 추가
child를 지우는 건 parent의 일)
// ① 삭제할 자식 가져오기 let myParagraph = document.getElementById("my_paragraph");
// ② 부모 노드 가져오기 let parent = myParagraph.parentNode;
// ③ 부모가 자식 삭제하기 parent.removeChild(myParagraph);
parent node
```html
<body> <div id="container">Hello!
</div> </body>```
div#container = Parent Nodep#my_paragraph = Child Node비교 연산자
JavaScript 형변환 핵심 규칙 5개 규칙 기억할 것 대표 예시 ① ===, !== 타입 변환 없이 비교 1 === "1" → false
② ==, != 필요하면 타입을 변환해서 비교 1 == "1" → true
③ + 문자열이 있으면 연결, 아니면 숫자 덧셈 "5" + 3 → "53"
④ -, *, /, % 숫자로 변환해서 계산 "5" - 3 → 2
⑤ <, >, <=, >= 둘 다 문자열이면 사전식, 그 외에는 보통 숫자로 비교 "2" > "12" → true
type="module"
그 파일을 ES Module로 처리 → import/export 사용 가능, 기본 defer
margin
테두리(Border) 바깥쪽 (주변 요소와의 거리)
padding
콘텐츠(Content)와 테두리(Border) 사이
border-radius: 25px;
border: 2px solid black;
• 역할: 요소의 바깥쪽에 직선(또는 점선 등)의 테두리 선을 그립니다.
font-family:monospace;
글꼴
Movie.aggregate
Aggregate는 여러 Document를 모아서 계산하거나 통계를 만드는 기능이야. 예를 들어 DB에 영화들이 이렇게 있다고 해보자. 영화 장르 평점 A Drama 8 B Drama 6 C Comedy 9 D Comedy 7 E Comedy 8 첫 번째 단계: $group $group: { _id: '$genre', count: { $sum: 1 }, avgRating: { $avg: '$rating' } }
한 부분씩 해석하면: - _id: '$genre' → genre 값이 같은 영화끼리 그룹 만들기 - count: { $sum: 1 } → 그룹 안의 영화 개수 세기 - avgRating: { $avg: '$rating' } → 그룹 안 영화들의 평균 평점 구하기 여기서 '$genre'는 genre라는 필드의 값을 참조한다는 뜻이야. 문자열 "genre" 자체로 그룹을 만들라는 뜻이 아니야. 따라서 첫 번째 단계 결과는: 장르 (_id) count avgRating Drama 2 7 Comedy 3 8
{ _id: id }
위치 코드 의미
첫 번째: Filter { _id: id } 어떤 영화를 수정할까?
year: { $gte: 2000 }
$gte는 greater than or equal, 즉 >=라는 뜻이야.
limit
최대 10개
sort
그리고 '-rating'에서 -는 내림차순이야.
lean()
일반 JavaScript 객체로 받기
next(new HttpError(404, `No route for ${req.method} ${req.originalUrl}`));
next(err)는 Express에 "에러가 발생했으니 다음 일반 함수 대신 Error Handler를 실행해 줘"
no route matched
app.use('/api/movies', movieRouter);
로 등록했다고 해보자. 그러면 서버는 다음 요청들을 처리할 수 있어.
Client 요청 일치하는 Route
GET /api/movies movies.list
POST /api/movies movies.create
GET /api/movies/42 movies.get
DELETE /api/movies/42 movies.remove
GET /api/bananas ❌ 없음
PATCH /api/users/42 ❌ 없음 (이 Router 기준)
Movie.findByIdAndUpdate
Movie.findByIdAndUpdate(id, update, options);
인자는 총 3개야. 인자 역할 PPT 코드
첫 번째 id 어떤 Document를 수정할까? req.params.id
두 번째 update 어떤 값으로 수정할까? { title, year, genre, rating }
세 번째 options 수정할 때 추가 설정은? { returnDocument: 'after', runValidators: true }
const movies = await Movie.find(genre ? { genre: String(genre) } : {}).sort(SORTS.includes(sort) ? sort : 'title').skip((page - 1) * PAGE_SIZE).limit(PAGE_SIZE).select('title year genre rating').lean();res.json(movies);
let query = Movie.find({ genre: "Drama" });
query = query.sort("-year");
query = query.skip(20);
query = query.limit(20);
query = query.select("title year genre rating");
query = query.lean();
const movies = await query;
res.json(movies);
const { genre, sort } = req.query;
const genre = req.query.genre; // "Drama" const sort = req.query.sort; // "-year"
const page = Math.max(1, Number(req.query.page) || 1);
복잡해 보이지만 페이지 번호를 최소 1로 만드는 코드야.
genre=Drama&sort=-year&page=2
Query String
import mongoose from 'mongoose';
Mongoose (전체 라이브러리) │ ├── connect() → MongoDB 연결 │ ├── Schema → Document 구조 정의 │ └── Model → Document CRUD 수행 │ ├── find() 조회 ├── create() 생성 ├── updateOne() 수정 └── deleteOne() 삭제
createApp().listen(PORT
은 3000번 포트에서 Client의 HTTP 요청을 받을 수 있도록 서버를 시작한다는 뜻이야.
const response = await fetch(`/api/movies/${id}`);
async function showMovie() { const result = await fetch("/api/movies/42");
console.log(result.status); }
이 함수 안에서는:
fetch()가 Promise를 반환해.
await에서 showMovie 함수가 잠시 멈춰.
Promise가 성공적으로 완료되면 실제 Response 객체를 꺼내 result에 넣어.
result.status를 출력해. 예를 들어 200.
async
async를 붙이면 그 함수 안에서 await를 사용할 수 있고, 함수는 항상 Promise를 반환해.
next)
즉, next()는 Client에게 응답하는 함수가 아니라 Express 내부에서 다음 처리 단계로 이동시키는 함수야.
app.use() 로 추가하며, 추가한 순서대로 실행된다 (Middleware Execution Order).
"The order is the behavior." 이건 등록 순서가 실제 실행 순서를 결정하기 때문에 중요하다는 뜻이야.
예를 들어:
app.use(express.json());
app.use(logger);
app.post("/api/movies", createMovie);
이렇게 등록하면 일반적으로:
express.json()이 JSON Body를 해석
logger가 요청 기록
Router가 /api/movies POST 요청을 매칭
createMovie Controller 실행
순서가 되는 거야.
반대로 express.json()을 Controller보다 뒤에 등록하면 Controller에서 JSON Body를 제대로 사용하지 못할 수 있어.
app.METHOD(PATH, HANDLER)
Client │ GET /api/movies ▼
Router │ "이 요청은 getMovies가 처리해!" ▼
Controller: getMovies() │ Movie.find() 호출 ▼
Model → MongoDB
// Controller 함수
async function getMovies(req, res) {
const movies = await Movie.find();
res.json(movies);
}
// Routing 설정 app.get("/api/movies", getMovies);
Middleware parse JSON, log|vRouter method + URL|vController does the work
Express = HTTP 요청을 받아 Middleware, Router, Controller로 처리하는 서버 프레임워크
GraphQL (Facebook, 2015)
REST는 Resource에 따라 여러 Endpoint를 두는 방식이고, GraphQL은 보통 하나의 Endpoint에 서로 다른 Query를 보내 원하는 데이터를 지정하는 방식
URL (path)
① Client가 요청을 보냄
GET /api/movies/42?genre=Drama │ │ │ └─ Query String └─ URL Path
↓
② Express Controller가 값 추출
req.params.id → "42" req.query.genre → "Drama"
↓
③ Controller가 Mongoose 함수 호출
Movie.find({ genre: "Drama" })
↓
④ MongoDB에서 실제 데이터 검색
↓
⑤ Controller가 Client에게 JSON 응답
Express 접근
req.params / req.query / req.body = Request에서 값 꺼내기
response: status code + JSON
Status Code = 성공인지 오류인지
JSON = 서버가 돌려주는 실제 데이터
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation’);
{ name: { first: "John", last: "Ghost" }, occupation: "Developer" }
new Schema( {...}, {...} )
구분 required: true timestamps: true
역할 특정 필드의 필수 여부 설정 날짜 자동 관리 기능 설정
적용 대상 개별 Field Schema 전체
코드 위치 첫 번째 인자 안 두 번째 인자 안
의미 이 필드가 필요해! 생성·수정 시간을 관리해!
Model
Model은 schema에서 제공해준 형태의 Document 하나가 아니라,
Document를 생성·조회·관리하는 기능을 제공한다고 이해하면 돼.
Schema
② Schema — 데이터의 형식 정의
const studentSchema = new mongoose.Schema({
name: String,
year: Number
});
Document(embedded/nested document)
MongoDB가 _id를 자동으로 붙여 주는 대상은 Collection의 최상위 Document이고,
그 안에 들어 있는 Embedded Document에는 자동으로 붙이지 않아.
12 bytes
언제, 어느 기계에서, 어떤 프로세스가 만들었는지와 카운터 값을 조합해 ID가 겹치지 않도록 설계한 것이야.
MappingAPI
ODM
ODM Object-Document Mapping 프로그램 객체 ↔ Document DB의 문서
ORM
ORM Object-Relational Mapping 프로그램 객체 ↔ 관계형 DB의 테이블
data for one session only
session = 그 탭을 열어서 사용하다가 닫을 때까지의 기간
if (document.cookie.split(';').filter(function(item) {return item.indexOf('username=') >= 0}).length)
// ① 쿠키 문자열을 배열로 나눔 const cookies = document.cookie.split(';');
// ② username=이 포함된 항목만 남김 const matches = cookies.filter(function(item) { return item.indexOf('username=') >= 0; });
// ③ 남은 항목 개수 확인 const count = matches.length;
// ④ 하나 이상 있으면 출력 if (count) { console.log('A username cookie was found'); }
myCookie.replace(regex, "$1")
let text = "username=Joe";
let result = text.replace( /username=(.*)/, "$1" );
console.log(result); // "Joe"
정규표현식을 분해하면: username=(.*) └─┘ ↑ 첫 번째 그룹
encryption
원본 메시지: Hello Joe Encryption + Key
암호화된 데이터 읽을 수 없는 형태의 바이트 데이터
Decryption + Key
복호화된 메시지: Hello Joe
compression
압축 전후 개념 압축 전 예시 데이터 10글자
AAAAAAAAAA
Compression
10 × A
encoding
사람이 보는 문자 A
UTF-8 인코딩
컴퓨터에 저장되는 바이트 01000001 A를 나타내는 비트 패턴
this.setState
너의 now State를 새 시간으로 업데이트하고 화면을 다시 계산해 줘. -> react에게 요청(rendering)
this.state.now.toLocaleTimeString()
toLocaleTimeString()은 Date 객체를 시간 문자열로 바꾸는 메서드야.
new Date()
new Date()는 현재 날짜와 시간을 나타내는 Date 객체를 만들어.
this.timerId
this.timerId = 5; // ✅ 일반 인스턴스 속성 변경 this.state.now = new Date(); // ❌ State 직접 수정 this.setState({ now: new Date() }); // ✅ State 업데이트
핵심은 this.를 붙였다고 전부 State가 되는 건 아니라는 거야!
한 줄 암기: state에 없어도 this.속성이름 = 값으로 인스턴스 속성을 새로 만들 수 있다. 단, React 화면에 영향을 주는 State는 setState()로 변경해야 한다.
document.title
document.title은 브라우저 탭에 표시되는 제목
setInterval
setInterval()은 정해진 시간 간격으로 함수를 반복 실행하는 JavaScript 함수
setInterval(실행할함수, 시간간격);
Update
단계 주요 흐름 Render Parent → Child Snapshot Child → Parent DOM Update 실제 변경 반영 DidUpdate Child → Parent
Mount
Mount = 내려갔다가 다시 올라오기! Parent render ↓ Child render ↓ Child mounted ↑ Parent mounted
Unmounting
Unmounting — 제거될 때
componentWillUnmount()
DOM Remove
Updating
Updating — State / Props가 변경될 때
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
DOM Update
componentDidUpdate()
Mounting
Mounting — 처음 생성될 때
constructor()
render()
DOM Update
componentDidMount()
hook method
React가 특정 Lifecycle 시점에 호출하는 메서드
메서드 역할 render() 현재 Props/State로 UI 계산
componentDidMount() 처음 DOM에 추가된 후 작업
componentDidUpdate() 업데이트가 반영된 후 작업
componentWillUnmount() DOM에서 제거되기 전 정리
Lifecycle
Lifecycle = 컴포넌트가 생성 → 업데이트 → 제거되는 과정
Click →
StudentRow에서 Jane 클릭
onSelect({selectedId}) 호출
App.setState({{selectedId}}) 요청
React가 App.render() 재실행
Roster와 StudentRow 렌더링
selected 결과에 따라 > 표시 변경
<section>
<section>은 <h2>와 <ul>을 하나의 Root Element로 묶기 위해 사용한 거야.
반드시 <section>일 필요는 없으며 <div>나 Fragment로 대체할 수도 있어.
const { title, students, selectedId, onSelect } = this.props;
destructing -> 굳이 this.props.속성 이렇게 사용 안 해도 됨
const title = this.props.title;
const students = this.props.students;
const selectedId = this.props.selectedId;
const onSelect = this.props.onSelect;
onSelect={this.handleSelectStudent}
함수 자체를 전달하는 거야
students={this.state.students}
는 JavaScript 변수/속성의 값을 읽어 전달하니까 {}가 필요해.
{
여기서 {}는 JavaScript 객체를 만드는 중괄호야.
State
시험에서 가장 헷갈리는 중괄호 정리 코드 {} 의미 state = { … } State 객체 생성
{ id: 1, name: 'Joe' } 학생 객체 생성
(id) => { … } 함수 본문
this.setState({ selectedId: id }) 변경할 state 속성을 객체로 전달
students={this.state.students} JSX에서 JavaScript 값 사용
onSelect={this.handleSelectStudent} JSX에서 함수 객체 전달
destructure
function Greeting({ name })은 function Greeting(props)에서 props.name을 일일이 쓰지 않도록 name을 바로 꺼내는 축약 문법이야
name="Joe"
React는 이 데이터를 Props로 전달해. props = { name: "Joe" }
B. Class-based vs Functional Components (S6–S8)
비교 Class-based Functional
정의 방식 class Greeting extends Component function Greeting(props)
React 상속 필요 필요 없음
JSX 반환 render() 안에서 함수에서 직접
Props 접근 this.props.name props.name
사용 방법 <Greeting name="Joe" /> <Greeting name="Joe" />
jsx
여기서 jsx()는 React의 JSX 변환 과정에서 사용하는 함수야. 쉽게 다음 형태로 이해하면 돼.
jsx(태그이름, { 속성들, children: 자식내용 });
fragment <>...</> 사용
```function App() {
return (
<>
<h1>Hello</h1>
<p>Welcome</p>
</>
); }```
안에 any JavaScript expression 사용 가능
일반 JavaScript에서는 평소처럼 코드를 쓰고, JSX 내부에서 JavaScript 값을 끼워 넣을 때만 {}를 사용하면 돼!
코드 {} 의미 function App() { ... } 함수 본문을 감싸는 중괄호
<h1>{name}</h1> JSX 안에서 JS 표현식 사용
onClick={() => alert("Hi")} JSX 속성에 JS 함수 전달
Single root
틀린 코드 — 최상위 요소가 2개
function App() {
return (
<h1>Hello</h1>
<p>Welcome</p>
); }
build tool이 각 <tag> 를 function call로 compile
JSX는 개발자가 편하게 작성하는 문법이고, Build Tool은 이를 브라우저에서 실행할 수 있는 JavaScript로 번역해 주는 거야.
B. Class-based vs Functional Components (S6–S8)
비교 Class-based Functional
정의 방식 class Greeting extends Component function Greeting(props)
React 상속 필요 필요 없음
JSX 반환 render() 안에서 함수에서 직접
Props 접근 this.props.name props.name
사용 방법 <Greeting name="Joe" /> <Greeting name="Joe" />
Single root
틀린 코드 — 최상위 요소가 2개
function App() {
return (
```<h1>Hello</h1>```
```<p>Welcome</p>```
); }
State mutation
값의 변경
Update는 즉시 일어나지 않는다
console.log(this.state.selectedId); // null
this.setState({ selectedId: 2 });
console.log(this.state.selectedId); // ?
강의자료의 설명에 따르면 두 번째 console.log()도 null이야.
왜냐하면 setState()는 변경을 즉시 완료하는 게 아니라, React에 State 업데이트를 예약(schedule)하기 때문이야.
실행 흐름 (슬라이드 기준) 1. 현재 selectedId = null 2. setState({ selectedId: 2 }) 요청 3. 바로 다음 줄에서는 여전히 이전 값 null을 읽음 4. React가 State 변경을 처리 5. 새 State를 바탕으로 다시 Render
never assign to it directly
JavaScript 객체의 값은 바뀔 수 있지만, React는 이 대입만으로 화면을 다시 렌더링해야 한다는 사실을 알지 못해.
this.state.selectedId = 2;
component lifecycle functions
class App extends Component {
componentDidMount() {
// 컴포넌트가 DOM에 추가된 후 실행
// Data fetch, timer 시작 등
}
render() {
// 화면에 보여줄 JSX 계산
```return <h1>Hello!</h1>;```
} }
setState 호출
반복 렌더링 유발 가능
document 사용
React의 DOM 관리와 충돌할 수 있음
data fetch
서버 요청이 반복될 수 있음
timer
렌더마다 타이머 중복 생성 가능
setState
setState() 반복 렌더링 유발 가능
Click
StudentRow에서 Jane 클릭
onSelect({selectedId}) 호출
App.setState({{selectedId}}) 요청
React가 App.render() 재실행
Roster와 StudentRow 렌더링
selected 결과에 따라 > 표시 변경
<section>
<section>은 <h2>와 <ul>을 하나의 Root Element로 묶기 위해 사용한 거야.
반드시 <section>일 필요는 없으며 <div>나 Fragment로 대체할 수도 있어.
const { title, students, selectedId, onSelect } = this.props;
destructing -> 굳이 this.props.속성 이렇게 사용 안 해도 됨
const title = this.props.title;
const students = this.props.students;
const selectedId = this.props.selectedId;
const onSelect = this.props.onSelect;
destructure
function Greeting({ name })은 function Greeting(props)에서 props.name을 일일이 쓰지 않도록 name을 바로 꺼내는 축약 문법이야
State
state = { ... } State 객체 생성
{ id: 1, name: 'Joe' } 학생 객체 생성
(id) => { ... } 함수 본문
this.setState({ selectedId: id }) 변경할 state 속성을 객체로 전달
students={this.state.students} JSX에서 JavaScript 값 사용
onSelect={this.handleSelectStudent} JSX에서 함수 객체 전달
onSelect={this.handleSelectStudent}
함수 자체를 전달하는 거야
students={this.state.students}
는 JavaScript 변수/속성의 값을 읽어 전달하니까 {}가 필요해.
{
여기서 {}는 JavaScript 객체를 만드는 중괄호야.
name="Joe"
React는 이 데이터를 Props로 전달해. props = { name: "Joe" }
jsx
여기서 jsx()는 React의 JSX 변환 과정에서 사용하는 함수야. 쉽게 다음 형태로 이해하면 돼.
jsx(태그이름, { 속성들, children: 자식내용 });
안에 any JavaScript expression 사용 가능
일반 JavaScript에서는 평소처럼 코드를 쓰고, JSX 내부에서 JavaScript 값을 끼워 넣을 때만 {}를 사용하면 돼!
코드 {} 의미 function App() { ... } 함수 본문을 감싸는 중괄호
<h1>{name}</h1> JSX 안에서 JS 표현식 사용
onClick={() => alert("Hi")} JSX 속성에 JS 함수 전달
fragment <>...</> 사용
```function App() {
return (
<>
<h1>Hello</h1>
<p>Welcome</p>
</>
); }```
build tool이 각 <tag> 를 function call로 compile
JSX는 개발자가 편하게 작성하는 문법이고, Build Tool은 이를 브라우저에서 실행할 수 있는 JavaScript로 번역해 주는 거야.
"What if you have many JS files?" → "Referencing all of them would be awful."
TEST ㅇㄹㅇ
person.k;
person["k"]를 조회함 → 객체에 'k'라는 키가 없으면 undefined
scope 규칙
let은 우리가 상식적으로 기대하는 블록 스코프(Block scope)를 엄격히 강제(enforce)하여 변수의 생명주기를 안전하게 가둠
window.main = main; // 인라인 classic script 에서 보이게 노출
앞서 살펴본 type="module" 내부에서 선언된 모든 변수, 함수, 클래스는 파일 단위의 독자적인 스코프를 갖기 때문에 window 객체에 전혀 등록되지 않습니다. 그러므로 따로 여기서 등록할 경우 말이 됨
<script type="module" src="App.js"></script>
type = module은 deferred되어서 html 파싱 완료 후 실행 됨
2. How it works
만약 스크립트가 <head>나 <body> 윗부분에 위치하고, 그 스크립트 안에 다음과 같은 전역 코드가 있다면:
JavaScript // external.js (head에서 로드됨) const btn = document.getElementById("submit-btn"); btn.addEventListener("click", () => { ... }); 브라우저는 아직 <body> 아래쪽에 적힌 <button id="submit-btn"> 태그를 읽지도 못한 상태입니다.
그런데 중간에 스크립트를 만나 실행부터 해버리므로 document.getElementById("submit-btn")은 null을 반환합니다.
결국 null.addEventListener(...)가 되어 TypeError (Cannot read properties of null)가 발생합니다.
C-1. 함수는 head에 둘 수 있지만 .js에 두는 게 좋다 (S10)
"단순 함수 정의라면 실행되지 않고 등록만 되니 head에 두어도 에러 없이 동작할 수는 있으나(can), 실제로는 성능(head 안의 스크립트를 만나는 순간 JS를 처리하므로, 코드가 길어지며 첫 화면 속도 저하)·캐싱·유지보수를 위해 별도 외부 .js 파일로 분리하는 것이 정석(should)"
awful
〈script src="utils.js"〉〈/script〉 〈script src="models.js"〉〈/script〉 〈script src="api.js"〉〈/script〉 〈script src="ui.js"〉〈/script〉 〈script src="main.js"〉〈/script〉
entry file
script type="module" src="app.js"></script
<script src="myScript.js"></script>
• 인라인 스크립트 (Inline Script): 태그 사이에 자바스크립트 코드를 직접 작성합니다.html
<script> alert("안녕하세요!"); </script>• 외부 파일 불러오기 (External Script): src 속성에 자바스크립트 파일(.js) 경로를 지정합니다.html
<script src="script.js"></script>(주의: src 속성이 있는 <script> 태그 내부에는 자바스크립트 코드를 직접 작성하면 안 됩니다.)
컨트롤러가 한 번 만들어서 넘기는 이유
처음 추가: Buy milk id = 123
redo 후: Buy milk id = 456
doTransaction() 안에서 new ListItem()을 하면 redo할 때마다 새 아이템이 생기기 때문에, 컨트롤러가 아이템을 한 번만 만들고 transaction이 그 같은 객체를 계속 들고 있어야 한다.
7-8. 흐름 4: 리스트 열기/닫기/만들기
홈 화면에서 누군가 리스트를 열거나 닫았을 때
const action = actionButton?.dataset.action ?? 'edit-item'; // ⑤ 버튼이면 그 종류, 아니면 'edit-item'
let action;
if (actionButton !== null && actionButton !== undefined) { action = actionButton.dataset.action; } else { action = 'edit-item'; }
const card = target.closest('.item-card'); // ① 클릭된 곳에서 위로 올라가며 카드를 찾음
button부터 시작 │ ├─ button 자신이 .item-card인가? │ → 아니야 │ ↑ 부모로 올라감 │ └─ li가 .item-card인가? → 맞아! → 이 li를 반환
domEvent.target
domEvent = 클릭 사건 전체 정보
domEvent.target = 그 정보 중 "실제로 클릭한 요소"
5-7. "HTML을 JS에서 만들지 않는다" 규칙
textContent = "이건 글자야." innerHTML = "이걸 HTML 코드로 해석해."
그리고 너희 과제에서는: 사용자가 입력한 데이터 → 글자로 보여주고 싶음 → textContent
pill.className = `priority-pill ${Priority.toCssClass(item.priority)}`; // 클래스 통째로 지정
<span class="priority-pill"> High </span> -> <span class="priority-pill priority-high"> High </span>
domEvent
domEvent ├── type: "click" ├── target: 실제 클릭한 요소 ├── currentTarget: 리스너가 달린 요소 ├── clientX: 마우스 X 위치 ├── clientY: 마우스 Y 위치 └── ...
이벤트 버블링
클릭 이벤트가 버블링해서 cardContainer까지 올라왔을 때, 그 클릭에 대한 정보(domEvent)를 받아서 실제 클릭한 요소(domEvent.target)를 확인한다.
CardPrototype.requirePart(element, '.item-description').
element 안에서 .item-description인 부분을 찾아줘 즉 이걸 찾는 거지: <span class="item-description"></span>
element.dataset.itemId = item.id; // data-item-id="item-3f9a..." element.dataset.index = String(index); // data-index="2"
HTML 요소 자체에 숨은 정보표를 붙이는 거야. 결과적으로 개념상 이렇게 돼.
5-2. ListView.render(): 그리기의 중심
render() → iterator로 item 하나 가져옴 → cardPrototype.clone(item, index) → clone() 안에서 prototype element 확보 → 그 prototype을 cloneNode(true)로 복사 → initializeClone()으로 실제 데이터 넣기 → 완성된 카드를 fragment에 추가 → 모든 카드가 모이면 replaceChildren(fragment) → 화면이 현재 model 상태에 맞게 다시 그려짐
initializeClone()
initializeClone() = 자식이 담당 = "그 빈 카드에 데이터를 어떻게 넣을까?"
buildPrototypeElement()
buildPrototypeElement() = 자식이 담당 = "어떤 빈 카드 틀을 쓸까?"
clone()
clone() = 부모가 담당 = "카드 하나 만들어줘"