Собственно вопрос - как при фильтровании 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>