少し前にMaterial-UIのv1.0を使ってみる記事を書いたのですが、その後GitHub issuesで意外とTypeScript対応についてきちんとしているのを見つけたので、ひとまずの雛形コードを紹介します。
コード全体はこちら https://github.com/gcoka/mui-ts-sample
前の記事
Material-UI v1.x (beta) を導入する
下準備
気軽に試してもらえるように動かすための手順です。
- create-react-appで環境作り
- Material-UIのインストール
create-react-appで環境作り
create-react-appのTypeScript用スクリプトを使います
$ create-react-app --version
1.4.1
# create-react-appコマンドをインストール
yarn global add create-react-app
# create-react-app-typescriptを使ってreactテンプレートを作成
create-react-app mui-ts-sample --scripts-version=react-scripts-ts
cd mui-ts-sample
# 動作確認
yarn start
Material-UIのインストール
動作確認した時のMaterial-UIのバージョンは1.0.0-beta.16
でした。
yarn add material-ui@next
yarn add material-ui-icons
サンプルコード
とりあえず完成形のコードを見てください。
App.tsx
を次の通り書き換えたら、yarn start
で起動します。
内容は前回のサンプルとほぼ同じですが、冗長なものは取り除いています。
また、withStylesが生成するHOC(High-Order Component)に対する型付けは、知らないとうまく行かないポイントなので、コンポーネントの切り出しもしています。
import * as React from 'react';
import Button from 'material-ui/Button';
import DeleteIcon from 'material-ui-icons/Delete';
import { withStyles, WithStyles } from 'material-ui/styles';
const styles = {
box: {
margin: 10,
padding: 10,
border: 'solid 1px gray',
},
button: {
margin: 10,
},
buttonWithHover: {
margin: 10,
// hoverも記述できる
'&:hover': {
backgroundColor: '#ff0000',
}
},
};
type ClassNames = keyof typeof styles;
interface FlatButtonsProps {
onClick: () => void;
}
// Stateless Function Componentsの場合
const FlatButtonsSample = withStyles(styles)<FlatButtonsProps>(
(props: FlatButtonsProps & WithStyles<ClassNames>) => {
const classes = props.classes;
return (
<div className={classes.box}>
{/* クリックイベントの処理はこんな感じ */}
<Button onClick={props.onClick} className={classes.button}>Default</Button>
<Button color="primary" className={classes.buttonWithHover}>Primary</Button>
<Button color="accent"><DeleteIcon />削除</Button>
</div>
);
}
);
interface RaisedButtonsProps {
onClick: () => void;
}
// Componentクラスの場合
class RaisedButtonsSample extends React.Component<RaisedButtonsProps & WithStyles<ClassNames>, {}> {
render() {
const classes = this.props.classes;
return (
<div className={classes.box}>
<Button raised={true} onClick={this.props.onClick} className={classes.buttonWithHover}>Default</Button>
<Button raised={true} color="primary" className={classes.button}>Primary</Button>
<Button raised={true} color="accent" className={classes.button}><DeleteIcon />削除</Button>
</div>
);
}
}
// 1ファイル内で書くためにHOCを変数に入れておく
const HocRaisedButtonsSample = withStyles(styles)<RaisedButtonsProps>(RaisedButtonsSample);
// 通常は別ファイルにするので、その場合は以下のようにexportする
// export default withStyles(styles)<RaisedButtonsProps>(RaisedButtonsSample);
// withStylesに型注釈が必要な場合は、以下のようにする
// export default withStyles<{} & ClassNames>(styles)<RaisedButtonsProps>(RaisedButtonsSample);
// そして別ファイルでimport
// import RaisedButtonsSample from './components/RaisedButtonsSample';
class App extends React.Component {
handleClick = () => {
alert('Clicked!');
}
render() {
return (
<div>
<FlatButtonsSample onClick={this.handleClick} />
<HocRaisedButtonsSample onClick={this.handleClick} />
</div>
);
}
}
export default App;
サンプルコード説明
TypeScriptはとにかく型付けがめんどくさい&何をつけたら良いのかわからない。というのがあると思います。
Material-UI v1.0ではそれを補助するために、WithStyles
という補助メソッドが用意されています。
コードのこの部分でインポートしています。
import { withStyles, WithStyles } from 'material-ui/styles';
小文字のwithStyles
はprops.classesを注入するためのHOC生成メソッドですが、
大文字のWithStyles
はprops.classesの型情報を付与するための補助メソッドです。
このあたりは、うまくやるためのトリックとして取り入れることになるかと思います。
また、styles
にJSS用のCSS定義をしているのですが、これの型定義をわざわざ書くと考えるとかなりげんなりしますね。
しかし、TypeScriptの記法でkeyof
をうまく使えば、ここも非常に楽をすることができます。
type ClassNames = keyof typeof styles;
このClassNames
の型情報を覗いてみると、次のようになっていて、props.classes
の利用時にこの型情報がきちんと反映されます。
type ClassNames = "box" | "button" | "buttonWithHover"
この辺りは、TypeScriptの型推論機能強化の恩恵ですね。
あと、注意するべきなのは、withStyles
によって生成されたHOCについても型注釈をつけてやらないと、利用側が型情報を参照できなくなることに注意しなければなりません。
例えば、一般形として、サンプルコード内のRaisedButtonsSampleを別ファイルに記述すると次のようになります。
import * as React from 'react';
import Button from 'material-ui/Button';
import DeleteIcon from 'material-ui-icons/Delete';
import { withStyles, WithStyles } from 'material-ui/styles';
const styles = { /* ...省略... */};
type ClassNames = keyof typeof styles;
interface RaisedButtonsProps {
onClick: () => void;
}
class RaisedButtonsSample extends React.Component<RaisedButtonsProps & WithStyles<ClassNames>, {}> {
render() {
const classes = this.props.classes;
return (
<div className={classes.box}>
{ /* ...省略... */}
</div>
);
}
}
export default withStyles<{} & ClassNames>(styles)<RaisedButtonsProps>(RaisedButtonsSample);
import RaisedButtonsSample from './components/RaisedButtonsSample';
withStyles<{} & ClassNames>
の部分については、調査不足ですが、こういう形で書かないと型チェックが通らないケースがあります。
総括
TypeScriptで書いているにもかかわらず、Material-UIを使う分においては、型注釈を書く労力は非常に少ないのではないかと思います。
TypeScriptでReactしている人にとっては、v1.0のリリースが非常に楽しみですね!
追記 2017/10/17
公式にTypeScriptのサンプルが掲載されましたね!
https://material-ui-next.com/getting-started/examples/