ReactでTypeScript使うときにPropsやStateってinterfaceよりType Aliasの方よいのでは説
※ この記事は2018年7月ごろに書いたものです
(意見は特に変わってませんが、情報やアンケートは過去のものです)
発端
まじか!
いや、まあMSのスターター見るとinterfaceだし、interfaceが主流なのはわかるんだけど
とはいえこんなに差がつくのか・・・
が、今のところ考えきってもinterfaceにする妥当性は強く感じてないという気持ち
同じこと思ってる人いた。
2.8以上を想定すると
同じな部分
エラーの出方は変わらなくなった
昔はType AliasだとObjectがそのまま展開されていた
拡張・継承もできる
Typeで宣言したものをinterfaceがextendsで指定できるようになっている
関数を表せる
違う部分
type aliasをUnionオペレータ使ったらimplements出来ない
type aliasをUnionオペレータ使ったらextends出来ない
type aliasは同名でマージされない
考えを深めてみる
話の前提
TSは2.8以上としとく。
古い場合は対応してない機能があるなどの事情があるので。
ライブラリなどではなく、あくまで自家製のReactでの話をする。
ライブラリの場合は「拡張に対して開いている」ほうが良いので、Interfaceのほうがいいと考えているため。
公式ではなんと言ってるか?
A second more important difference is that type aliases cannot be extended or implemented from (nor can they extend/implement other types). Because an ideal property of software is being open to extension, you should always use an interface over a type alias if possible.
「ソフトウェアは拡張に開いているべきなのでinterfaceを使うべき」という主張
焦点:果たしてこれはPropsに当てはまるだろうか?
type aliasが良いと僕が感じている理由
いたずらに拡張「させない」仕組み
ReactのProp、Stateはオブジェクトである
Classとして扱われることはなく、pure objectであるのが慣例。
Reactのコンポーネントはそもそも拡張するか?
-> すべきでないという感覚。
継承より移譲。コンポジションのほうが良い
仮に拡張がある場合でも、交差型のほうが良くない?
コンポーネントの親子関係
interfaceの場合
code:ts
///// interfaceの場合
/// Child1
interface Component1Props {
foo: string;
}
const Component1 = (props: Component1Props) => <div>{props.foo}</div>;
//// Child2
interface Component2Props {
baz: number;
}
const Component2 = (props: Component2Props) => <div>{props.baz}</div>;
//// Parent
// 親となるコンポーネントが子コンポーネントのPropsをextendsしている
// クラス的な構造とコンポジションの構造が逆転している印象
interface ParentProps extends Component1Props, Component2Props {}
const ParentComponent = (props: ParentProps) => (
<div>
<Component1 foo={props.foo} />
<Component2 baz={props.baz} />
</div>
);
Type aliasの場合
code:ts
///// type aliasの場合
/// Child1
type Component1Props ={
foo: string;
}
const Component1 = (props: Component1Props) => <div>{props.foo}</div>;
//// Child2
type Component2Props {
baz: number;
}
const Component2 = (props: Component2Props) => <div>{props.baz}</div>;
//// Parent
// それぞれの交差型として記述する。こっちのほうが自然じゃない?
type ParentProps = Component1Props & Component2Props
const ParentComponent = (props: ParentProps) => (
<div>
<Component1 foo={props.foo} />
<Component2 baz={props.baz} />
</div>
);
意味合いが違うものをPropsを分けたい場合があったとする(あんま無いけど
type aliasだと
code:typescript
// Propsがでかすぎるから意味的に分解したいとする
type UserItemProps = {
id: number
name: string
}
type HandlerProps = {
onClick: Function
}
type StatusProps = {
isDisabled: boolean
}
// それぞれを交差型にする
type ComponentProps = UserItemProps & HandlerProps & StatusProps
interfaceだと
code:typescript
interface UserItemProps {
id: number;
name: string;
}
interface HandlerProps {
onClick: Function;
}
interface StatusProps {
isDisabled: boolean;
}
// extendではあるんだけど、なんとなく直感に反している感じがある(自分だけ?)
interface ComponentProps extends UserItemProps, HandlerProps, StatusProps {}
//// もしくはこういう風にも書けるがもはや分けてる意味が伝わらなさそう
interface ComponentProps {
id: number;
name: string;
}
interface ComponentProps {
onClick: Function;
}
interface ComponentProps {
isDisabled: boolean;
}
同名の宣言がある場合エラーになる
interfaceならマージされる。type aliasならエラーになる。
code:ts
// マージが起こる。PropsInterfaceはfooもbazも持つ(この場合は持ってしまう)
interface PropsInterface {
foo: string
}
interface PropsInterface {
baz: string
}
// エラー
type PropsType = {
foo: string
}
type PropsType = {
baz: string
}
うっかりComponentの宣言を挟んで上下にPropsを二回記述してしまった!とかを考えると、これはむしろエラーになってほしい気がする
Type aliasだけで使える機能
Mapped Types
良い事例が出てこないが多分styleとかで使えそうな気がしている。
code:ts
type Props<T> = {
}
Unionでの宣言が可能
(こんなケースあるかなーというのとあってもやるべきかなーはまあある・・・)
code:typescript
type UnionProps = {
type: "foo"
foo: string
} | {
type: "baz"
baz: string
}
const SomeComponent = (props: UnionProps) => {
if(props.type == "foo"){
return <div>foo: {props.foo} </div>
} else {
return <div>baz: {props.baz} </div>
}
}
宣言が短い(雑な理由)
type 4文字
拡張は&で1文字
interface 9文字
拡張 extendsで6文字
結局MappedTypesやらUnionやらintersectを使うとかで一定数Propsに対してType Aliasじゃないと宣言し辛い場合がある
であればtypeで統一ってのはナシではないのでは?
コード補完で見ても実態がわからない
「SomeProps」などになってて何が必須要素なのかなどわかりづらいケースがある
隣の芝
flow
typeで宣言するのが慣習っぽい。
flowとTSで挙動も同じではないはずなので参考にはならんかもしれないけど・・・
とはいえ、Reactの思想としてはtype aliasを使うというのはあるという話しには見える。
これflowから来た人はtypeで書きそう
Interfaceであるべき場合
ライブラリの場合
特に型付けだけ提供している場合
型が壊れている可能性や、ライブラリが更に他のライブラリに依存しているとかでPropsが壊れていて、パッチしなきゃいけない場合がある
その場合type aliasだと詰む。
なぜinterfaceが主流なのか?
Starter
各種ライブラリの型情報、DefiniteryTypedを参考にしてる
これは上記の通りライブラリは絶対interface
tslint
このルールがある。
参考文献
情報古いかも