export default function App() {
const movieTitle = "인터스텔라";
const genre = "SF";
const releasedDate = new Date('2014-11-06');
return (
<article className="movie-card">
<h1>{movieTitle}</h1>
<p>장르: {genre}</p>
<p>개봉일: {releasedDate.toLocaleDateString()}</p>
</article>
);
}
{}로 데이터 넣기 python의 f-string과 유사
function Header()
{
return <h1>테스트 헤더</h1>;
}
function MovieList()
{
return (
<>
<MovieCard />
<MovieCard />
</>
);
//Fragment이다. 여러 개를 하나의 객체로 묶어서 리턴
}
function MovieCard()
{
return <h3>테스트 영화</h3>;
}
export default function App() {
return (
<main>
<Header />
<MovieList />
</main>
);
}
함수를 컴포넌트로 이용 가능. 단 대문자로 이용하셈.
return 할 때, 한 객체로 묵어서 나가야하기 때문에 <></> 같은 것으로 리턴
export interface MovieCardProp {
title: string;
releasedDate: string;
isBookmarked: boolean;
};
//default 뜻: "이 파일의 메인 대표 선수"라는 뜻 (파일당 1개만 가능, 가져올 때 내 마음대로 이름 지정 가능)
//딱 하나
모듈화로 처리
import type { MovieCardProp } from "./components";
//이건 엄격함 때문에 추가됨
function MovieCard(prop: MovieCardProp)
{
return (
<article>
<h1>{prop.title}</h1>
<p>{prop.releasedDate}</p>
<p>{prop.isBookmarked ? "북마크 됨" : "북마크하기"}</p>
</article>
);
}
function MovieList()
{
return (
<>
<MovieCard
title="영화1"
releasedDate="날짜1"
isBookmarked={true}
/>
<MovieCard
title="영화2"
releasedDate="날짜2"
isBookmarked={false}
/>
<MovieCard
title="영화3"
releasedDate="날짜3"
isBookmarked={true}
/>
</>
)
}
export default function App()
{
return (
<>
<MovieList></MovieList>
{/* <MovieList />랑 완전히 똑같다 */}
</>
);
}
/*
interface MovieCardProps {
title: string;
releasedDate: string;
isBookmarked: boolean;
test?: string; //이걸로 없어도 된다 표시 ts ㅇㅇ
}
function MovieCard(prop: MovieCardProps)
{
return (
<article>
<h2>{prop.title}</h2>
<p>{prop.releasedDate}</p>
<p>{prop.isBookmarked ? "북마크됨" : "북마크 안 됨"}</p>
</article>
);
}
export default function App() {
return (
<>
<MovieCard
title="오디세이"
releasedDate="2026.08.05"
isBookmarked={true}
/>
<MovieCard
title="토이 스토리 5"
releasedDate="2026.06.17"
isBookmarked={false}
/>
</>
);
}
*/
/*
function MovieCard({
title,
releasedDate: theDate,
isBookmarked,
}: MovieCardProps)
{
return (
<article>
<h2>{title}</h2>
<p>{theDate}</p>
<p>{isBookmarked ? "북마크됨" : "북마크 안 됨"}</p>
</article>
);
}
export default function App() {
return (
<>
<MovieCard
title="오디세이"
releasedDate="2026.08.05"
isBookmarked={true}
/>
<MovieCard
title="토이 스토리 5"
releasedDate="2026.06.17"
isBookmarked={false}
/>
</>
);
}
*/
/*
// ① 인터페이스(규칙 설계도)
interface MovieCardProps {
title: string; // 👈 'title'이라는 이름표 필수!
releasedDate: string; // 👈 'releasedDate' 필수!
isBookmarked: boolean; // 👈 'isBookmarked' 필수!
}
// ② 컴포넌트 (받는 곳)
function MovieCard({
title, // 👈 설계도에 있는 이름이랑 정확히 같아야 함
releasedDate: 별명, // 👈 (다른 이름으로 바꾸려면 별칭 써야 함)
isBookmarked,
}: MovieCardProps)
{
별명
}
export ... App
*/
Prop으로 리턴 형태 지정 가능
interface Movie {
id : number,
title : string,
releasedDate : Date,
}
const movies : Movie[] = [
{id: 1, title: "영화1", releasedDate: new Date("2026-09-22")},
{id: 2, title: "영화2", releasedDate: new Date("2026-09-23")},
];
function MovieList()
{
return (
<ul>
{movies.map((movie) => (
<li key={movie.id}> {/* 나중에 id 쓰려고*/}
<h1>{movie.title}</h1>
<p>{movie.releasedDate.toLocaleDateString()}</p>
<p>id : {movie.id}</p>
</li>
))}
</ul>
);
}
function MovieSection()
{
return (
<>
{movies.length === 0 ? (<p>표시할 영화가 없어요.</p>) : (MovieList())}
</>
);
}
export default function App()
{
return (
<>
<MovieSection></MovieSection>
</>
);
}
map으로 반복하면서 출력
export default function App() {
function handleClick() {
alert("버튼을 클릭했어요.");
}
return (
<button onClick={handleClick}>
클릭하기
</button>
);
}
버튼 예제
import {useState} from "react";
export default function App()
{
const [value, setValue] = useState(0);
const MAX_VALUE = 5, MIN_VALUE = 0;
function changeValue(updater : Function){
let nextVal = updater(value);
nextVal = Math.max(Math.min(nextVal, MAX_VALUE), MIN_VALUE);
setValue(nextVal);
}
return (
<>
<h1>카운터 : {value}</h1>
<p>max : {MAX_VALUE} / min : {MIN_VALUE}</p>
{
(value < MAX_VALUE) &&
<button onClick={() => changeValue(a => a+1)}>
+1
</button>
}
{
(value > MIN_VALUE) &&
<button onClick={() => changeValue(a => a-1)}>
-1
</button>
}
</>
);
}
/*
// 입력받은 값(a)에 1을 더해서 리턴하는 함수
const myUpdater = function(a) {
return a + 1;
};
// function 키워드를 지우고 인수 뒤에 => 를 붙입니다.
const myUpdater2 = (a) => {
return a + 1;
};
console.log(myUpdater(1));
console.log(myUpdater2(1));
//똑같다
*/
/*
// 우리가 쓴 축약형
() => changeValue(a => a + 1)
// 이걸 전통적인 함수 형태로 길게 풀어쓰면
function() {
return changeValue(a => a + 1);
}
*/
react에서 useState로 변수 처리
useState(초기값) => [값, 값설정함수]
button에서 onClick=changeValue() 하면)가 바로 실행됨 즉, () (이름없는함수)로 이용
changeValue(
updater로 변수 유사 transaction 처리함
import { useState } from "react";
/* 왜 useState를 쓸까?
//리렌더링 함수가 있었으나, 후에 추적하기 어려워 삭제
//forceUpdate()
//또, 한 부분만 업데이트 하면 되기에 특수한 함수를 이용
//그냥 변수는 상수 처리로, 업데이트가 되지 않음.
let count: number = 0;
export default function App() {
const [forceData, forceUpdate] = useState(0);
function add() {
count += 1;
forceUpdate((forceData) => forceData + 1);
}
return (
<>
<p>{count}</p>
<button onClick={
add
}>
+1
</button>
</>
)
}
*/
/* 기본 코드
export default function App() {
const [count, setCount] = useState(0);
// : [number, React.Dispatch<React.SetStateAction<number>>]
return (
<>
<h1>카운터</h1>
<p>{count}</p>
<button onClick={() => setCount((val) => val + 1)}>
+1
</button>
<button onClick={() => setCount((val) => val - 1)}>
-1
</button>
<button onClick={() => setCount(0)}>
리셋
</button>
</>
)
}*/
// useState의 함수를 쓸 때, a => a+1 해야하는 이유
export default function App() {
const [count, setCount] = useState(0);
function add1() {
setCount(a => a+1);
setCount(a => a+1);
setCount(a => a+1);
// +3이 됨
}
function add2() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// +1이 됨
}
return (
<>
<p>{count}</p>
<button onClick={add1}>add1</button>
<button onClick={add2}>add2</button>
</>
);
}
이게 그 예시
/*
구조 설계
MovieCardProps로 타입
App
ㄴMovieGrid
ㄴMovieCard
ㄴ Movie 관련 버튼 등
*/
import { useState } from "react";
interface Movie {
id: number,
title: string,
releasedDate: Date,
isBookmarked: boolean,
}
interface Arguments {
movies: Movie[],
onToggleBookmark: (id:number) => void
}
const initialMovies: Movie[] = [
{id:1, title:"영화1", releasedDate:new Date("2026.09.22"), isBookmarked:false},
{id:2, title:"영화2", releasedDate:new Date("2026.09.21"), isBookmarked:true},
{id:3, title:"영화3", releasedDate:new Date("2026.09.20"), isBookmarked:false},
]
function MovieList({movies, onToggleBookmark}: Arguments) {
return (
<ul>
{movies.map((movie) =>
<li key={movie.id}>
<h3>{movie.title}</h3>
<p>{movie.releasedDate.toLocaleDateString()}</p>
<button
aria-pressed={movie.isBookmarked}
onClick={() => onToggleBookmark(movie.id)}
>
{movie.isBookmarked
? "[O] 북마크 됨"
: "[ ] 북마크 하기"
}
</button>
</li>
)}
</ul>
);
}
function MovieGrid({movies, onToggleBookmark}: Arguments) {
return (
<>
{movies.length === 0
? <p>영화가 없습니다.</p>
: <MovieList movies={movies} onToggleBookmark={onToggleBookmark}/>
}
</>
);
}
export default function App() {
const [movies, setMovies] = useState(initialMovies);
function onChangeBookmark(id: number)
{
setMovies((currentMovies) =>
currentMovies.map((movie) =>
(movie.id === id)
? {...movie, isBookmarked: !movie.isBookmarked}
: movie
)
);
}
return (
<>
<h1>영화 목록</h1>
<MovieGrid movies={movies} onToggleBookmark={onChangeBookmark} />
</>
)
}
파편화해서 관리 쉽게
...으로 unpacking후, 덮어씌우기로 isBookmarked이용
App 최상위에서
useState로, 변수 만들어주고
onChangeBookmark라는 변경 함수 만들어줌.
그 후, MoveGrid에 이거 전달
그 밑에 또 전달
또 전달
import { useState } from "react";
interface Movie {
id: number,
title: string,
releasedDate: Date,
isBookmarked: boolean,
}
const initialMovies: Movie[] = [
{
id: 1,
title: "오디세이",
releasedDate: new Date("2026.08.05"),
isBookmarked: true,
},
{
id: 2,
title: "토이 스토리 5",
releasedDate: new Date("2026.06.17"),
isBookmarked: false,
},
];
export default function App() {
const [movies, setMovies] = useState(initialMovies);
function handleToggleBookmark(movieID: number) {
function getUpdatedMovies(currentMovies: Movie[])
{
currentMovies = currentMovies.map((movie) =>
movie.id === movieID
? { ...movie, isBookmarked: !movie.isBookmarked} //덮어씌우기 !로 토글
: movie,
)
return currentMovies;
}
setMovies(getUpdatedMovies);
}
function deleteMovie(movieID: number) {
setMovies((currentMovies) =>
currentMovies.filter((movie) =>
movie.id !== movieID
) //조건 맞는 값들만 리턴
)
}
function addMovie() {
setMovies((currentMovies) =>
[...currentMovies,
{
id : 99,
title : "ㅇ",
releasedDate: new Date("2026.09.22"),
isBookmarked: false,
}
]
)
}
return (
<>
<h1>영화목록</h1>
<ul>
{movies.map((movie) => (
<li key={movie.id}>
<h3>{movie.title}</h3>
<p>{movie.releasedDate.toLocaleDateString()}</p>
<button
aria-pressed={movie.isBookmarked}
onClick={() => handleToggleBookmark(movie.id)}
>
{movie.isBookmarked
? "[O] 북마크 됨"
: "[ ] 북마크하기"
}
</button>
<button
onClick={() => deleteMovie(movie.id)}
>
영화 제거하기
</button>
</li>
))}
</ul>
<button
onClick={addMovie}
>
영화 추가하기
</button>
</>
);
/*
setMovies(a => a) 처럼 아예 새로운 값으로 바꾸는 듯
function handleToggleBookmark(movieID : number) {
setMovies((currentMovies) =>
currentMovies.map((movie) =>
movie.id === movieID
? { ...movie, isBookmarked: !movie.isBookmarked} //덮어씌우기 !로 토글
: movie,
)
);
}*/
}
/*
코드 작성 순서를 주석으로 표시할게용
*/
//1
import { createContext, useContext, useState } from "react";
//2
type StudyMode = "focus" | "break";
//3
const initialStudyMode: StudyMode = "focus";
//4
const StudyModeContext = createContext<StudyMode>(initialStudyMode);
//8
function ModeStatus() {
const mode = useContext(StudyModeContext);
return (
<h3>
{mode === "focus" ? "집중중..." : "휴식중!!!"}
</h3>
)
}
//5
export default function App() {
//6
const [mode, setMode] = useState<StudyMode>(initialStudyMode);
//10
function toggleMode() {
setMode((now) =>
(now === "focus") ? "break" : "focus"
);
}
return (
//7
<StudyModeContext.Provider value={mode}>
<h1>학습모드</h1>
<ModeStatus></ModeStatus>
<button onClick={toggleMode}>
변경하기
</button>
{/* 9 */}
</StudyModeContext.Provider>
)
}
이때부터 좀 헷갈려서 코드 작성 순서 추가함
Context는 괄호 안의 변수 느낌.
로 그 안의 scope에 있는 친구들
그 변수들 쓸 수 있게 함.
이게 한 파일이어서 변수 값만 받았지,
원래는 변경 함수 등등 다 받는 게 좋음.
#7 에서는 props를 한땀한땀 자식으로 전달했다면,
Context는 useContext로 바로 이용 가능하게.
그래서 순서 정리하면
그니까 class 짜는 느낌인거임
instance는 <~.Provider></~.Provider> 로 만들고
initial value도 이때 value={}로 정의하니까 ㄱㅊ
그 안에 있는 친구들이 instance를 useContext로 이용 ㄱㄴ한거
State로 변수 만들기 & 조작함수 만들기
보통 상위 Fragment(Function으로 정의함) 안에서 만드는데
그 Fragment 안에서 useState로 변수 처리할거임
그러면 그 scope 안에서는 변수가 정의되어있으니까
변수 처리해도 ㄱㅊ겠죠잉 그래서 변수 조작하는 function 만듦
updater가 필요할 땐 그거 쓰면 굿. 유연하게 대처가 가능
Provider
이제까지 class 만들었으니까 instance 생성할 느낌
먼저 ~.Provider로 <~.Provider>자식들</~.Provider>로 함
자식들은 Provider에서 준 Context 이용 가능.
Provider에서 기본 값 설정은 value={값,값,값 ...}으로 함
값에 key:value아니라 value꼴로 넣는다면 순서 중요하니 유의
useContext로 값 써먹기
그 Provider의 자식들이 useContext(그 Context)로 값이나 변수들 가져올 수 있음.
보통 const {변수들 중 필요한 것들만} = useContext(그 Context);
꼴로 가져오고 알아서 잘 쓰면 됨
미션

