createComponent

createComponentは、バリアントなど条件付きスタイルをサポートするコンポーネントを作成します。また、createSlotComponentを使用すれば、簡単にスロット付きコンポーネントを作成できます。

概要

createComponentは、バリアントなど条件付きスタイルをサポートするコンポーネントを作成します。作成したコンポーネントは、継承することが可能で、拡張性が高く、classNamedisplayNameも生成するので、プロジェクトで一貫した命名規則を持つコンポーネントを作成することもできます。

使い方

単一のコンポーネントを作成する場合は、createComponentを使用し、スロット付きコンポーネントを作成する場合は、createSlotComponentを使用します。

createComponent

単一のコンポーネントを作成する場合は、createComponentを使用します。

import type { HTMLStyledProps, ThemeProps } from "@yamada-ui/react"
import { createComponent, defineComponentStyle } from "@yamada-ui/react"
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 }
  • 第一引数は、classNamedisplayNameに使用されるコンポーネント名を設定します。
  • 第二引数は、コンポーネントのスタイルを設定します。

コンポーネントを作成する

コンポーネントを作成するには、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を計算する

withContextcomponentは、提供された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にrefonClickが存在する場合、上書きされてしまいます。ロジックによっては、上手く機能しない場合もあります。これらを解決するために、コンポーネントごとに各値やロジックをマージする必要があります。

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を使用してコンポーネントを継承することで、refonClickなどのイベントハンドラを自動的にマージすることができます。

createSlotComponent

スロット付きコンポーネントを作成する場合は、createSlotComponentを使用します。機能は、createComponentと同様です。

import type { HTMLStyledProps, ThemeProps } from "@yamada-ui/react"
import { createSlotComponent, defineComponentSlotStyle } from "@yamada-ui/react"
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 }
  • 第一引数は、classNamedisplayNameに使用されるコンポーネント名のプレフィックスを設定します。
  • 第二引数は、コンポーネントのスタイルを設定します。

コンポーネントを作成する

コンポーネントを作成するには、withProviderwithContextを使用します。それぞれ、第一引数に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"])()

この場合は、itemstartまたはendのスタイルが設定され、クラス名は"{prefix}-{name}__item--start"または"{prefix}-{name}__item--end"になります。