Собственно вопрос - как при фильтровании children понять, какой элемент является Column, какой GridToolbar и т.д.? Есть ли какие-либо хорошие практики для этого? Склоняюсь к тому, чтобы проверять свойство type.name
, которое вроде как должно быть названием компонента, т. е. 'Column', 'GridToolbar' и т. д.
Можно просто type, без name. Только, конечно, в этом случае сравнивать надо не со строкой, а с самим компонентом.
Но должны ли это быть именно children?
Есть вариант......оформить передачу каждого из элементов
через отдельный prop:
While this is less common, sometimes you might need multiple “holes” in a component. In such cases you may come up with your own convention instead of using children
:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
UPD. Вынесено из комментариев:
Мне хочется в React достичь того, что называется слотами в Web Components и Vue.js.
Окей, сделаем компонент слота:
function Slot({ name, content, scope, children = null }) {
const slotContent = []
.concat(content || [])
.filter(n => n.props.slot === name)
.map(n => n.type === 'template'
? [].concat(n.props.children).map(m => m instanceof Function ? m(scope) : m)
: n
);
return slotContent.length
? slotContent
: children;
}
Пример использования - компонент таблицы с возможностью кастомизации внешнего вида столбцов (как заголовков, так и ячеек с данными):
function Table({ columns, data, children }) {
return (
<table>
<thead>
<tr>{columns.map(n => (
<th>
<Slot name={`header.${n.name}`} content={children} scope={n}>
{n.label}
</Slot>
</th>))}
</tr>
</thead>
<tbody>{data.map((row, index) => (
<tr>{columns.map(({ name }) => (
<td>
<Slot name={`cell.${name}`} content={children} scope={{ name, row, index }}>
{row[name]}
</Slot>
</td>))}
</tr>))}
</tbody>
</table>
);
}
const COLUMNS = [
{ name: 'num', label: 'number' },
{ name: 'str', label: 'string' },
{ name: 'bool', label: 'boolean' },
];
const DATA = [
{ num: 69, str: 'hello, world!!', bool: false },
{ num: 187, str: 'fuck the world', bool: true },
{ num: 666, str: 'fuck everything', bool: true },
];
<Table columns={COLUMNS} data={DATA}>
{/* в один слот можно передавать контент несколько раз */}
<u slot="header.num" style={{ color: 'red' }}>NUM</u>
<i slot="header.num" style={{ color: 'green' }}>!!!</i>
{/* или, за один раз передавать несколько элементов */}
<template slot="header.bool">
boolean:
<input type="checkbox" disabled />
or
<input type="checkbox" disabled defaultChecked />
</template>
{/* контент слота можно определять динамически */}
<template slot="cell.bool">
{({ row }) => <input type="checkbox" disabled defaultChecked={row.bool} />}
</template>
</Table>
Можно даже сделать, чтобы было похоже на то, что вы показывали в тексте вопроса - добавляем компонент строки таблицы, экземпляры которого будут получать описания столбцов и контент слотов,
вот как-то так:
function TableColumn() {
// да, всё правильно - тут пусто, этот компонент нужен только как место, где будет
// собрано всё, что относится к столбцу, сам он ничего делать не должен
return null;
}
function Table({ data, children }) {
const columns = []
.concat(children || [])
.reduce((acc, n) => (
(n.type === TableColumn) && acc.push(n.props),
acc
), []);
return (
<table>
<thead>
<tr>{columns.map(({ children, ...n }) => (
<th>
<Slot name="header" content={children} scope={n}>
{n.label}
</Slot>
</th>))}
</tr>
</thead>
<tbody>{data.map((row, index) => (
<tr>{columns.map(({ name, children }) => (
<td>
<Slot name="cell" content={children} scope={{ name, row, index }}>
{row[name]}
</Slot>
</td>))}
</tr>))}
</tbody>
</table>
);
}
<Table data={DATA}>
<TableColumn name="row-index" label="#">
<template slot="cell">
{({ index }) => -~index}
</template>
</TableColumn>
<TableColumn name="num" label="number" />
<TableColumn name="str" label="string" />
<TableColumn name="bool">
<span slot="header" style={{ color: 'red' }}>boolean</span>
<template slot="cell">
{({ row }) => <input type="checkbox" disabled defaultChecked={row.bool} />}
</template>
</TableColumn>
</Table>