App
ㄴHeader
ㄴPagination
ㄴPageProvider
ㄴMovieProvider 이 Provider를 병렬로 처리하기엔 한 Provider가 여러 역할할 듯
ㄴMovieGrid
ㄴMovieCard
ㄴPageSelector
//App.tsx
import Header from "./header";
import Pagination from "./pagination";
export default function App() {
return (
<main>
<Header></Header>
<Pagination></Pagination>
</main>
);
}
```
```tsx
//header.tsx
export default function Header() {
return (
<h3>헤더입니당</h3>
)
}
```
```tsx
//pagination.tsx
import MovieGrid from "./movie-grid";
import { MovieProvider } from "./movie-context";
import { PageProvider } from "./page-context";
import { PageSelector } from "./page-selector";
export default function Pagination() {
return (
<PageProvider>
<MovieProvider>
<h2>영화 목록</h2>
<MovieGrid></MovieGrid>
<PageSelector></PageSelector>
</MovieProvider>
</PageProvider>
);
}
```
```tsx
//page-context.tsx
import { createContext, useState } from "react";
const initialPage: number = 1;
const possiblePages: number[] = [1,2,3,4,5];
type PageContextType = {
page: number,
possiblePages: number[],
changePage: (page:number) => void,
};
export const PageContext = createContext<PageContextType>({
page: initialPage,
possiblePages: possiblePages,
changePage: () => {},
});
export function PageProvider({ children }) {
const [page, setPage] = useState<number>(initialPage);
const changePage = (page: number) => {
setPage(() => page);
}
return (
<PageContext.Provider value={{page, possiblePages, changePage}}>
{children}
</PageContext.Provider>
);
}
```
```tsx
//movie-context.tsx
import { createContext, useState } from "react";
import { type Movie } from "./types/movie";
import { movies } from "./data/movies";
const initialMovie: Movie[] = movies;
type MovieContextType = {
movies: Movie[],
toggleBookmark: (id:number) => void,
};
export const MovieContext = createContext<MovieContextType>({
movies : initialMovie,
toggleBookmark: () => {},
});
export function MovieProvider({ children }) {
const [movies, setMovies] = useState<Movie[]>(initialMovie);
const toggleBookmark = (id:number) => {
setMovies(movies => movies.map((movie) =>
movie.id === id
? {...movie, isBookmarked: !movie.isBookmarked}
: movie
))
}
return (
<MovieContext.Provider value={{movies, toggleBookmark}}>
{children}
</MovieContext.Provider>
);
}
```
```tsx
//movie-grid.tsx
import { useContext } from "react";
import { MovieContext } from "./movie-context";
import MovieCard from "./movie-card";
import { PageContext } from "./page-context";
import "./movie-grid.css";
export default function MovieGrid() {
const {movies} = useContext(MovieContext);
const {page} = useContext(PageContext);
const START = 0;
const COUNT = 3;
const targetMovies = movies.slice(START+(page-1)*COUNT, START+COUNT*page);
return (
<div className="movie-grid">
{targetMovies.length === 0
? <p>영화가 없습니다</p>
: targetMovies.map((movie) => (
<MovieCard key={movie.id} movie={movie}/>
))
}
</div>
);
}
```
```tsx
//movie-card.tsx
import { type Movie } from "./types/movie";
import { useContext } from "react";
import { MovieContext } from "./movie-context";
import "./movie-card.css";
type MovieCardProps = {
movie : Movie
}
export default function MovieCard({movie} : MovieCardProps) {
const {toggleBookmark} = useContext(MovieContext);
return (
<div className="movie-card">
<img className="movie-card__poster" src={movie.posterPath}></img>
<h3>{movie.title}</h3>
<p>{movie.genres}</p>
<p>{movie.releaseDate}</p>
<button onClick={() => toggleBookmark(movie.id)} aria-pressed={movie.isBookmarked}>
{movie.isBookmarked ? "북마크됨" : "북마크하기"}
</button>
</div>
)
}
```
```tsx
//page-selector.tsx
import { PageContext } from "./page-context";
import {useContext} from "react";
export function PageSelector() {
const {page, possiblePages, changePage} = useContext(PageContext);
return (<div>
{possiblePages.map((pageNum) => (
<button onClick={() => changePage(pageNum)}>
{page === pageNum ? `[ ${pageNum} ]` : pageNum}
</button>
))}
</div>);
}
```
- React 컴포넌트와 JSX
- JSX는 HTML과 어떤 점이 다르며 React 컴포넌트 안에서 어떤 역할을 하나요?
JavaScript의 확장으로, {}안에 표현식으로 쉽게 이용할 수 있고, 가독성이 좋다.
- 하나의 화면을 여러 컴포넌트로 나누면 어떤 장점이 있으며, 분리 기준은 어떻게 정할 수 있을까요?
자주 쓰는 것, 너무 많은 일을 하지 않게 등. 함수와 비슷하게 처리하자
- 조건부 렌더링과 목록 렌더링은 데이터에 따라 보여 줄 컴포넌트를 어떻게 결정하나요?
조건이나 값에 따라 렌더링 제어. 목록은 map으로 순환해서 렌더링
- props와 단방향 데이터 흐름
- 부모와 자식 컴포넌트 사이에서 props는 어떤 역할을 하나요?
내려가는 데이터
- 데이터가 부모에서 자식으로만 흐르면 화면의 변화를 추적하는 데 어떤 도움이 되나요?
역추적이 한 방향이어서 좋음
- props에 TypeScript 타입을 붙이면 컴포넌트를 잘못 사용하는 실수를 어떻게 찾을 수 있나요?
오타방지
- 상태와 리렌더링
- 일반 변수와 state(상태)는 값이 바뀐 뒤 화면을 다시 보여 주는 방식이 어떻게 다른가요?
변수는 바뀌어도 리렌더 ㄴㄴ state는 감지해서 리렌더
- React에서 원본 상태를 직접 수정하지 않고 새로운 값으로 업데이트해야 하는 이유는 무엇일까요?
리액트는 이전 상태와 참조 값 비교로 빠르게 판단함. 그래서 원본 직접 수정하면 주소고정 ⇒ 뭐가 바뀐거고 ;; 라고 함
- 이전 상태로 다음 상태를 계산할 때 업데이터 함수를 사용하는 이유는 무엇일까요?
a ⇒ a+1 같은 거 쓰면 유연하게 이용 가능. add, subtract, multiply 등으로 함수 나누거나 설정 안해도 a ⇒ a에 관한 연산 으로 쉽게 가능
- 상태 공유와 Context
- 여러 컴포넌트가 같은 상태를 사용해야 할 때 상태를 어느 컴포넌트에 두는 것이 좋을까요?
위에 있는 게 좋음. 근데 너무 위면 굳이고, 가장 가까운 공통 조상 ㄱㄱ (상태 끌어올리기)
- props 전달, 상태 끌어올리기와 Context는 각각 어떤 상황에 알맞을까요?
prop은 부모⇒자식 단순히 한 칸 전달할 때 굿
상태 끌어올리기는 형제들끼리 데이터 공유할 때 공통 부모로 이동
Context는 부모 자식 너무 깊어서 힘들 때나 전역 공유
- 모든 값을 Context로 전달하면 어떤 문제가 생길 수 있을까요?
Context 변경하면 그 하위 트리가 다 리렌더링됨.
그니까 하나만 바뀌어도 그 밑이 바뀔 수 있기에 성능 저하 가능.