3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【型は命、anyは敵】 React + TypeScript のコーディング規約を一部晒す

Last updated at Posted at 2021-12-09

この記事は、Wano Group Advent Calendar 2021 の記事です。

型は命、anyは敵?

「型は命、anyは敵」 とタイトルに書きましたが、anyが必要な場面ももちろんあると思います。避けるべきは不用意なanyによる型の握りつぶしです。
この前提をもとに可能な限り正しく型付けを行うことが、最大のコーディング規約です。

Reactファイルのテンプレート

半分結論に近いですが、先にReactのファイルのテンプレートを晒します。
何かしらのComponentを作成するとき、とりあえずこれを書いています。

SampleCompnent.tsx
import React from 'react';

interface Props {
  className?: string;
}

const SampleComponent: React.VFC<Props> = ({ className }) => {
  return (
    <div className={className}>
      SampleComponent 
    </div>
  )
}

export default SampleComponent;

コーディング規約

原則としてComponentは1ファイル1つにし、ファイル名とComponent名を一致させる

コードの見通しを良くするためです。
(まれに例外として、小さなComponentをファイル内に定義することはあります。)

SampleComponent.tsx
const SampleComponent: React.VFC<Props> = () => {} ;

Component の export には default export を使い、 Named Exportしない

const SampleComponent: React.VFC<Props> = () => {} ;
export default SampleComponent;

tsxファイルでは、import { useEffect } のような Named import はしない

import React するので、基本的に個別でのNamed Importはしないように揃えています。

// Good
import React from "react";

  React.useEffect(()=>{ /* ... */ },[]);

// Bad
import React, { useEffect } from "react";

  useEffect(()=>{ /* ... */ },[]);

styled-componentsは使わない

material-ui を採用しており、基本的に makeStyles を使っています。
頻度は低いですが、共通のClassを使いたい場合に共通のhookとして複数Componentから使い回すことも可能なのが利点です。

const useStyles = makeStyles(theme => {
  root: {
    marginTop: theme.spacing(2),
  },
});

Component の props の型は明示的に宣言する

可読性向上が目的です。

// Good
interface Props {
  className?: string;
};
const Component: React.VFC<Props> = (props) => {};

// Bad
const Component = (props: { className?: string }) => {};

型の定義(特にProps)には type ではなく interface を使う

多くの場合、Propsはシンプルな型なので interfaceで定義可能だと思っています。
ジェネリクスや型推論が必要であったり、よほど複雑な型の場合はtypeを使うことは許容していますが、よりシンプルな型にする意識を保つことで可読性も保ちやすいと考えています。

// Good
interface Props {
  title: string,
  className?: string,
};

// Bad
type Props = {
  title: string
  className?: string,
};

// OK - ただし、他のComponentに依存してよいかなどはレビューで議論する
type Props = {
  title: string,
  className?: string,
} & Partial<React.ComponentProps<OtherComponent>>;

Component の型定義には FC ではなく VFC を使う

FC を使うと、 children が optional として props に入ってしまいます。
VFC を使い、明示的に children の有無を指定することで、コーディングミスを減らすことが目的です。
同様の理由から、PropsWithChildren を使うことも避けています。

// Good
interface Props {
  className?: string;
  children: React.ReactNode;
};

const Component: React.VFC<Props> = ({ children, className }) => {};

// Bad
interface Props {
  className?: string;
};

const Component: React.FC<Props> = ({ children, className }) => {};

props は destruct で受ける

useEffect などのhookの第二引数depsにpropsそのものを指定しないように。
deps で毎回 props. とアクセスしないで良いように。

// Good
const Component: React.VFC<Props> = ({ className }) => {};

// Bad
const Component: React.VFC<Props> = (props) => {};

あとがき

ここに書いたものはあくまでも個人的なコーディング規約であり、パフォーマンスや可読性を万人に保証するものではありません。
ただし、できるだけベストプラクティスに近い形になるように心がけています。

初歩的なものばかりになってしまったので、いつか型に関するコーディング規約とTipsに関する第二弾を書きたいと思います。
↓こういう感じのとか。

interface PropsA {
  type: "typeA",
  content?: never;
};

interface PropsB {
  type: "typeB",
  content: React.ReactNode;
};

type Props = PropsA | PropsB;

TypeScriptの型システムを最大限活かすことで、Reactの安全に書くことができると思います。
もしも各項目でより良い型付けがあれば是非教えて下さい。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?