Compound Components
2つ以上のComponentを一緒に使うことを表現する際に使う
1つの 親
と、複数の 子
という関係になる
利用者から見れば、どのComponent同士を一緒に使うべきかが明確になる
それら複数のComponentの中で状態を扱う時に便利
その状態を利用者に表出させずに扱える
1つのimoprtで済む
こういうやつ
jsx<Tab>
<Tab.Item>あいてむ1</Tab.Item>
<Tab.Item>あいてむ2</Tab.Item>
<Tab.Item>あいてむ3</Tab.Item>
</Tab>
定義の仕方
こんな関数を用意して
tsconst compound = <
Root extends React.FC<any>,
Children extends { [key: string]: React.FC<any> },
>(
root: Root,
children: Children,
): Root & Children => {
return Object.assign(root, children) as any;
};
使う
tsexport const FormLayout = compound(FormLayoutRoot, { Group, Group2 });
例
<Tab>
と <Tab.Item>
とか
<Select>
と <Select.Option>
とか
<Menu>
<Menu.Button>
<Menu.Items>
<Menu.Item>
参考
useContextを使っている
Parent.Child
という形にはなっていないが、例としてわかりやすい
useContextと相性が良い
recoilだとglobalに影響が出る
同じ構造のものを使う時にfamilyを意識しないといけなくなる
子要素を複数置けるとか
これだけなら、listで受け取れば良いだけか
これはrender props patternでもできる
状態を共有できる
これはrender props patternでもできる
配置する場所を自由に指定できる
ここが大きな違いだと思う

ShopifyのPoralisのStack
ts// item.tsx
export function Item({children, fill}: ItemProps) {
const className = classNames(styles.Item, fill && styles['Item-fill']);
return <div className={className}>{children}</div>;
}
// stack.tsx
export const Stack = memo(function Stack({
children,
vertical,
spacing,
distribution,
alignment,
wrap,
}: StackProps) {
const className = classNames(
styles.Stack,
vertical && styles.vertical,
spacing && styles[variationName('spacing', spacing)],
distribution && styles[variationName('distribution', distribution)],
alignment && styles[variationName('alignment', alignment)],
wrap === false && styles.noWrap,
);
const itemMarkup = elementChildren(children).map((child, index) => {
const props = {key: index};
return wrapWithComponent(child, Item, props);
});
return <div className={className}>{itemMarkup}</div>;
}) as NamedExoticComponent<StackProps> & {
Item: typeof Item;
};
Stack.Item = Item;
elementChildren
wrapWithComponent