Material ui の Modal のサンプルを TypeScript にコピペしたら JSX でインラインスタイルの指定に CSS のオブジェクトを渡している箇所で形エラーになってしまった
const style = { // インラインスタイルに使う CSS }; const MyComponent = () => { return <div style={style} /> // => style={style} の箇所が型エラーになる }
環境
typescript@4.1.6
react@17.0.2
@mui/material@5.2.7
結論
- 通常の JSX にCSS のオブジェクトを渡す際は
React.CSSProperties
の型をつける - Material UI の
sx
に渡す CSS のオブジェクトはas const
するかSxProps<Theme>
の型をつける
とするのが良さそう。
通常の React の JSX
const style: React.CSSProperties = { // インラインスタイルに使う CSS }; const MyComponent = () => { return <div style={style} /> }
Material UI
Materia UI 公式のドキュメントに載っているのは as const
を使う方法
import Box from '@mui/material/Box'; const style = { // インラインスタイルに使う CSS } as const; const MyComponent = () => { return <Box sx={style} /> }
又は
import Box from '@mui/material/Box'; import { Theme } from '@mui/material/styles'; import { SxProps } from '@mui/system'; const style: SxProps<Theme> = { // インラインスタイルに使う CSS }; const MyComponent = () => { return <Box sx={style} /> }
cf.
- TypeScript usage | The sx prop - MUI
- `SxProps` Type for `sx` property. · Issue #27564 · mui-org/material-ui · GitHub
- TypeScript error spreading SxProps in Box without @mui/material · Issue #29900 · mui-org/material-ui · GitHub
- The `sx` Prop – Theme UI
経緯
tsx にインラインスタイルに CSS のオブジェクトを渡した際に型エラーになる
cf. React Modal component - MUI
// Modal.tsx const style = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 400, bgcolor: 'background.paper', border: '2px solid #000', boxShadow: 24, p: 4, }; export const MyModal = ():JSX.Element => { const [open, setOpen] = React.useState(false); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); return ( <div> <Button onClick={handleOpen}>Open modal</Button> <Modal open={open} onClose={handleClose} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" > <Box sx={style}> // 略 </Box> </Modal> </div> ); }
<Box sx={style}>
の箇所が下記のような型エラーになる
(JSX attribute) sx?: SxProps<Theme> | undefined No overload matches this call. Overload 1 of 2, '(props: { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; component?: ElementType<any> | undefined; ref?: Ref<...> | undefined; sx?: SxProps<...> | undefined; } & CommonProps & Omit<...>): Element', gave the following error. Type '{ position: string; top: string; left: string; transform: string; width: number; bgcolor: string; border: string; boxShadow: number; p: number; }' is not assignable to type 'SxProps<Theme> | undefined'. Type '{ position: string; top: string; left: string; transform: string; width: number; bgcolor: string; border: string; boxShadow: number; p: number; }' is not assignable to type 'CSSSelectorObject<Theme>'. Property 'position' is incompatible with index signature. Type 'string' is not assignable to type 'SystemStyleObject<Theme> | ((theme: Theme) => SystemStyleObject<Theme>)'. Overload 2 of 2, '(props: DefaultComponentProps<BoxTypeMap<{}, "div">>): Element', gave the following error. Type '{ position: string; top: string; left: string; transform: string; width: number; bgcolor: string; border: string; boxShadow: number; p: number; }' is not assignable to type 'SxProps<Theme> | undefined'.ts(2769) Box.d.ts(11, 7): The expected type comes from property 'sx' which is declared here on type 'IntrinsicAttributes & { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; component?: ElementType<...> | undefined; ref?: Ref<...> | undefined; sx?: SxProps<...> | undefined; } & CommonProps & Omit<...>' Box.d.ts(11, 7): The expected type comes from property 'sx' which is declared here on type 'IntrinsicAttributes & SystemProps<Theme> & { children?: ReactNode; component?: ElementType<any> | undefined; ref?: Ref<...> | undefined; sx?: SxProps<...> | undefined; } & CommonProps & Omit<...>'
長げぇ… (ᐡ •̥ ̫ •̥ ᐡ)
Material UI を使わないただの div タグにスタイルのオブジェクトを渡しても同じような型エラーが発生する
<div style={style} />
👇
(JSX attribute) React.HTMLAttributes<T>.style?: React.CSSProperties | undefined Type '{ position: string; top: string; left: string; transform: string; width: number; bgcolor: string; border: string; boxShadow: number; p: number; }' is not assignable to type 'Properties<string | number, string & {}>'. Types of property 'boxShadow' are incompatible. Type 'number' is not assignable to type 'BoxShadow | undefined'.ts(2322) index.d.ts(1839, 9): The expected type comes from property 'style' which is declared here on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'
こちらの方が理由が分りやすく出ていました。要するに style 属性に渡してるオブジェクトが CSS のオブジェクトとは限らないのでエラーになっているようです。
1. JSX のインラインスタイル属性に直接スタイルを記述する
身も蓋もないけど、直接属性にスタイルを記述すると属性に合っているのかどうかで判定されるので型エラーにならない
// Modal.tsx - const style = { - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: 400, - bgcolor: 'background.paper', - border: '2px solid #000', - boxShadow: 24, - p: 4, - }; export const MyModal = ():JSX.Element => { // … return ( <div> <Button onClick={handleOpen}>Open modal</Button> <Modal> - <Box sx={style}> + <Box sx={{ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 400, + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, + }}> ); };
本当に見も蓋もないけどエラーにならないからオッケー ₍ ᐢ. ̫ .ᐢ ₎
2. CSS のオブジェクトに型定義をする
CSS の変数の型が無いので、マッチしない要素が渡される可能性があり型エラーになっているので、CSS を定義してあるオブジェクトに型を定義すれば OK
インラインスタイルで style 属性に変数でスタイルを渡す際は CSS の変数に型定義をする必要がある
<div style={style}>
のような通常のJSXのインラインスタイルの場合は React.CSSProperties
で型定義をすれば OK
- const style = { + const style: React.CSSProperties = { position: 'absolute', top: '50%', border: 0, }; export const MyComponent = (): JSX.Element => { return <div style={style} /> }
₍ ᐢ. ̫ .ᐢ ₎👌
Material UI は 独自のプロパティを CSS に変換するので React.CSSProperties
では型エラーになってしまう
Material UI のコンポーネントは style 属性ではなく、cx
という props に CSS を渡すようになっており、CSS のプロパティも {p: 4}
のような独自のプロパティを指定できるので、Reacr.CSSProperties
では型が合わずエラーになってしまう。
// Modal.tsx const style: React.CSSProperties = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 400, bgcolor: 'background.paper', // => type error border: '2px solid #000', boxShadow: 24, // => type error p: 4, // => type error };
Material UI 独特の定義方法である bgcolor: 'background.paper'
, boxShadow: 24
, p: 4
のような箇所が Reacr.CSSProperties の型定義に合わないとエラーになる。(それはそう)
2-1. as const
を使う
A frequent source of confusion with the
sx
prop is TypeScript's type widening, which causes this example not to work as expected:const style = { flexDirection: 'column', }; export default function App() { return <Button sx={style}>Example</Button>; } // Type '{ flexDirection: string; }' is not assignable to type 'SxProps<Theme> | undefined'. // Type '{ flexDirection: string; }' is not assignable to type 'CSSSelectorObject<Theme>'. // Property 'flexDirection' is incompatible with index signature. // Type 'string' is not assignable to type 'SystemStyleObject<Theme>'.The problem is that the type of theflexDirection
prop is inferred asstring
, which is too wide. To fix this, you can cast the object/function passed to thesx
prop to const:
cf. TypeScript usage | The sx prop - MUI
公式のドキュメントに書いてある方法。
// Modal.tsx const style = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 400, bgcolor: 'background.paper', border: '2px solid #000', boxShadow: 24, p: 4, - }; + } as const; export const MyModal = ():JSX.Element => { // … return ( <div> <Button onClick={handleOpen}>Open modal</Button> <Modal> <Box sx={style}>
₍ ᐢ. ̫ .ᐢ ₎👌
sx
に渡す style のオブジェクトに as const
付ければエラーにならないんだ… というお気持ち
2-2. SxProps を使う方法
sx
の型を見ていると (JSX attribute) sx?: SxProps<Theme> | undefined
と定義されていました。
SxProps
は @mui/system
から import できるので、インラインスタイルのオブジェクトにこの型を与えればうまく動作しました。
// Modal.tsx + import { Theme } from '@mui/material/styles'; + import { SxProps } from '@mui/system'; - const style = { + const style: SxProps<Theme> = { // ... }; export const MyModal = ():JSX.Element => { // … return ( <div> <Button onClick={handleOpen}>Open modal</Button> <Modal> <Box sx={style}>
₍ ᐢ. ̫ .ᐢ ₎👌
公式のドキュメントには無い方法なので、正しいのかどうか判断つかず…
cf.
- `SxProps` Type for `sx` property. · Issue #27564 · mui-org/material-ui · GitHub
- TypeScript error spreading SxProps in Box without @mui/material · Issue #29900 · mui-org/material-ui · GitHub
- The `sx` Prop – Theme UI
2-3. { [key: string]: string | number }
のオレオレ型を付けても通る
SxProps の型定義の中身を深堀りしてないので、なんとも言えませんが、CSS のプロパティと値の型にできそうな { [key: string]: string | number }
を使うと何故か通る。
// Modal.tsx - const style = { + const style: { [key: string]: string | number } = { // ... }; export const MyModal = ():JSX.Element => { // … return ( <div> <Button onClick={handleOpen}>Open modal</Button> <Modal> <Box sx={style}>
🤔 通るけれども、React.CSSProperties
や SxProps<Theme>
のようにプロパティとして扱えるのかまでは判定できないんど微妙だと思います。
cf. SxProps
と Theme
の型定義
export type SxProps<Theme extends object = {}> = | SystemStyleObject<Theme> | ((theme: Theme) => SystemStyleObject<Theme>) | Array<boolean | SystemStyleObject<Theme> | ((theme: Theme) => SystemStyleObject<Theme>)>; export interface Theme extends SystemTheme { mixins: Mixins; components?: Components; palette: Palette; shadows: Shadows; transitions: Transitions; typography: Typography; zIndex: ZIndex; unstable_strictMode?: boolean; }
所感
最初は単純に型がないなら { [key: string]: string | number }
したらええやろ〜で通ってしまったので、30分くらいでメモしておくか〜と思ってエビデンスの為に少し深堀りしたら React.CSSProperties
や Material UI の SxProps
を知ることになって結果 LOOOOOONG な記事になってしまった。だから結論を先に書いた。
結論としては (重複するけど) 下記で良いかなと思いました。
- 通常の JSX にわたす CSS のオブジェクトには
React.CSSProperties
の型を指定する - Material UI の
sx
に渡す CSS のオブジェクトはas const
してしまうか、型指定したい場合はSxProps<Theme>
を指定する
[参考]
- The sx prop - MUI
- `SxProps` Type for `sx` property. · Issue #27564 · mui-org/material-ui · GitHub
- TypeScript error spreading SxProps in Box without @mui/material · Issue #29900 · mui-org/material-ui · GitHub
- The `sx` Prop – Theme UI
- React コンポーネントに CSS スタイルを設定する|まくろぐ
- React with TypeScriptでインラインスタイルでCSS書くときに、objectにそんなプロパティないよって怒られた時の対処法 - Qiita