const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);
Children API의 명세
Children.count(children)
import { Children } from 'react';
function RowList({ children }) {
return (
<>
<h1>Total rows: {Children.count(children)}</h1>
...
</>
);
}
파라미터
- children: 컴포넌트가 받은 children prop의 값.
리턴값
- children 내부의 노드 수
주의 사항
- 빈 노드(null, undefined, Boolean), string, number 및 React 요소는 개별 노드로 계산됩니다.
- 배열 자료구조 자체는 개별 노드로 계산되지 않지만 자식 노드는 개별 노드로 계산합니다.
- 자식 노드에 대한 순회 깊이는 React 요소의 최대 깊이보다 깊을 수 없습니다.
- 순회 중 React 요소를 렌더링하지 않으며, 따라서 해당 React 요소가 렌더링하는 하위 항목을 순회하며 갯수를 게산하지 않습니다.
- 또한 Fragment도 순회하지 않습니다.
아래와 같이 예제를 작성해 보았습니다.
- 일반적인 루트가 하나인 JSX는 1로 계산됩니다.
- React 요소 하나(Fragment 또한)는 하나로 count 하기 때문입니다.
- 리액트 요소는 렌더링 되지 않으며, 리액트 요소의 자식 또한 순회하지 않기 떄문입니다.
- 배열의 경우, 중첩 배열에 flatMap을 적용한 배열의 길이가 결과입니다.
- 배열 자체는 세지 않습니다
- 배열 안에 있는 각 리액트 요소, 빈 노드만 하나로 계산합니다.
Children.forEach(children, fn, thisArg?)
Children.forEach(children, fn, thisArg?)를 호출하여 children 데이터 구조의 각 자식에 대해 일부 코드를 실행합니다.import { Children } from 'react';
function RowList({ children }) {
return (
<>
<h1>Total rows: {Children.count(children)}</h1>
...
</>
);
}
파라미터
- children : 컴포넌트가 받은 children prop의 값.
- fn : 배열 forEach 메서드 콜백과 유사하게 각 자식에 대해 실행하려는 함수입니다.
- 자식을 첫 번째 인수로, 인덱스를 두 번째 인수로 사용하여 호출됩니다. 인덱스는 0에서 시작하여 호출할 때마다 증가합니다.
- (optional) thisArg : fn 함수에 바인딩할 this 입니다. 생략하면 undefined 입니다.
리턴값
- children.forEach는 undefined를 반환합니다.
주의 사항
- 빈 노드(null, undefined, Boolean), string, number 및 React 요소는는 개별 노드로 계산됩니다.
- 배열 자료구조 자체는 개별 노드로 계산되지 않지만 자식 노드는 개별 노드로 계산합니다.
- 자식 노드에 대한 순회 깊이는 React 요소의 최대 깊이보다 깊을 수 없습니다.
- 순회 중 React 요소를 렌더링하지 않으며, 따라서 해당 React 요소가 렌더링하는 하위 항목을 순회하며 갯수를 게산하지 않습니다.
- 또한 Fragment도 순회하지 않습니다.
- fn를 이용해 키가 있는 요소 또는 요소 배열을 반환하면 반환된 요소의 키가 자식의 해당 원본 항목 키와 자동으로 결합됩니다.
- 즉, 배열의 fn에서 여러 요소를 반환할 때 긱 요소의 키는 해당 함수 컨텍스트 내에서 고유해야 합니다.
참고:
리액트 요소는 아래와 같은 객체일 뿐입니다.
컴포넌트를 렌더링하거나 돔을 만들지 않습니다.
// 아래 호출은
<Greeting name="Taylor" />
// 아래와 유사한 객체를 만듭니다.
{
type: Greeting,
props: {
name: 'Taylor'
},
key: null,
ref: null,
}
Children.only(children)
function Box({ children }) {
const element = Children.only(children);
// ...
파라미터
- children : 컴포넌트가 받은 children prop의 값.
리턴값
- childrend이 유효한 리액트 요소이면, 해당 요소를 반환합니다.
- 그렇지 않으면 에러를 throw 합니다.
주의 사항
이 메서드는 배열(예: Children.map의 반환 값)을 자식으로 전달하는 경우 항상 에러를 throw 합니다.
즉, children을 단일 요소만을 포함한 배열이 아니라 단일 React 요소로 강제합니다.
Children.toArray(children)
import { Children } from 'react';
export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...
파라미터
- children : 컴포넌트가 받은 children prop의 값.
리턴값
자식 요소의 플랫 배열을 반환합니다.
- 만약 자식이 중첩 배열이면 중첩을 전부 해체한 배열입니다.
주의 사항
사용법
children 변형
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
위의 예에서 RowList는 받는 모든 children을 <div className="Row"> 컨테이너로 래핑합니다.
예를 들어 부모 컴포넌트가 세 개의 <p> 태그를 children prop으로 RowList에 전달한다고 가정해 보겠습니다.
<RowList>
<p>This is the first item.</p>
<p>This is the second item.</p>
<p>This is the third item.</p>
</RowList>
<div className="RowList">
<div className="Row">
<p>This is the first item.</p>
</div>
<div className="Row">
<p>This is the second item.</p>
</div>
<div className="Row">
<p>This is the third item.</p>
</div>
왜 children prop이 항상 배열이 아니죠?
React에서 children prop은 불투명(opaque)한 데이터 구조로 간주됩니다.
즉, children의 구조에 의존하여 로직을 적용하면 안됩니다.
자식을 변환, 필터링 또는 계산하려면 Children 메서드를 사용해야 합니다.
import { Children, Fragment } from "react";
function RowList({ children }) {
let key = 0;
return (
<div className="RowList">
{Children.map(children, (child) => [
<Fragment key={++key + ""}></Fragment>,
child
])}
</div>
);
}
export default function App() {
return (
<RowList>
<MyComp key="1">This is the first item.</MyComp>
<MyComp key="2">This is the second item.</MyComp>
<MyComp key="3">This is the third item.</MyComp>
</RowList>
);
}
const MyComp = ({ children, ...rest }) => <p {...rest}>{children}</p>;
children data 구조는 렌더린됭 결과를 의미하지 않습니다.
자식 데이터 구조에는 JSX로 전달하는 컴포넌트의 렌더링된 출력이 존재하는 것이 아닙니다.
아래 예에서 RowList가 파라미터로 받은 children에는 3개가 아닌 2개의 항목만 포함됩니다.
- <p>This is the first item.</p>
- <MoreRows />
children을 조작해서 <MoreRows />와 같은 내부 컴포넌트의 렌더링된 출력을 가져올 방법은 없습니다.
이것이 일반적으로 대체 솔루션 중 하나를 사용하는 것이 더 나은 이유입니다.
각 자식(child)에 대해 코드 실행
Children.forEach를 호출하여 자식 데이터 구조의 각 자식을 반복합니다.
해당 메서드는 값을 반환하지 않으며 array forEach 메서드와 유사합니다.
이를 사용하여 고유한 배열 구성과 같은 사용자 지정 로직을 실행할 수 있습니다.
앞서 언급했듯이 children을 조작할 때 내부 컴포넌트의 렌더링된 출력을 얻을 수 있는 방법은 없습니다.
이것이 일반적으로 대체 솔루션 중 하나를 사용하는 것이 더 나은 이유입니다.
각 자식(child)을 배열로 변환
Children.toArray(children)를 호출하여 자식 데이터 구조를 일반 JavaScript 배열로 전환합니다.
이를 통해 필터, 정렬 또는 반전과 같은 내장 배열 메서드를 사용하여 배열을 조작할 수 있습니다.
Children API의 대안
import { Children } from 'react';
children prop 사용과 혼동하지 마세요. children prop를 사용하는 것은 매우 좋고 권장합니다.
여러 컴포넌트 노출
Children 메서드를 사용하여 자식을 조작하면 종종 깨지기 쉬운 코드가 생성됩니다.
JSX에서 컴포넌트에 자식을 전달할 때 일반적으로 컴포넌트가 개별 자식을 조작하거나 변환할 것이라고 기대하지 않습니다.
가능하면 Children 메서드를 사용하지 마세요.
예를 들어 RowList의 모든 자식을 <div className="Row">로 래핑하려면
Row 컴포넌트를 내보내고 다음과 같이 모든 행을 수동으로 래핑합니다.
import { RowList, Row } from './RowList.js';
export default function App() {
return (
<RowList>
<Row>
<p>This is the first item.</p>
</Row>
<Row>
<p>This is the second item.</p>
</Row>
<Row>
<p>This is the third item.</p>
</Row>
</RowList>
);
}
import { RowList, Row } from './RowList.js';
export default function App() {
return (
<RowList>
<Row>
<p>This is the first item.</p>
</Row>
<MoreRows />
</RowList>
);
}
function MoreRows() {
return (
<>
<Row>
<p>This is the second item.</p>
</Row>
<Row>
<p>This is the third item.</p>
</Row>
</>
);
}
객체를 prop으로 전달받기
import { RowList, Row } from './RowList.js';
export default function App() {
return (
<RowList rows={[
{ id: 'first', content: <p>This is the first item.</p> },
{ id: 'second', content: <p>This is the second item.</p> },
{ id: 'third', content: <p>This is the third item.</p> }
]} />
);
}
import { RowList, Row } from './RowList.js';
export default function App() {
return (
<RowList rows={[
{ id: 'first', content: <p>This is the first item.</p> },
{ id: 'second', content: <p>This is the second item.</p> },
{ id: 'third', content: <p>This is the third item.</p> }
]} />
);
}
자식을 JSX로 전달하는 것과 달리 이 접근 방식을 사용하면 각 탭에 헤더와 같은 추가 컴포넌트를 전달할 수 있습니다.
tabs 자료구조로로 직접 작업하고 있으며 JS 배열이기 때문에 Children 메서드가 필요하지 않습니다.
render props 패턴 사용하기
App.js
import TabSwitcher from './TabSwitcher.js';
export default function App() {
return (
<TabSwitcher
tabIds={['first', 'second', 'third']}
getHeader={tabId => {
return tabId[0].toUpperCase() + tabId.slice(1);
}}
renderContent={tabId => {
return <p>This is the {tabId} item.</p>;
}}
/>
);
}
Tabswitcher.js
import { useState } from 'react';
export default function TabSwitcher({ tabIds, getHeader, renderContent }) {
const [selectedId, setSelectedId] = useState(tabIds[0]);
return (
<>
{tabIds.map((tabId) => (
<button
key={tabId}
onClick={() => setSelectedId(tabId)}
>
{getHeader(tabId)}
</button>
))}
<hr />
<div key={selectedId}>
<h3>{getHeader(selectedId)}</h3>
{renderContent(selectedId)}
</div>
</>
);
}
renderContent와 같은 prop은 사용자 인터페이스의 일부를 렌더링하는 방법을 지정하는 prop이기 때문에
render prop이라고 합니다.
특별한 것은 없습니다. 함수일 뿐인 일반적인 prop입니다.
render prop은 함수이므로 정보를 전달할 수 있습니다.
예를 들어 이 RowList 컴포넌트는 각 행의 ID와 인덱스를 renderRow render prop에 전달합니다.
이 prop은 인덱스를 사용하여 짝수 행을 강조 표시합니다.
App.js
import { RowList, Row } from './RowList.js';
export default function App() {
return (
<RowList
rowIds={['first', 'second', 'third']}
renderRow={(id, index) => {
return (
<Row isHighlighted={index % 2 === 0}>
<p>This is the {id} item.</p>
</Row>
);
}}
/>
);
}
RowList.js
import { Fragment } from 'react';
export function RowList({ rowIds, renderRow }) {
return (
<div className="RowList">
<h1 className="RowListHeader">
Total rows: {rowIds.length}
</h1>
{rowIds.map((rowId, index) =>
<Fragment key={rowId}>
{renderRow(rowId, index)}
</Fragment>
)}
</div>
);
}
export function Row({ children, isHighlighted }) {
return (
<div className={[
'Row',
isHighlighted ? 'RowHighlighted' : ''
].join(' ')}>
{children}
</div>
);
}
문제 해결
커스텀 컴포넌트를 전달했지만 Children 메서드가 렌더링 결과를 표시하지 않습니다.
다음과 같이 두 개의 자식을 RowList에 전달한다고 가정합니다.
<RowList>
<p>First item</p>
<MoreRows />
</RowList>
Reference
'FrontEnd' 카테고리의 다른 글
React의 cloneElement API, 기존 엘리먼트를 기반으로 새로운 엘리먼트 생성하는 방법 알아보기 (0) | 2023.02.16 |
---|---|
리액트 디자인 패턴(React design pattern) : Compound Component Pattern(컴파운드 컴포넌트패턴)과 Uncontrolled Component Pattern(유상태 컴포넌트 패턴) (0) | 2023.02.15 |
Stitches와 Radix를 이용해 디자인 시스템 만들기 (0) | 2023.02.14 |
나만의 CSS Reset(리셋) 만들기 [번역] (0) | 2023.02.13 |
가장 일반적이며 기초적인 CSS 문제와 해결 방법 [번역] (0) | 2023.02.13 |