Children
cho phép bạn thao tác và chuyển đổi JSX mà bạn nhận được dưới dạng children
prop.
const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);
Tham khảo
Children.count(children)
Gọi Children.count(children)
để đếm số lượng phần tử con trong cấu trúc dữ liệu children
.
import { Children } from 'react';
function RowList({ children }) {
return (
<>
<h1>Tổng số hàng: {Children.count(children)}</h1>
...
</>
);
}
Tham số
children
: Giá trị củachildren
prop mà component của bạn nhận được.
Giá trị trả về
Số lượng node bên trong children
.
Lưu ý
- Các node rỗng (
null
,undefined
và Booleans), strings, numbers và React elements được tính là các node riêng lẻ. Mảng không được tính là các node riêng lẻ, nhưng các phần tử con của chúng thì có. Việc duyệt không đi sâu hơn các React elements: chúng không được render và các phần tử con của chúng không được duyệt. Fragments không được duyệt.
Children.forEach(children, fn, thisArg?)
Gọi Children.forEach(children, fn, thisArg?)
để chạy một đoạn code cho mỗi phần tử con trong cấu trúc dữ liệu children
.
import { Children } from 'react';
function SeparatorList({ children }) {
const result = [];
Children.forEach(children, (child, index) => {
result.push(child);
result.push(<hr key={index} />);
});
// ...
Tham số
children
: Giá trị củachildren
prop mà component của bạn nhận được.fn
: Hàm bạn muốn chạy cho mỗi phần tử con, tương tự như callback của arrayforEach
method. Nó sẽ được gọi với phần tử con là đối số đầu tiên và chỉ mục của nó là đối số thứ hai. Chỉ mục bắt đầu từ0
và tăng lên trên mỗi lần gọi.- optional
thisArg
: Giá trịthis
mà hàmfn
sẽ được gọi. Nếu bỏ qua, nó làundefined
.
Giá trị trả về
Children.forEach
trả về undefined
.
Lưu ý
- Các node rỗng (
null
,undefined
và Booleans), strings, numbers và React elements được tính là các node riêng lẻ. Mảng không được tính là các node riêng lẻ, nhưng các phần tử con của chúng thì có. Việc duyệt không đi sâu hơn các React elements: chúng không được render và các phần tử con của chúng không được duyệt. Fragments không được duyệt.
Children.map(children, fn, thisArg?)
Gọi Children.map(children, fn, thisArg?)
để ánh xạ hoặc chuyển đổi mỗi phần tử con trong cấu trúc dữ liệu children
.
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
Tham số
children
: Giá trị củachildren
prop mà component của bạn nhận được.fn
: Hàm ánh xạ, tương tự như callback của arraymap
method. Nó sẽ được gọi với phần tử con là đối số đầu tiên và chỉ mục của nó là đối số thứ hai. Chỉ mục bắt đầu từ0
và tăng lên trên mỗi lần gọi. Bạn cần trả về một React node từ hàm này. Đây có thể là một node rỗng (null
,undefined
hoặc Boolean), một string, một number, một React element hoặc một mảng các React node khác.- optional
thisArg
: Giá trịthis
mà hàmfn
sẽ được gọi. Nếu bỏ qua, nó làundefined
.
Giá trị trả về
Nếu children
là null
hoặc undefined
, trả về giá trị tương tự.
Nếu không, trả về một mảng phẳng bao gồm các node bạn đã trả về từ hàm fn
. Mảng trả về sẽ chứa tất cả các node bạn đã trả về ngoại trừ null
và undefined
.
Lưu ý
-
Các node rỗng (
null
,undefined
và Booleans), strings, numbers và React elements được tính là các node riêng lẻ. Mảng không được tính là các node riêng lẻ, nhưng các phần tử con của chúng thì có. Việc duyệt không đi sâu hơn các React elements: chúng không được render và các phần tử con của chúng không được duyệt. Fragments không được duyệt. -
Nếu bạn trả về một element hoặc một mảng các element có keys từ
fn
, các keys của các element được trả về sẽ tự động được kết hợp với key của mục gốc tương ứng từchildren
. Khi bạn trả về nhiều element từfn
trong một mảng, các keys của chúng chỉ cần là duy nhất cục bộ giữa chúng với nhau.
Children.only(children)
Gọi Children.only(children)
để xác nhận rằng children
đại diện cho một React element duy nhất.
function Box({ children }) {
const element = Children.only(children);
// ...
Tham số
children
: Giá trị củachildren
prop mà component của bạn nhận được.
Giá trị trả về
Nếu children
là một element hợp lệ, trả về element đó.
Nếu không, đưa ra một lỗi.
Lưu ý
- Phương thức này luôn ném ra lỗi nếu bạn truyền một mảng (chẳng hạn như giá trị trả về của
Children.map
) làmchildren
. Nói cách khác, nó thực thi rằngchildren
là một React element duy nhất, không phải là một mảng với một element duy nhất.
Children.toArray(children)
Gọi Children.toArray(children)
để tạo một mảng từ cấu trúc dữ liệu children
.
import { Children } from 'react';
export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...
Tham số
children
: Giá trị củachildren
prop mà component của bạn nhận được.
Giá trị trả về
Trả về một mảng phẳng các element trong children
.
Lưu ý
- Các node rỗng (
null
,undefined
và Booleans) sẽ bị bỏ qua trong mảng trả về. Các keys của các element được trả về sẽ được tính toán từ các keys của các element gốc và mức độ lồng nhau và vị trí của chúng. Điều này đảm bảo rằng việc làm phẳng mảng không gây ra thay đổi trong hành vi.
Cách sử dụng
Chuyển đổi các phần tử con
Để chuyển đổi JSX children mà component của bạn nhận được dưới dạng children
prop, hãy gọi Children.map
:
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
Trong ví dụ trên, RowList
bao bọc mọi phần tử con mà nó nhận được vào một container <div className="Row">
. Ví dụ: giả sử component cha truyền ba thẻ <p>
làm children
prop cho RowList
:
<RowList>
<p>Đây là mục đầu tiên.</p>
<p>Đây là mục thứ hai.</p>
<p>Đây là mục thứ ba.</p>
</RowList>
Sau đó, với việc triển khai RowList
ở trên, kết quả render cuối cùng sẽ trông như thế này:
<div className="RowList">
<div className="Row">
<p>Đây là mục đầu tiên.</p>
</div>
<div className="Row">
<p>Đây là mục thứ hai.</p>
</div>
<div className="Row">
<p>Đây là mục thứ ba.</p>
</div>
</div>
Children.map
tương tự như chuyển đổi mảng với map()
. Sự khác biệt là cấu trúc dữ liệu children
được coi là opaque. Điều này có nghĩa là ngay cả khi đôi khi nó là một mảng, bạn không nên cho rằng nó là một mảng hoặc bất kỳ kiểu dữ liệu cụ thể nào khác. Đây là lý do tại sao bạn nên sử dụng Children.map
nếu bạn cần chuyển đổi nó.
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
Tìm hiểu sâu
Trong React, children
prop được coi là một cấu trúc dữ liệu opaque. Điều này có nghĩa là bạn không nên dựa vào cách nó được cấu trúc. Để chuyển đổi, lọc hoặc đếm các phần tử con, bạn nên sử dụng các phương thức Children
.
Trong thực tế, cấu trúc dữ liệu children
thường được biểu diễn dưới dạng một mảng bên trong. Tuy nhiên, nếu chỉ có một phần tử con duy nhất, thì React sẽ không tạo một mảng bổ sung vì điều này sẽ dẫn đến chi phí bộ nhớ không cần thiết. Miễn là bạn sử dụng các phương thức Children
thay vì trực tiếp xem xét children
prop, code của bạn sẽ không bị hỏng ngay cả khi React thay đổi cách cấu trúc dữ liệu được thực hiện trên thực tế.
Ngay cả khi children
là một mảng, Children.map
có hành vi đặc biệt hữu ích. Ví dụ: Children.map
kết hợp các keys trên các element được trả về với các keys trên children
mà bạn đã truyền cho nó. Điều này đảm bảo rằng các JSX children ban đầu không “mất” keys ngay cả khi chúng được bao bọc như trong ví dụ trên.
Chạy một đoạn code cho mỗi phần tử con
Gọi Children.forEach
để lặp lại trên mỗi phần tử con trong cấu trúc dữ liệu children
. Nó không trả về bất kỳ giá trị nào và tương tự như array forEach
method. Bạn có thể sử dụng nó để chạy logic tùy chỉnh như xây dựng mảng của riêng bạn.
import { Children } from 'react'; export default function SeparatorList({ children }) { const result = []; Children.forEach(children, (child, index) => { result.push(child); result.push(<hr key={index} />); }); result.pop(); // Remove the last separator return result; }
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> <h1 className="RowListHeader"> Tổng số hàng: {Children.count(children)} </h1> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
Chuyển đổi các phần tử con thành một mảng
Gọi Children.toArray(children)
để biến cấu trúc dữ liệu children
thành một mảng JavaScript thông thường. Điều này cho phép bạn thao tác mảng với các phương thức mảng tích hợp như filter
, sort
hoặc reverse
.
import { Children } from 'react'; export default function ReversedList({ children }) { const result = Children.toArray(children); result.reverse(); return result; }
Các giải pháp thay thế
Trình bày nhiều component
Việc thao tác các phần tử con bằng các phương thức Children
thường dẫn đến code dễ bị lỗi. Khi bạn truyền các phần tử con vào một component trong JSX, bạn thường không mong đợi component đó thao tác hoặc chuyển đổi các phần tử con riêng lẻ.
Khi có thể, hãy cố gắng tránh sử dụng các phương thức Children
. Ví dụ: nếu bạn muốn mọi phần tử con của RowList
được bao bọc trong <div className="Row">
, hãy xuất một component Row
và tự bao bọc từng hàng vào nó như sau:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>Đây là mục đầu tiên.</p> </Row> <Row> <p>Đây là mục thứ hai.</p> </Row> <Row> <p>Đây là mục thứ ba.</p> </Row> </RowList> ); }
Không giống như sử dụng Children.map
, phương pháp này không tự động bao bọc mọi phần tử con. Tuy nhiên, phương pháp này có một lợi ích đáng kể so với ví dụ trước với Children.map
vì nó hoạt động ngay cả khi bạn tiếp tục trích xuất thêm các component. Ví dụ: nó vẫn hoạt động nếu bạn trích xuất component MoreRows
của riêng bạn:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>Đây là mục đầu tiên.</p> </Row> <MoreRows /> </RowList> ); } function MoreRows() { return ( <> <Row> <p>Đây là mục thứ hai.</p> </Row> <Row> <p>Đây là mục thứ ba.</p> </Row> </> ); }
Điều này sẽ không hoạt động với Children.map
vì nó sẽ “nhìn thấy” <MoreRows />
như một phần tử con duy nhất (và một hàng duy nhất).
Chấp nhận một mảng các đối tượng làm prop
Bạn cũng có thể truyền một mảng một cách rõ ràng làm prop. Ví dụ: RowList
này chấp nhận một mảng rows
làm prop:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rows={[ { id: 'first', content: <p>Đây là mục đầu tiên.</p> }, { id: 'second', content: <p>Đây là mục thứ hai.</p> }, { id: 'third', content: <p>Đây là mục thứ ba.</p> } ]} /> ); }
Vì rows
là một mảng JavaScript thông thường, component RowList
có thể sử dụng các phương thức mảng tích hợp như map
trên nó.
Mô hình này đặc biệt hữu ích khi bạn muốn có thể truyền thêm thông tin dưới dạng dữ liệu có cấu trúc cùng với các phần tử con. Trong ví dụ dưới đây, component TabSwitcher
nhận một mảng các đối tượng làm prop tabs
:
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabs={[ { id: 'first', header: 'First', content: <p>Đây là mục đầu tiên.</p> }, { id: 'second', header: 'Second', content: <p>Đây là mục thứ hai.</p> }, { id: 'third', header: 'Third', content: <p>Đây là mục thứ ba.</p> } ]} /> ); }
Không giống như truyền các phần tử con dưới dạng JSX, phương pháp này cho phép bạn liên kết một số dữ liệu bổ sung như header
với mỗi mục. Vì bạn đang làm việc trực tiếp với tabs
và nó là một mảng, bạn không cần các phương thức Children
.
Gọi một render prop để tùy chỉnh rendering
Thay vì tạo JSX cho mọi mục duy nhất, bạn cũng có thể truyền một hàm trả về JSX và gọi hàm đó khi cần thiết. Trong ví dụ này, component App
truyền một hàm renderContent
cho component TabSwitcher
. Component TabSwitcher
chỉ gọi renderContent
cho tab đã chọn:
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>Đây là mục {tabId}.</p>; }} /> ); }
Một prop như renderContent
được gọi là render prop vì nó là một prop chỉ định cách render một phần của giao diện người dùng. Tuy nhiên, không có gì đặc biệt về nó: nó là một prop thông thường, tình cờ là một hàm.
Render props là các hàm, vì vậy bạn có thể truyền thông tin cho chúng. Ví dụ: component RowList
này truyền id
và index
của mỗi hàng cho render prop renderRow
, sử dụng index
để làm nổi bật các hàng chẵn:
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>Đây là mục {id}.</p> </Row> ); }} /> ); }
Đây là một ví dụ khác về cách các component cha và con có thể hợp tác mà không cần thao tác các phần tử con.
Khắc phục sự cố
Tôi truyền một component tùy chỉnh, nhưng các phương thức Children
không hiển thị kết quả render của nó
Giả sử bạn truyền hai phần tử con cho RowList
như sau:
<RowList>
<p>Mục đầu tiên</p>
<MoreRows />
</RowList>
Nếu bạn thực hiện Children.count(children)
bên trong RowList
, bạn sẽ nhận được 2
. Ngay cả khi MoreRows
render 10 mục khác nhau hoặc nếu nó trả về null
, Children.count(children)
vẫn sẽ là 2
. Từ góc độ của RowList
, nó chỉ “nhìn thấy” JSX mà nó đã nhận được. Nó không “nhìn thấy” các phần bên trong của component MoreRows
.
Hạn chế này gây khó khăn cho việc trích xuất một component. Đây là lý do tại sao các giải pháp thay thế được ưu tiên hơn là sử dụng Children
.