この記事はReact.js その2 Advent Calendar 2018の5日目です。
React+TypeScriptでStatelessFunctionalComponentを作りたいとき、最適化の方法はいろいろあると思いますが、今回はなるべく関数型で処理の追いやすさを考えてみます。
現在ProposalのReact Hooksからもその傾向はありますが、関心事に沿って関数として分離するのが最近の良さげな書き方な気がします。
STEP.1
import * as React from 'react';
export const Button: React.SFC<{}> = props => (
<button>
{props.children}
</button>
);
まずはこの形から始めます。chlidrenをそのまま描画するだけのbuttonコンポーネントです。ここから実現したいことをいくつか挙げていきます。
- 外部からのPropsでbutton要素のattributeを変更する
- CSSをカプセル化して適用する
- Propsを元にして数パターン定義する
- Propsには型指定をする
- ソースコードを読みやすく保つ
STEP.2
import * as React from 'react';
interface IProps {
type?: string;
disabled?: boolean;
}
const getAttributes = (props: IProps) => ({
type: props.type || 'button',
disabled: props.disabled,
});
export const Button: React.SFC<IProps> = props => {
const attributes: React.ButtonHTMLAttributes<HTMLButtonElement> = getAttributes(props);
return (
<button {...attributes}>
{props.children}
</button>
)
};
Propsからattributeを生成するような関数を作りました。
受け取るPropsと実際にbuttonタグへ渡すことになるattribute値の差異をgetAttributes
関数内で吸収します。このときButtonコンポーネント自身は値の変化については無関心です。
Optionalなプロパティの場合は、ここで初期値をフォールバックしています。
どのHTMLタグに渡すattributeなのかはButtonコンポーネントが知っているので、変数に対して型をキャストしています。これでgetAttributes
関数から型安全に取得できてるはずです。
STEP.3
次にCSSを適用していきます。今回はstyled-components
のような予めstyleを指定してコンポーネントを生成するタイプのライブラリではなく、あとからstyleを注入するものを使いました。(どちらのタイプでも工夫次第ではあまり実装上問題ない気もします…)
ひとまずサクッとtypestyleを使ってみます。
import * as React from 'react';
import { style } from 'typestyle';
interface IProps {
type?: string;
disabled?: boolean;
size?: 'big' | 'medium' | 'small';
color?: 'red' | 'blue';
}
const styleSettings = {
fontSize: {
big: 24,
medium: 16,
small: 10,
},
color: {
red: '#d16666',
blue: '#6696d1',
},
};
const getClassName = (props: IProps) => style({
fontSize: props.size ? styleSettings.fontSize[props.size] : styleSettings.fontSize.medium,
color: props.color ? 'white' : 'black',
backgroundColor: props.color ? styleSettings.color[props.color] : undefined,
border: 0,
borderRadius: 5,
padding: '5px 20px',
});
const getAttributes = (props: IProps) => ({
type: props.type || 'button',
disabled: props.disabled,
});
export const Button: React.SFC<IProps> = props => {
const attributes: React.ButtonHTMLAttributes<HTMLButtonElement> = getAttributes(props);
const className: string = getClassName(props);
return (
<button className={className} {...attributes}>
{props.children}
</button>
);
};
size
プロパティを元にフォントサイズの変更と、color
プロパティを元に文字色・背景色を変えてみました。関数型に沿ってスタイルの定義もなるべく宣言的にしてみましたが、この辺は自由にやれる所かなと思います。純粋なCSSシンタックスで書きたいとかの要望もあると思いますが、それらは(基本的には)getClassName
関数より先で完結できています。
結局モチベーションは関数ベースに依存性を切り出したいという点なので、以下のように違和感なく分離できることが個人的に◎です。
そしてこれが2019年以降のReactの基本シンタックスに近い形なるかな?と期待してます。
import * as React from 'react';
import { getClassName, getAttributes } from './XXX';
interface IProps {
// ...
}
export const ComponentName: React.SFC<IProps> = props => {
const attributes = getAttributes(props);
const className = getClassName(props);
return (
<x-tag className={className} {...attributes}>
{props.children}
</x-tag>
);
};
ぬるりと終わります。お疲れ様でした。