Chakra UIのCustom Themeで型補完を効かせる
はじめまして。去年の8月からスペースマーケットのフロントエンドチームにJoinしましたsei63です。
アイコンから分かるように星のカービィが好きなもので、星のカービィ ディスカバリーの発売をwktkで楽しみにしています。
コピー能力が進化するなんて。。。さらに育成要素も入れてくるとは思いませんでした。
styled-componentsの課題
スペースマーケットでは長らく styled-components を使ってきましたが、タグ一つ一つに対してスタイルを当てるようなインターフェースの為以下のような課題感がありました。
1.個別にスタイルを当てる都合上、命名が無理やりなものになっていく
const StyledListHeading = styled.div`
// リスト要素の見出しのテキストのスタイル
`;
const StyledListHeadingEmphasis = styled.div`
// リスト要素の見出しの強調させるテキストのスタイル
`;
そのコンポーネントが行なっていることを文章にしようとしてどんどん命名が長くややこしくなっていきますね。
適切に命名することは悪くないのですが、命名が長いコンポーネントが増えると render の記述が長くなり、可読性が落ちます。
2.構造上セットで使わなければならないものも、別々に定義する必要がある
const StyledContainerDiv = styled.div`
display: flex;
`;
const StyledItemDiv = styled.div`
flex: 1 1 auto; // 親が display: flex でないと意味をなさない。
`;
この場合、使用する側でこの親子関係を考慮して StyledContainerDiv > StyledItemDiv をという構造を組む必要が出てきます。ファイル内のコンポーネントがこれだけであれば良いのですが、そんなことはないのでこういった依存関係を強いられるコンポーネントは減らしたいですね。
3.Themeのデザイントークンを使うのが手間
これは styled-components の問題ではなくスペースマーケットの Theme 管理に問題があったという話なのですが、開発効率を下げていたので紹介させてください。
import { uiMain } from 'frontend_component/dist/color';
import { spacing } from 'frontend_component/dist/styles';
const StyledLink = styled.a`
color: ${uiMain}; // #23729e
margin-top: ${spacing(16)}; // 16px 定義されていない値を入れるとconsole.error
`;
色 や margin などのデザイントークンを使う際に毎回 import しています。
たった5行のコードですが、
どんな色の選択肢があるかがわかりづらい。
spacing が何に対して使えるか不明瞭 margin? width?
そもそも import が面倒
という問題が詰まっています。
これを毎回書いていくとなると地味にコストかかってきます。
Chakra-UIの採用
styled-components の抱えていた課題を解決するためにユーティリティーファーストなアプローチが取れる Tailwind css か Chakra-UI が候補上がりました。
パフォーマンスを重視した場合 Tailwind css となるのですが、スペースマーケットでは Chakra-UIを採用しています。
1.学習コストの低さ
Chakra-UI は CSS を書いた人であれば、CheetSheet などを用意しなくともすぐに書くことができます。
import { chakra } from '@chakra-ui/react';
const Container: VFC<{ children: ReactNode }> = ({ children }) => (
<chakra.div display="grid" placeItems="center">
<chakra.div padding={{base: "8px", md: "16px"}} backgroundColor="#fff">{children}</chakra.div>
</chakra.div>
);
Props で使われる key は CSS のプロパティそのままですし、Typescript を使っていれば Suggest が出るので迷うことはまずないでしょう。
また padding={{base: "8px", md: "16px"}} といった具合にモバイルファーストの形式でレスポンシブのスタイルが簡単に当てられるのも良いです。
2.アクセシビリティとの親和性
Chakra-UIには _checked や _disabled などのPseudoが用意されています。これが非常に便利です
const Button: VFC<{ children: ReactNode }> = ({ children }) => (
<chakra.button p="16px" color="#000" _disabled={{color: "#ddd"}}>
{children}
</chakra.button>
)
disbaled やaria-checked などのマシンリーダブルな状態に応じてスタイルを変更をすることができるので、この仕組みに乗っ取ってスタイルを当てると自然とアクセシビリティを意識して書くようになります。
3.TypeScriptとの親和性
const Section:VFC<HTMLChakraProps<'div'>> = (...chakraProps) => (
<chakra.div {...chakraProps}>
<chakra.h2>見出し</chakra.h2>
<Contents mt="8px">内容</Contents> // コンポーネントにmarginを定義せず、使う側で指定する
</chakra.div>
)
const Contents: VFC<HTMLChakraProps<'div'>> = ({ children, ...chakraProps }) => (
<chakra.div d="grid" placeItems="center" {...chakraProps}>
<chakra.div p="16px" bgColor="#fff">
{children}
</chakra.div>
</chakra.div>
)
TypeScript で作られいるため、Props を好きなように定義できます。
上記の例では ChakraProps をそのまま chakra の要素に渡せるようにして、Contents コンポーネントを使う際にマージンを指定してます。
この書き方ですと ChakraProps 全て渡せてしまいますので、mt だけ渡せるように Pick などを使うのもありです。
(同じファイルで消費されるコンポーネントの場合は面倒なのでそこまでしていなかったりします。)
Chakra-UI の書き味の良さに関しては、書き始めると止まらないので今回はここまでで。この記事のタイトルである『Custom Themeで型補完を効かせる』を見ていきましょう。
Custom Themeで型補完を効かせる
Chakra-UI には他の CSS ライブラリと同様に Theme を管理する機構があります。以下は Theme に使用している color.ts と Theme 自体を定義している theme.ts です
// color
export const colors = {
ui: {
main: '#23729E',
active: '#1B6086',
activeLine: 'rgba(234, 234, 234, 0.2)',
light: '#E8F6FB',
disabled: {
main: '#999',
light: '#EAEAEA',
},
accent: {
main: '#F6C000',
light: '#FFE056',
},
},
mono: {
black: '#222222',
gray7: '#717171',
gray9: '#999',
grayB: '#B3B3B3',
grayD: '#D1D1D1',
grayE: '#EAEAEA',
grayF: '#F3F3F3',
white: '#FFFFFF',
},
// 省略
} as const
import { theme as defaultTheme } from '@chakra-ui/react'
import { radii } from './borderRadius'
import { breakpoints } from './breakpoints'
import { colors } from './colors'
import { shadows } from './shadows'
import { space } from './space'
import { styles } from './styles'
import { typography } from './typography'
import { zIndices } from './zIndices'
export const theme = {
...defaultTheme,
breakpoints,
colors,
radii,
shadows,
sizes: {}, // sizeはdesign-tokenがないため、defaultThemeのsizesを上書きする
space,
styles,
...typography,
zIndices,
} as const
このように Theme を作ったら ChakraProvider に渡して Theme を適用することができます。
<ChakraProvider theme={theme} resetCSS={false}>
{children}
</ChakraProvider>
defaultTheme を使用しているのは定義漏れの theme があるとChakraProvider でエラーが起きてしまうので、拡張という形をとっています。
この辺りはおいおい defaultTheme を使わない形にし、不要なものを削っていく予定です。
では自作の Theme が用意できたところで、型定義補完が効くようにしてみましょう。やることはとても簡単です。
以下のドキュメントの Theme Typings の項目の内容を実施するだけです。
コマンドラインツールを使うため @chakra-ui/cli を追加します
npm install -D @chakra-ui/cli
そして以下のコマンドを実行して Theme から型定義を生成します。
chakra-cli tokens <path/to/your/theme.(js|ts)>
上記のコマンドを叩くと、
node_modules/@chakra-ui/styled-system/dist/declarations/src/theming.types.d.ts
にThemeからbuildされた型定義が書き込まれ補完が効くようになります
また --out オプションを指定すると好きな場所に出力することができるので、生成した型定義の確認をさっと行いたい場合に便利です。
chakra-cli tokens <path/to/your/theme.(js|ts)> --out hoge.ts
そして毎回 chakra-cli のコマンド叩くのは不便なので以下のようにpostinstall にコマンドを登録しておくと、更新漏れがないでしょう。
"scripts": {
"gen:theme-typings": "chakra-cli tokens <path/to/your/theme.(js|ts)>",
"postinstall": "npm run gen:theme-typings"
},
chakra-cliは頻繁にupdateが入っているので、次にどんな機能が入るかが楽しみですね。
この記事書いていた期間の間に Semantic Tokenの対応が入って 1.9.0 にversionが上がってました笑
最後に
まだまだ新メンバー募集しています!
自分で考えた方法で能動的に仕事に取り組み、経験を重ねられる良い会社ですので興味ある方は一緒に開発しましょう🙌