createComponent
createComponentは、バリアントなど条件付きスタイルをサポートするコンポーネントを作成します。また、createSlotComponentを使用すれば、簡単にスロット付きコンポーネントを作成できます。
概要
createComponentは、バリアントなど条件付きスタイルをサポートするコンポーネントを作成します。作成したコンポーネントは、継承することが可能で、拡張性が高く、classNameやdisplayNameも生成するので、プロジェクトで一貫した命名規則を持つコンポーネントを作成することもできます。
使い方
単一のコンポーネントを作成する場合は、createComponentを使用し、スロット付きコンポーネントを作成する場合は、createSlotComponentを使用します。
createComponent
単一のコンポーネントを作成する場合は、createComponentを使用します。
import type { HTMLStyledProps, ThemeProps } from "@yamada-ui/react"
import { createComponent, defineComponentStyle } from "@yamada-ui/react"
import type { HTMLStyledProps, ThemeProps } from "@/components/ui"
import { createComponent, defineComponentStyle } from "@/components/ui"
import type { HTMLStyledProps, ThemeProps } from "@workspaces/ui"
import { createComponent, defineComponentStyle } from "@workspaces/ui"
const componentStyle = defineComponentStyle({
base: {
/* base style */
},
variants: {
/* variant style */
},
sizes: {
/* size style */
},
props: {
/* props style */
},
compounds: {
/* compound style */
},
defaultProps: {
/* default props */
},
})
type ComponentStyle = typeof componentStyle
export interface ComponentProps
extends HTMLStyledProps<"div">,
ThemeProps<ComponentStyle> {}
const {
component,
ComponentContext,
PropsContext: ComponentPropsContext,
useComponentContext,
usePropsContext: useComponentPropsContext,
withContext,
useComponentProps,
} = createComponent<ComponentProps, ComponentStyle>("component", componentStyle)
export { ComponentPropsContext, useComponentPropsContext }
defineComponentStyleは、コンポーネントのスタイルを定義する関数です。この関数は、型補完をする重要な役割があります。- 第一引数は、
classNameやdisplayNameに使用されるコンポーネント名を設定します。 - 第二引数は、コンポーネントのスタイルを設定します。
コンポーネントを作成する
コンポーネントを作成するには、withContextを使用します。引数にHTML要素名または関数を設定します。withContextは、提供されたスタイルとPropsContextから提供されるpropsを使用します。
export const Component = withContext("div")()
export const Component = withContext((props) => {
return <styled.div {...props} />
})()
もし、提供されたスタイルとPropsContextから提供されるpropsを使用しない、またはそのロジックをハンドリングしたい場合は、componentを使用します。
export const Component = component((props) => {
const computedProps = useComponentProps(props)
return <styled.div {...computedProps} />
})()
propsを計算する
withContextやcomponentは、提供されたpropsに対して多段階計算を行うことができます。
export const Component = withContext("button")(
{ "aria-label": "Default Label" },
({ "aria-label": ariaLabel, ...rest }) => ({
ariaLabel: ariaLabel === "Default Label" ? "Changed Label" : ariaLabel,
...rest,
}),
)
propsを転送する
スタイルのpropsは、スタイリングを計算した後フィルタリングされます。もし、コンポーネントのロジックでも使用する場合は、transferPropsを使用します。
export const Component = withContext(
({ size, ...rest }) => {
return <styled.button data-size={size} {...rest} />
},
{
transferProps: ["size"],
},
)()
コンポーネントを継承する
createComponentで作成したコンポーネントは、継承することができます。
import { Component } from "./component"
const additionalComponentStyle = defineComponentStyle({
base: {
/* base style */
},
variants: {
/* variant style */
},
sizes: {
/* size style */
},
props: {
/* props style */
},
compounds: {
/* compound style */
},
defaultProps: {
/* default props */
},
})
type AdditionalComponentStyle = typeof additionalComponentStyle
export interface AdditionalComponentProps
extends HTMLStyledProps<"div">,
ThemeProps<AdditionalComponentStyle> {}
const {
ComponentContext,
PropsContext: AdditionalComponentPropsContext,
useComponentContext,
usePropsContext: useAdditionalComponentPropsContext,
useComponentProps,
withContext,
component,
} = createComponent<AdditionalComponentProps, AdditionalComponentStyle>(
"additional-component",
additionalComponentStyle,
)
export { AdditionalComponentPropsContext, useAdditionalComponentPropsContext }
export const AdditionalComponent = withContext(Component)()
これで、Componentを継承したAdditionalComponentが作成されます。
従来のコンポーネントの継承との違いは、ref・クラス名・スタイル・イベントハンドラをマージできている点です。
export const AdditionalComponent: FC<AdditionalComponentProps> = ({
className,
...rest
}) => {
const ref = useRef<HTMLDivElement>(null)
const onClick = useCallback(() => {}, [])
return (
<Component
ref={ref}
onClick={onClick}
className={[className, "additional-component"].join(" ")}
{...rest}
/>
)
}
この場合は、提供されたpropsにrefとonClickが存在する場合、上書きされてしまいます。ロジックによっては、上手く機能しない場合もあります。これらを解決するために、コンポーネントごとに各値やロジックをマージする必要があります。
export const AdditionalComponent: FC<AdditionalComponentProps> = ({
ref: forwardedRef,
className,
onClick: onClickProp,
...rest
}) => {
const ref = useRef<HTMLDivElement>(null)
const onClick = useCallback(() => {}, [])
return (
<Component
ref={mergeRefs(forwardedRef, ref)}
onClick={handlerAll(onClickProp, onClick)}
className={[className, "additional-component"].join(" ")}
{...rest}
/>
)
}
createComponentを使用してコンポーネントを継承することで、refやonClickなどのイベントハンドラを自動的にマージすることができます。
variantsが存在する場合は、継承したコンポーネントのvariantsが優先されます。createSlotComponent
スロット付きコンポーネントを作成する場合は、createSlotComponentを使用します。機能は、createComponentと同様です。
import type { HTMLStyledProps, ThemeProps } from "@yamada-ui/react"
import { createSlotComponent, defineComponentSlotStyle } from "@yamada-ui/react"
import type { HTMLStyledProps, ThemeProps } from "@/components/ui"
import { createSlotComponent, defineComponentSlotStyle } from "@/components/ui"
import type { HTMLStyledProps, ThemeProps } from "@workspaces/ui"
import { createSlotComponent, defineComponentSlotStyle } from "@workspaces/ui"
const componentStyle = defineComponentSlotStyle({
base: {
root: {
/* base root style */
},
item: {
/* base item style */
},
},
variants: {
/* variant style */
},
sizes: {
/* size style */
},
props: {
/* props style */
},
compounds: {
/* compound style */
},
defaultProps: {
/* default props */
},
})
type ComponentStyle = typeof componentStyle
export interface ComponentRootProps
extends HTMLStyledProps<"div">,
ThemeProps<ComponentStyle> {}
const {
ComponentContext,
PropsContext: ComponentPropsContext,
StyleContext,
useComponentContext,
usePropsContext: useComponentPropsContext,
useStyleContext,
useClassNames,
useRootComponentProps,
useSlotComponentProps,
withProvider,
withContext,
component,
} = createSlotComponent<ComponentRootProps, ComponentStyle>(
"component",
componentStyle,
)
export { ComponentPropsContext, useComponentPropsContext }
defineComponentSlotStyleは、コンポーネントのスタイルを定義する関数です。この関数は、型補完をする重要な役割があります。- 第一引数は、
classNameやdisplayNameに使用されるコンポーネント名のプレフィックスを設定します。 - 第二引数は、コンポーネントのスタイルを設定します。
コンポーネントを作成する
コンポーネントを作成するには、withProviderとwithContextを使用します。それぞれ、第一引数にHTML要素名または関数を設定し、第二引数にスロット名を設定します。withProviderは、提供されたスタイルとPropsContextから提供されるpropsを使用して、コンテキストを生成します。withContextは、withProviderで生成されたコンテキストを使用して、スロット名に基づいたスタイルを使用します。
export const RootComponent = withProvider("div", "root")()
export const ItemComponent = withContext("div", "item")()
export const RootComponent = withProvider((props) => {
return <styled.div {...props} />
}, "root")()
export const ItemComponent = withContext((props) => {
return <styled.div {...props} />
}, "item")()
もし、提供されたスタイルとPropsContextから提供されるpropsを使用しない、またはそのロジックをハンドリングしたい場合は、componentを使用します。
export const RootComponent = component((props) => {
const [context, computedProps] = useRootComponentProps(props, "root")
return (
<StyleContext value={context}>
<styled.div {...computedProps} />
</StyleContext>
)
}, "root")()
export const ItemComponent = component((props) => {
const computedProps = useSlotComponentProps(props, "item")
return <styled.div {...computedProps} />
}, "item")()
修飾子を使用する
修飾子を使用する場合は、スロット名を配列にします。
const componentStyle = defineComponentSlotStyle({
base: {
root: {
/* base root style */
},
item: {
/* base item style */
},
start: {
/* base start style */
},
end: {
/* base end style */
},
},
})
export const StartItemComponent = withContext("div", ["item", "start"])()
export const EndItemComponent = withContext("div", ["item", "end"])()
この場合は、itemとstartまたはendのスタイルが設定され、クラス名は"{prefix}-{name}__item--start"または"{prefix}-{name}__item--end"になります。