📌 Children Props
리액트 모든 컴포넌트에서 children props를 사용할 수 있습니다.
children props란 컴포넌트의 여는 태그와 닫는 태그 사이의 내용입니다.
사용예시
이 태그에서 Hello world! 문자열이
Welcome 컴포넌트의 children props로 전달이 되어 접근할 수 있습니다.
function App() {
return <Welcome>Hello world!</Welcome>
}
function Welcome(props) {
return <p>{props.children}</p>;
}
📌 합성(Composition)과 props.children
🔎 children 이란?
props.children은 React 컴포넌트의 특별한 props입니다.
컴포넌트가 다른 컴포넌트를 포함할 때, 그 안에 들어가는 내용을 모두 children으로 접근할 수 있어요.
App 컴포넌트에서 작성한 <Category> ~ </Category> 내부에 작성한 내용들이
Category 컴포넌트에게 props.children으로 전달됩니다.
{props.children}은 <Category> ~ </Category> 내부에
작성된 내용들을 화면에 표시합니다.
예를 들어 <Category>라는 컴포넌트를 만들었다고 생각해봅시다.
<Category> 태그 사이에 들어가는 내용은 모두 props.children으로 전달됩니다.
위 코드에서 <Category> 태그 안에 있는 <li> 태그들이 props.children으로 전달되고,
Category 컴포넌트는 이를 <ul> 태그로 감싸서 화면에 표시합니다.
const Category = (props) => {
return <ul>{props.children}</ul>;
};
const App = () => (
<Category>
<li>First item.</li>
<li>Second item.</li>
<li>Another item.</li>
</Category>
);
📌 props.children 필요성
props.children은 자식 엘리먼트나 컴포넌트가 미리 몇 개가 들어올지 알 수 없거나,
다양한 상황에서 재사용 가능하게 만들 때 유용합니다.
이렇게 하면 코드가 더 간단하고 재사용 가능해집니다.
const li_Array = ["First item.", "Second item."];
const Category = (props) => {
return <ul>{props.children}</ul>;
};
const App = () => (
<Category>
{li_Array.map((value, idx) => (
<li key={idx}>{value}</li>
))}
</Category>
);
📌 props.children 메서드
React는 props.children가 컴포넌트 내의 JSX 요소를 단순히 화면에 표시하는 기능 이외에도
다양하게 다룰 수 있는 메서드를 제공합니다.
React.Children 전용 메서드는 아니지만,
props.children을 복제하여 변경이 필요한 경우 React.cloneElement 메서드를 사용할 수 있습니다.
props.children은 수정이 불가능한 읽기 전용이므로
React.cloneElement 메서드를 사용하여 props.children을 복제 후 수정할 수 있습니다.
메서드 | 설명 |
React.Children.map | 각 자식에 대하여 함수를 호출하고, 새 배열을 반환 |
React.Children.forEach | 각 자식에 대하여 함수를 호출 |
React.Children.count | 자식의 수를 반환 |
React.Children.only | 자식이 하나만 있는지 확인 |
React.Children.toArray | 자식을 새 배열로 반환 |
📌 자식과 자손
React에서 props.children을 사용할 때 주의할 점은 모든 요소들이 자식(child)으로 취급되지 않는다.
props.children은 직접적인 첫 번째 자식만을 가리킵니다.
- 자식 (Child) : 컴포넌트가 직접 감싸고 있는 요소들입니다.
- 자손 (Descendant) : 자식 요소의 내부에 중첩되어 있는 요소들까지 포함하는 더 넓은 범위의 요소들
위 코드에서 Category의 직접적인 자식은 <ul> 엘리먼트입니다.
<ul> 안에 있는 <li> 엘리먼트들은 Category 입장에서는 "자손(descendant)"이지, 직접적인 자식은 아닙니다.
그래서 React.Children.count로 Category의 자식 개수를 확인하면 1개만 출력됩니다.
const App = () => (
<Category>
<ul>
{li_Array.map((value, idx) => (
<li key={idx}>{value}</li>
))}
</ul>
</Category>
);
class Category extends React.Component {
render() {
console.log("자식의 수 : " + React.Children.count(this.props.children));
return <React.Fragment>{this.props.children}</React.Fragment>;
}
}
📌 Children Props Type
타입스크립트에서는 Children Props를 전달을 할 때 타입을 지정을 해주어야 합니다.
그렇다면 어떤 타입으로 지정을 해주어야 하는지 알아보겠습니다.
대부분의 경우, React.ReactNode를 사용하면 대부분의 시나리오에 적합하고 유연하게 대응할 수 있어요.
따라서 children props의 타입을 지정할 때는 React.ReactNode를 사용하는 것이 좋습니다.
- JSX.Element : 단일 React 요소만 허용. 예외 사항이 많음
- React.ReactChild : 문자열, 숫자 등을 포함하지만 배열은 허용되지 않으며 사용이 지양됨
- React.PropsWithChildren : children이 선택 사항인 경우 적합
- React.ReactNode : 모든 타입을 지원하며 가장 유연하고 많이 사용됨
🔎 JSX.Element
JSX.Element는 하나의 React 요소만을 나타낼 수 있습니다.
문자열, 숫자와 같은 원시 타입은 사용할 수 없습니다.
import React from "react";
type OnlyElementProps = {
children: JSX.Element; // 단일 React 요소만 허용
};
const OnlyElementComponent: React.FC<OnlyElementProps> = ({ children }) => {
return <div>{children}</div>;
};
// ⭕사용 예시 - 올바른 사용
const App = () => (
<OnlyElementComponent>
<h1>이것은 React 요소입니다</h1>
</OnlyElementComponent>
);
// ❌사용 예시 - 잘못된 사용 (에러 발생)
// <OnlyElementComponent>
// Hello World!
// </OnlyElementComponent>
🔎 React.ReactChild
React.ReactChild는 JSX.Element, string, number를 포함하는 타입입니다.
배열은 허용되지 않으며, React 18부터는 사용이 지양됩니다.
import React from "react";
type ChildProps = {
children: React.ReactChild; // React 요소, 문자열, 숫자 등 허용
};
const ChildComponent: React.FC<ChildProps> = ({ children }) => {
return <div>{children}</div>;
};
// ⭕사용 예시 - 올바른 사용
const App = () => (
<>
<ChildComponent>텍스트도 가능합니다</ChildComponent>
<ChildComponent>{42}</ChildComponent>
</>
);
// ❌사용 예시 - 잘못된 사용 (에러 발생, 배열은 허용되지 않음)
// <ChildComponent>
// {["배열", "은", "허용되지 않습니다."]}
// </ChildComponent>
🔎 React.PropsWithChildren
React.PropsWithChildren은 children이 옵션인 경우에 사용하는 타입입니다.
children이 필수일 때는 부적합합니다.
type PropsWithChildren<P = unknown> = P & {
children?: ReactNode | undefined;
}
import React from "react";
type OptionalChildrenProps = React.PropsWithChildren<{
title: string;
}>;
const OptionalChildrenComponent: React.FC<OptionalChildrenProps> = ({
title,
children,
}) => {
return (
<div>
<h1>{title}</h1>
{children && <div>{children}</div>}
</div>
);
};
// 사용 예시 - children이 없어도 작동
const App = () => (
<>
<OptionalChildrenComponent title="제목만 있는 컴포넌트" />
<OptionalChildrenComponent title="제목과 내용이 있는 컴포넌트">
<p>내용</p>
</OptionalChildrenComponent>
</>
);
🔎 React.ReactNode
React.ReactNode는 가장 범용적이고 많이 사용되는 타입입니다.
모든 React 컴포넌트에서 children을 처리하기 위해 이 타입을 사용할 수 있어요.
거의 모든 타입의 값을 허용하기 때문에 children props의 타입을 지정할 때 가장 안전하고 널리 사용됩니다.
- 문자열 (string), 숫자 (number), 불리언 (boolean)
- null과 undefined
- JSX.Element, React Fragments 등 대부분의 React 요소들
import React from "react";
type FlexibleProps = {
children: React.ReactNode; // 거의 모든 타입 허용
};
const FlexibleComponent: React.FC<FlexibleProps> = ({ children }) => {
return <div>{children}</div>;
};
// 사용 예시 - 모든 타입이 허용됨
const App = () => (
<>
<FlexibleComponent>텍스트도 되고</FlexibleComponent>
<FlexibleComponent>{42}</FlexibleComponent>
<FlexibleComponent>
<h1>React 요소도 됩니다</h1>
</FlexibleComponent>
<FlexibleComponent>
<>
<p>Fragment도 됩니다</p>
<p>여러 요소도 가능합니다</p>
</>
</FlexibleComponent>
</>
);