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"になります。

2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd
2nd