Skip to content

ooooorobo/slots

Repository files navigation

생각중...

필요한 거

  • Slot에 키값 설정해 줄 수 있다
    • 이 키값은 type-safe해서 컴포넌트 안에 지정된 값만 사용할 수 있음
    • 컴포넌트 안에서 이 키값을 사용해서 각 Slot이 어디에 렌더링될지 정할 수 있음

1

Slot 컴포넌트를 사용하는 방법. Slot 컴포넌트의 name prop에 slotConfig 키값을 적으면, 키에 해당되는 값을 Slot 컴포넌트 자리에 치환해서 보여줌

// Card.tsx
const slotConfig = {
    header: CardHeader,
    rightTop: CardRightTop,
}

export const Card = ({ children }: Props) => {
    const slots = useSlots(slotConfig);
    return <div>
        {slots.header}
        <hr />
        {slots.rightTop}
    </div>
};

// page.tsx
export const Page = () => {
    return <div>
        <Card>
            <Slot name={'header'} >카드 헤더</Slot>
            <Slot name={'rightTop'}>
                <Icon name={'heart'} />
            </Slot>
        </Card>
    </div>
}

이러면 실제 페이지는 이렇게 치환됨

<div>
    <CardHeader>
        카드 헤더
    </CardHeader>
    <hr />
    <CardRightTop>
        <Icon name={'heart'} />
    </CardRightTop>
</div>

만약에 name이 header나 rightTop인 Slot을 하나도 넘기지 않았다면, CardHeader나 CardRightTop은 아예 렌더링되지 않음. Card 컴포넌트 안에서 조건부 렌더링을 하지 않아도 된다는 장점. 근데 각 슬롯의 레이아웃이 SlotConfig로 빠지게 되니 코드 흐름이 왔다갔다하게 되는 단점.

근데 type-safe하게 하려면 Context랑 Compound를 혼합해서 써야할 것 같음. 지금 CardV2 방식이 그거

const { SlotProvider, Slot, useSlots } = createSlot({
  header: CardHeader,
  rightTop: ({ children }: PropsWithChildren) => (
          <div style={{ position: "absolute", top: 0, right: 0 }}>{children}</div>
  ),
});

const Card = ({ children }: { children: ReactNode }) => {
  const slots = useSlots(children);

  return (
          <SlotProvider>
            <div className="card" style={{ position: "relative" }}>
              {slots.header}
              {slots.rightTop}
            </div>
          </SlotProvider>
  );
};

const CardNamespace = Object.assign(Card, {
  Slot,
});

export { CardNamespace as Card };

2

slotConfig에 받은 element를 슬롯 체크용으로 사용하는 버전

const slotConfig = {
    header: CardHeader,
    rightTop: CardRightTop,
}

export const Card = ({ children }: Props) => {
    const slots = useSlots(slotConfig);
    return <div>
        {slots.header && ( 
            <div className={'header'}>
                {slots.header}
            </div>
        )}
        {slots.rightTop && (
            <div className={'rightTop'}>
                {slots.rightTop}
            </div>
        )}
    </div>
};

// page.tsx
export const Page = () => {
    return <div>
        <Card>
            <CardHeader>카드 헤더</CardHeader>
            <CardRightTop>
                <Icon name={'heart'} />
            </CardRightTop>
        </Card>
    </div>
}

useSlots 내부적으로 어느 슬롯에 컴포넌트를 넣을지 판단할 때 element의 타입을 체크한다. 즉, Card의 children 중에 CardHeader 컴포넌트는 slots.header 자리에 들어가게 됨

이거 특징(장점?)은 CompoundPattern처럼 Card.CardHeader = CardHeader 같은 할당을 하지 않아도 된다는 점, 각 컴포넌트의 자리를 지정해 줄 수 있다는 점

근데 그렇다 보니 오히려 슬롯이라는 티가 잘 안 나는 것 같다

3

renderProps 방식으로 할 수도 있음

// page.tsx
import {ReactElement} from "react";

export const Page = () => {
    return <div>
        <Card>
            {({Slot}) => (
                <>
                    <Slot name={'header'}>카드 헤더</Slot>
                    <Slot name={'rightTop'}>
                        <Icon name={'heart'}/>
                    </Slot>
                </>
            )}
        </Card>
    </div>
}

// card.tsx
type CardProps = {
    children: (props: { Slot: ReactElement<{ name: keyof typeof slotConfig }> }) => ReactNode;
}

1번 방식이랑 비슷한데, 이렇게 하면 좋은 점은 Context나 Compound 방식 없이도 Slot을 type-safe하게 사용할 수 있음 (아마도)

Card 컴포넌트 내부가 1번에 비해서 단순해질 것 같다

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published