TypeScriptで値を書き換えられないようにする
TypeScriptの標準のReadonlyのジェネリック型だと、ネストしたデーター構造の場合にネストした階層に対しては書き換えられてしまう。
utility-typesのDeepReadonlyを使うとネストした場合でも値を書き換えられないようにでできる。
Readonly
type Foo = Readonly<{
first: {
second: {
name: string
}
}
}>
const foo: Foo = {
first: {
second: {
name: 'name'
}
}
}
ネストしてない値を書き換えた場合
foo.first = { second: 'name2' } // TS2540: Cannot assign to 'first' because it is a read-only property.
ネストした値を書き換えた場合
foo.first.second.name = 'name2' // 値が書き換えられる
DeepReadonly
import { DeepReadonly } from 'utility-types'
type Foo = DeepReadonly<{
first: {
second: {
name: string
}
}
}>
const foo: Foo = {
first: {
second: {
name: 'name'
}
}
}
ネストしてない値を書き換えた場合
foo.first = { second: 'name2' } // TS2540: Cannot assign to 'first' because it is a read-only property.
ネストした値を書き換えた場合
foo.first.second.name = 'name2' // TS2540: Cannot assign to 'name' because it is a read-only property.
styled-componentsのthemeに型付けて定義してない色を付けた場合は型エラーにする
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]
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')};
`
定義されてない色を指定した場合
const Main = styled.div`
color: ${color('green')}; // TS2345: Argument of type '"green"' is not assignable to parameter of type '"black" | "white"'.
`
透明度の指定
themeの色のパターンに同じ色でも違う透明度のものを定義してしまうとパターンが増えてしまうので、polishedのrgbaという関数を使うと、themeの色に対して透明度の調整した色を指定できる。
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に:
や-
が入ってる場合は変換する必要がある。
例
stroke-width
=> strokeWidth
JSXに変換する方法
-
webpackのloaderを使う方法
-
svgを予め変換しておく方法
新しいsvgアイコンが増えない限り、毎回webpackのloaderで変換する必要がないためsvgrを使うことにした。
svgrの設定例
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};
`
}
}
package.json
{
"scripts": {
"svgr:build": "svgr ./svgs --ext tsx --out-dir ./components/ui/icons"
}
}
svgrで変換したコンポーネントの組込例
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;
`
同じアイコンでサイズや色のバリエーションを変える例
const StyledIcon = styled(Icon)`
width: 32px;
height: 32px;
fill: red;
`