React children 타입은 ReactNode와 PropsWithChildren 중 무엇을 사용하나요?
children에는 텍스트뿐 아니라 숫자·React 요소·배열·null 등 여러 렌더 가능한 값이 들어갈 수 있어 일반적으로 ReactNode를 씁니다. PropsWithChildren은 children을 포함한 props를 표현할 때 사용합니다.
적용 조건·범위: 단독 타입 검사에도 React 타입 선언과 JSX 설정이 필요합니다. React Vite 시작 글의 React+TypeScript 환경을 준비한 뒤 아래 수정 예제를 src/App.tsx에 저장합니다. 기존 파일의 내용과 섞지 않습니다. 아래 mount 연결을 함께 추가하면 화면에서도 확인할 수 있습니다.
공식 문서 참고: React: TSX·props·children의 TypeScript 타입 · @types/react 원본 타입: PropsWithChildren
직접 확인: 오류에서 수정까지
TypeScript children 타입: ReactNode와 PropsWithChildren 차이 · 독립 실습
얻을 결과: children: ReactNode로 필수 입력을 선언하고 빠진 children 오류를 확인합니다.
예제 크게 보기 (새 탭)단독 타입 검사에도 React 타입 선언과 JSX 설정이 필요합니다. React Vite 시작 글의 React+TypeScript 환경을 준비한 뒤 아래 수정 예제를 src/App.tsx에 저장합니다. 기존 파일의 내용과 섞지 않습니다. 아래 mount 연결을 함께 추가하면 화면에서도 확인할 수 있습니다.
독자 질문: TaskCard가 children을 반드시 받아야 한다면 어떻게 표시할까요?
얻을 결과: children: ReactNode로 필수 입력을 선언하고 빠진 children 오류를 확인합니다.
React와 타입 선언이 설치된 프로젝트의 .tsx 파일에서 실행합니다.
import type { ReactNode } from 'react';
type CardProps = { children: ReactNode };
function Card({ children }: CardProps) { return <section>{children}</section>; }
const card = <Card />;
예상 오류 (TS2741): 필수 children 속성이 빠졌습니다. 내용을 전달하거나 children?: ReactNode로 API를 변경합니다.
오류 예제를 다음 코드로 교체합니다.
import type { ReactNode } from 'react';
type CardProps = { children: ReactNode };
function Card({ children }: CardProps) { return <section>{children}</section>; }
const card = <Card>작업</Card>;
npx tsc -p . --noEmit
확인 결과: TypeScript 7.0.2와 설치된 React 타입 선언의 별도 최소 예제에서 오류 TS2741, 수정 후 exit 0 확인. TaskFlow 전체 체크포인트·React 앱 빌드는 미확인.
다음 학습: 21회차에서 공개 타입과 내부 구현 타입을 분리합니다.
ReactNode
import type {
ReactNode,
} from 'react';
type PanelProps = {
children:
ReactNode;
};
렌더 가능한 넓은 범위의 값을 받을 수 있습니다.
function Panel({
children,
}: PanelProps) {
return (
<section>
{children}
</section>
);
}
PropsWithChildren
기존 props에 children을 추가하는 helper type입니다.
import type {
PropsWithChildren,
} from 'react';
type PanelProps =
PropsWithChildren<{
title: string;
}>;
children이 함께 포함됩니다.
ReactElement와 차이
ReactNode
→ 넓은 렌더 가능한 값
ReactElement
→ React element 객체
텍스트도 받아야 한다면 ReactNode가 더 자연스럽습니다.
children 필수 여부
children: ReactNode
필수입니다.
children?: ReactNode
생략 가능입니다.
PropsWithChildren은 children을 선택적으로 포함하므로 반드시 children이 있어야 하는 컴포넌트라면 직접 타입을 작성하는 편이 명확할 수 있습니다.
children을 무조건 넣지 않기
TaskItem처럼 내부 구조가 고정된 컴포넌트라면 children을 허용하지 않아도 됩니다.
type TaskItemProps = {
task: Task;
onDelete:
(id: string) =>
void;
};
합성이 실제 목적일 때 children을 사용합니다.
actions slot
여러 위치를 합성하고 싶다면 별도의 ReactNode prop을 만들 수 있습니다.
type CardProps = {
children:
ReactNode;
actions?:
ReactNode;
};
function Card({
children,
actions,
}: CardProps) {
return (
<article>
<div>
{children}
</div>
{actions ? (
<div>
{actions}
</div>
) : null}
</article>
);
}
render prop과 children은 다릅니다
함수를 children으로 받아야 하는 고급 API라면 별도 함수 타입을 명시하는 편이 좋습니다.
type ListProps<T> = {
items: T[];
renderItem:
(item: T) =>
ReactNode;
};
모든 합성 API를 children 하나로 해결하려고 하지 않습니다.
학습 완료 체크리스트
- ReactNode가 넓은 렌더 가능한 타입임을 이해했습니다.
- PropsWithChildren을 사용할 수 있습니다.
- ReactElement와 ReactNode를 구분할 수 있습니다.
- children 필수/선택 여부를 설계할 수 있습니다.
- 모든 컴포넌트에 children을 무조건 추가하지 않을 수 있습니다.
수정 예제의 화면 연결
export default function App() { return card; }
개발 서버에서 section 안에 작업이라는 글자가 표시됩니다.
작은 변형으로 확인하기
<Card>{null}</Card>는 타입 검사에 통과하지만 화면에 내용은 나타나지 않습니다. 필수 children prop과 눈에 보이는 내용 보장은 다른 조건입니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 JavaScript 코드를 TypeScript로 확장합니다.
TaskCard를 합성 가능한 component로 만들고 children과 actions 타입을 명시합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
TypeScript children 타입: ReactNode와 PropsWithChildren 차이 · 단계별 TaskFlow
타입을 적용한 TaskFlow에서 작업을 추가·완료 전환·삭제하세요. 타입만 바뀐 단계는 이전과 화면이 같습니다.
예제 크게 보기 (새 탭)변경 설명과 부분 예제를 읽은 뒤, 시작·완료 파일과 적용 순서에서 필요한 선언·import·화면 연결을 포함한 전체 파일을 확인하세요. 이번 단계의 변경 이유와 적용 위치도 함께 확인합니다.
시작본과 이번 변경
React TypeScript optional props 기본값 처리: undefined·null 차이의 완료 체크포인트를 기준으로 이어갑니다. 변경하지 않는 파일은 그 시작 상태를 유지합니다.
src/components/TaskCard.tsx에서 children을 필수 ReactNode로, actions를 선택적으로 선언합니다. 목록의 사용 위치에서 기존 title prop을 children으로 옮기고 삭제 버튼을 actions에 전달합니다.
이전 체크포인트
typescript-step-19-optional-props
이번 목표
TaskCard를 합성 가능한 component로 만들고 children과 actions 타입을 명시합니다.
전체 파일·코드 보기
src/components/TaskCard.tsx
import type {
ReactNode,
} from 'react';
type TaskCardProps = {
children:
ReactNode;
actions?:
ReactNode;
};
export default function TaskCard({
children,
actions,
}: TaskCardProps) {
return (
<li>
<div>
{children}
</div>
{actions ? (
<div>
{actions}
</div>
) : null}
</li>
);
}
사용
<TaskCard
actions={
<button
type="button"
onClick={() =>
onDelete(
task.id,
)
}
>
삭제
</button>
}
>
<span>
{task.title}
</span>
</TaskCard>
검증
npx tsc --noEmit
npm run build
중간부터 시작하는 전체 파일과 적용 순서
시작 ZIP은 직전 프로젝트 단계 typescript-step-19-optional-props의 실제 완료 파일입니다. 프로젝트 순서를 이어가는 기준이며 사이트의 기술 읽기 순서와는 구분합니다.
- 시작 ZIP을 새 폴더에 풉니다. 이 폴더가 작업 루트이며 기존 체크포인트 파일을 별도로 찾을 필요가 없습니다.
- TaskCard를 children/actions 슬롯으로 교체하고 기존 title 사용처를 제거합니다. TaskItem에서 슬롯을 연결한 전체 파일을 제공합니다.
- 아래 변경 파일을 같은 상대 경로에 추가하거나 전체 교체합니다. 표시하지 않은 파일과 images는 시작본 그대로 유지합니다. package-lock.json은 완료 ZIP에 있는 파일을 함께 적용합니다.
- 작업 루트에서 명령을 실행하고 예상 결과를 확인합니다. 완료 ZIP은 직접 적용한 결과를 비교하는 기준입니다.
추가·교체 파일
CHANGES.md— 전체 교체README.md— 전체 교체src/components/TaskItem.tsx— 전체 교체src/App.tsx— 전체 교체src/components/TaskCard.tsx— 전체 교체
실행과 확인
npm ci
npm run dev
npm run typecheck
npm test
npm run build
개발 서버는 실행 상태로 두고 다른 터미널에서 검사 명령을 실행하세요. Node.js 24.18.0에서 검증했습니다. JavaScript는 http://127.0.0.1:4173, TypeScript는 Vite 실행 출력의 주소로 접속합니다.
예상 결과: 각 작업은 li 하나로 렌더링되고 삭제·완료가 동작합니다. children은 ReactNode이며 null도 허용됩니다.
실제 검증: TypeScript strict 검사·긍정/부정 타입 계약·런타임 검사·Vite 빌드 통과; React 폼·버튼·초점 DOM 검사 통과. 시작 ZIP에 이 글의 변경 파일만 적용한 결과와 완료 ZIP의 모든 파일 내용이 일치했습니다. 브라우저 화면 배치·실제 기기·외부 CDN 가용성은 이 검사에 포함하지 않았습니다.
이 절의 전체 파일은 ZIP과 일치합니다. 위쪽 원문의 짧은 예제나 생략 표시가 있는 부분 코드는 개념·변경 위치 설명용입니다. 서버 저장과 인증은 제공하지 않으며 JSON fixture는 로컬 읽기 실습용입니다.
복사할 변경 파일 전체 코드
src/components/TaskItem.tsx — 전체 파일
import type { Task } from '../types/task.js';
import TaskCard from './TaskCard.js';
type TaskItemProps={task:Task;onToggle:(id:string)=>void;onDelete:(id:string)=>void};
export default function TaskItem({task,onToggle,onDelete}:TaskItemProps){
return <TaskCard actions={<><button type="button" onClick={()=>onToggle(task.id)}>{task.completed?'완료 취소':'완료'}</button><button type="button" onClick={()=>onDelete(task.id)}>삭제</button></>}><span>{task.title}</span></TaskCard>;
}
src/App.tsx — 전체 파일
import TaskDetail from './components/TaskDetail.js';
import { useState } from 'react';
import type { Task } from './types/task.js';
import { toggleTask, deleteTask, addTask } from './core/tasks.js';
import TaskForm from './components/TaskForm.js';
import TaskList from './components/TaskList.js';
export default function App() {
const [tasks,setTasks]=useState<Task[]>([{id:'1',title:'TypeScript 공부',completed:false},{id:'2',title:'React props 확인',completed:true}]);
const onToggle=(id:string)=>setTasks(current=>toggleTask(current,id));
const onDelete=(id:string)=>setTasks(current=>deleteTask(current,id));
return <><TaskDetail task={tasks[0] ?? null}/><TaskForm onAdd={title=>setTasks(current=>addTask(current,title))}/><p>전체 {tasks.length} · 완료 {tasks.filter(task=>task.completed).length}</p><TaskList tasks={tasks} onToggle={onToggle} onDelete={onDelete}/></>;
}
src/components/TaskCard.tsx — 전체 파일
import type {
ReactNode,
} from 'react';
type TaskCardProps = {
children:
ReactNode;
actions?:
ReactNode;
};
export default function TaskCard({
children,
actions,
}: TaskCardProps) {
return (
<li>
<div>
{children}
</div>
{actions ? (
<div>
{actions}
</div>
) : null}
</li>
);
}
완료 체크포인트
typescript-step-20-children
다음 학습
다음 글에서는 내부 구현 타입과 외부에서 사용할 공개 타입을 분리하고 export type 경계를 설계합니다.
다음 글: TypeScript 공개 타입 설계 export type과 라이브러리 경계
글 소개·학습 준비·전체 목차

TypeScript · 20 / 22 · React 연결 · children 타입
React의 children은 문자열만 전달되는 값이 아닙니다.
텍스트, 숫자, React element, 배열, null 등 다양한 렌더 가능한 값을 받을 수 있기 때문에 일반적으로 ReactNode를 사용합니다.
이번 글에서는 ReactNode, PropsWithChildren, ReactElement를 비교하고 children을 실제로 허용해야 하는 컴포넌트에만 추가하는 기준을 정리합니다.
이번 글에서 배우는 내용
- ReactNode
- PropsWithChildren
- ReactElement
- children 필수/선택
- 합성 컴포넌트
- children을 모든 컴포넌트에 넣지 않는 기준
단독 학습 선수 지식
React JSX 합성과 optional props·type import을 알면 됩니다. 이전 TaskFlow 파일 없이 직접 확인 예제로 학습할 수 있습니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 22개 · 필수 21개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.