createProxy createProxy는 Show.div나 For.ul처럼 동일한 동작을 다양한 태그로 제공하는 컴포넌트 패밀리를 만들며, 라이브러리 외부에서도 활용할 수 있습니다. 아래 단계는 Custom Clickable 예제로 전체 흐름을 설명합니다.
function createProxy<TProxy extends object, TBase extends object = TProxy>(
base: TBase,
renderForTag: (tag: any) => React.ForwardRefExoticComponent<any>,
category: RegistryCategory
): TProxy;props와 base 정의 # 공통 동작을 담은 base 컴포넌트를 먼저 정의합니다. 프록시는 이를 태그 변형마다 재사용합니다.
import { ComponentPropsWithRef, createElement, forwardRef } from "react";
import { createProxy, BaseTypeHelperFn, ProxyType } from "@ilokesto/utilinent";
export type ClickableProps = {
onClick?: React.MouseEventHandler;
disabled?: boolean;
children?: React.ReactNode;
};
type BaseClickableType<X = object> = {
(props: X & ClickableProps): React.ReactNode;
};
const BaseClickable: BaseClickableType = ({ onClick, disabled, children }) => {
return (
<button type="button" onClick={onClick} disabled={disabled}>
{children}
</button>
);
};왜 BaseClickableType<X = object>인가요? 프록시는 ClickableProps와 태그/컴포넌트 고유 props를 함께 받아야 합니다. X는 태그 props 자리이고 X & ClickableProps로 병합됩니다. 기본값 object는 base를 추가 props 없이도 쓰기 쉽게 해줍니다. 제네릭이 없으면 Clickable.div 같은 변형에서 className, href, role 같은 네이티브 props 타입이 사라집니다.
renderForTag + ProxyType 추가 # 태그 렌더러와 프록시 타입을 정의해 Clickable.div 같은 변형을 타입으로 보장합니다.
const renderForTag =
(tag: any) =>
forwardRef(function Render(
{ onClick, disabled, children, ...props }: ClickableProps & ComponentPropsWithRef<any>,
ref: any
) {
return createElement(
tag,
{ ...props, ref, onClick, "aria-disabled": disabled ? true : undefined },
children
);
});
interface BaseClickableFn extends BaseTypeHelperFn {
type: BaseClickableType<this["props"]>;
}
export type ClickableType = ProxyType<BaseClickableFn, "clickable">;
export const Clickable: ClickableType = createProxy(BaseClickable, renderForTag, "clickable");왜 BaseClickableFn extends BaseTypeHelperFn이고 this["props"]를 쓰나요? ProxyType은 BaseTypeHelperFn을 타입 레벨 함수처럼 취급합니다. 각 태그의 props를 props 슬롯에 주입한 뒤 type을 평가해 최종 시그니처를 만듭니다. this["props"]를 사용해야 태그별 props가 반영되어 Clickable.div는 ComponentPropsWithRef<"div"> + ClickableProps가 되고, Clickable.Link는 등록된 컴포넌트 props를 받습니다. 여기에 ClickableProps만 고정하면 태그 props가 전달되지 않습니다.
커스텀 컴포넌트 등록 (선택) # 카테고리에 컴포넌트를 등록하고 UtilinentRegister를 확장해 Clickable.*를 타입 안전하게 사용합니다.
import { PluginManager } from "@ilokesto/utilinent";
import Link from "./Link";
PluginManager.register({
clickable: {
Link,
},
});
declare module "@ilokesto/utilinent" {
interface UtilinentRegister {
clickable: {
Link: typeof Link;
};
}
}사용 예시 # 기본 컴포넌트와 태그/컴포넌트 변형을 사용할 수 있습니다.
<Clickable onClick={() => console.log("click")}>Default button</Clickable>
<Clickable.div onClick={() => console.log("click")}>Div wrapper</Clickable.div>
<Clickable.Link href="/docs">Custom Link</Clickable.Link>