React+TypeScriptで関数型SFCを書いてみる

この記事は[React.js その2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/react2)の5日目です。

React+TypeScriptでStatelessFunctionalComponentを作りたいとき、最適化の方法はいろいろあると思いますが、今回はなるべく関数型で処理の追いやすさを考えてみます。

現在ProposalのReact Hooksからもその傾向はありますが、関心事に沿って関数として分離するのが最近の良さげな書き方な気がします。

# STEP.1

```tsx
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

```tsx
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](https://github.com/typestyle/typestyle)を使ってみます。

```tsx
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の基本シンタックスに近い形なるかな？と期待してます。

```tsx
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>
  );
};
```

ぬるりと終わります。お疲れ様でした。