TypeScript + React + styled componentsで開発するのに行ったこと

## TypeScriptで値を書き換えられないようにする

TypeScriptの標準のReadonlyのジェネリック型だと、ネストしたデーター構造の場合にネストした階層に対しては書き換えられてしまう。

[utility-types](https://github.com/piotrwitek/utility-types)の[DeepReadonly](https://github.com/piotrwitek/utility-types#deepreadonlyt)を使うとネストした場合でも値を書き換えられないようにでできる。

### Readonly

```ts
type Foo = Readonly<{
  first: {
    second: {
      name: string
    }
  }
}>

const foo: Foo = {
  first: {
    second: {
      name: 'name'
    }
  }
}
```

### ネストしてない値を書き換えた場合

```ts
foo.first = { second: 'name2' } // TS2540: Cannot assign to 'first' because it is a read-only property.
```

## ネストした値を書き換えた場合

```ts
foo.first.second.name = 'name2' // 値が書き換えられる
```

### DeepReadonly

```ts
import { DeepReadonly } from 'utility-types'

type Foo = DeepReadonly<{
  first: {
    second: {
      name: string
    }
  }
}>

const foo: Foo = {
  first: {
    second: {
      name: 'name'
    }
  }
}
```

### ネストしてない値を書き換えた場合

```ts
foo.first = { second: 'name2' } // TS2540: Cannot assign to 'first' because it is a read-only property.
```

### ネストした値を書き換えた場合

```ts
foo.first.second.name = 'name2' // TS2540: Cannot assign to 'name' because it is a read-only property.
```

## styled-componentsのthemeに型付けて定義してない色を付けた場合は型エラーにする

```ts:color.ts
export type Colors = {
  black: string
  white: string
}

type Color = keyof Colors

export const color = (color: Color): ((props: { theme: Theme }) => string) => props => props.theme.colors[color]
```

```tsx
import React from 'react'
import styled, { ThemeProvider } from 'styled-components'
import { Colors, color } from './color'

type Theme = {
  colors: Colors
}

const theme: Theme = {
  colors: {
    black: '#000',
    white: '#fff'
  }
}

export const App: React.FC = () => (
  <ThemeProvider theme={theme}>
    <Main>main</Main>
  </ThemeProvider>
)

const Main = styled.div`
  color: ${color('black')};
`
```

### 定義されてない色を指定した場合

```tsx
const Main = styled.div`
  color: ${color('green')}; // TS2345: Argument of type '"green"' is not assignable to parameter of type '"black" | "white"'.
`
```

### 透明度の指定

themeの色のパターンに同じ色でも違う透明度のものを定義してしまうとパターンが増えてしまうので、[polished](https://github.com/styled-components/polished)の[rgba](https://polished.js.org/docs/#rgba
)という関数を使うと、themeの色に対して透明度の調整した色を指定できる。

```tsx
import React from 'react'
import { rgba } from 'polished'
import styled from 'styled-components'
import { color } from './color'

const Main = styled.div`
  color: ${props => rgba(color('black')(props), 0.3)};
`
```

### SVGをJSXとして扱う

jsxとして使う場合svgのタグのままでは使えなくて、 attributeに`:`や`-`が入ってる場合は変換する必要がある。

https://reactjs.org/docs/dom-elements.html#all-supported-html-attributes

#### 例

`stroke-width`  => `strokeWidth`

### JSXに変換する方法

* webpackのloaderを使う方法
  * [react-svg-loader](https://www.npmjs.com/package/react-svg-loader)

* svgを予め変換しておく方法
  * [svgr](https://github.com/smooth-code/svgr)

新しいsvgアイコンが増えない限り、毎回webpackのloaderで変換する必要がないためsvgrを使うことにした。
 
### svgrの設定例

```js:svgrrc.js
module.exports = {
  icon: true,
  prettierConfig: {
    semi: false,
    singleQuote: true,
    printWidth: 128
  },
  template(
    { template },
    opts,
    { imports, componentName, props, jsx, exports }
  ) {
    const typeScriptTpl = template.smart({ plugins: ['typescript'] })
    const name = `${componentName.name.replace('Svg', '')}: React.FC<SVGProps<SVGSVGElement>>`
    return typeScriptTpl.ast`
      import React, { SVGProps } from 'react';
      export const ${name} = props => ${jsx};
    `
  }
}
```

```json:package.json
{
  "scripts": {
    "svgr:build": "svgr ./svgs --ext tsx --out-dir ./components/ui/icons"
  }
}
```

### svgrで変換したコンポーネントの組込例

```tsx
import React from 'react'
import styled from 'styled-components'
import { Icon } from './components/ui/icons/Icon'

const App: React.FC = () => (
  <StyledIcon />
)

const StyledIcon = styled(Icon)`
  width: 16px;
  height: 16px;
  fill: blue;
`
```

#### 同じアイコンでサイズや色のバリエーションを変える例

```tsx
const StyledIcon = styled(Icon)`
  width: 32px;
  height: 32px;
  fill: red;
`
```