🦋

【React Native】Tailwind Variants みたいなユーティリティを実装してみる

2025/02/20に公開

普段はウェブの開発をするのですが、ネイティブアプリを作りたくなったのでReact Nativeを触り始めました。ウェブの開発では、コンポーネントのバリアントの定義にtailwind variantsをよく使うのですが、React Native でも同じ感じの実装がしたいなと思ったので実装してみます。(実装雑です。。)

実装

import { StyleProp, TextStyle, ViewStyle } from 'react-native'

type StyleVariant<T> = {
  [key: string]: {
    [Key: string]: StyleProp<T>
  }
}

type Variant<T> = {
  [Key in keyof T]: keyof T[Key]
}

// 作成したバリアントからコンポーネントのPropsを抽出する型
export type VariantProps<T extends (...args: any) => any> = Partial<
  Parameters<T>['0']
>

// バリアント定義用の関数
export const createVariant = <T>() => {
  return <U extends StyleVariant<T>>(variant: U) => {
    return (props: Partial<Variant<U>>) => {
      const entries = Object.entries(props)
      return entries
        .map(([key, value]) => variant[key][value])
        .reduce((acc, curr) => {
          return { ...(acc as any), ...(curr as any) }
        }, {} as StyleProp<T>)
    }
  }
}

// Viewのスタイリング
export const viewVariant = createVariant<ViewStyle>()

// Textのスタイリング
export const textVariant = createVariant<TextStyle>()

WebはCSSのプロパティがタグによらないので、1つの定義で良いのですが、 React Native はコンポーネントによって適用できるスタイルが違うので分けてあげる必要があります。
tailwind variantsみたいにまとめたかったのですが、うまくできなかったので分ける形で実装しています。
any は「ユーティリティなら使ってもいいかなあ」と思って使ってます。

使ってみる

以下のように使えます。
今回は例としてFlexboxのコンポーネントを定義してみます。
directionなどのプロパティは、createVariantのジェネリクスに沿ってエディタの推論が効きます。

バリアント定義
import { viewVariant } from '../../../utils/variant'

export const FLEX_VARIANTS = viewVariant({
  direction: {
    row: {
      flexDirection: 'row'
    },
    column: {
      flexDirection: 'column'
    }
  },
  justify: {
    center: {
      justifyContent: 'center'
    },
    start: {
      justifyContent: 'flex-start'
    },
    end: {
      justifyContent: 'flex-end'
    }
  },
  align: {
    center: {
      alignItems: 'center'
    },
    start: {
      alignItems: 'flex-start'
    },
    end: {
      alignItems: 'flex-end'
    }
  },
  gap: {
    1: {
      gap: 4
    },
    2: {
      gap: 8
    },
    3: {
      gap: 12
    },
    4: {
      gap: 16
    }
  },
  wrap: {
    wrap: {
      flexWrap: 'wrap'
    },
    nowrap: {
      flexWrap: 'nowrap'
    }
  }
})
Props
import { ViewProps } from 'react-native'
import { VariantProps } from '@/utils/variant' // 任意のパス
import { FLEX_VARIANTS } from './const'

export type FlexProps = ViewProps & VariantProps<typeof FLEX_VARIANTS>
コンポーネント
import { View } from 'react-native'
import { FlexProps } from './type'
import { FLEX_VARIANTS } from './const'

export const Flex = ({
  children,
  style,
  direction,
  justify,
  align,
  gap,
  wrap,
  ...props
}: FlexProps) => {
  return (
    <View
      {...props}
      style={[
        style,
        {display: "flex"},
        FLEX_VARIANTS({ direction, justify, align, gap, wrap })
      ]}
    >
      {children}
    </View>
  )
}

おわり

雑ですが、tailwind variants に似た感じでスタイルを定義できるようになりました。
React Native初心者なので、検討できてない点があればコメントいただけると嬉しいです🙏

Discussion