JavaScript
JSX
reactjs
React

[翻訳] Airbnb React/JSX Style Guide

この翻訳について

Airbnb React/JSX Style Guideの和訳です。
間違っていたり分かりにくい箇所があれば、ご指摘いただけると幸いです。

Airbnb React/JSX スタイルガイド

目次

  1. 基本的なルール
  2. クラス vs React.createClass vs ステートレス
  3. ミックスイン
  4. 命名規則
  5. 宣言
  6. アラインメント
  7. 引用符
  8. 空白
  9. 引数
  10. 参照
  11. 括弧
  12. タグ
  13. メソッド
  14. 順序
  15. isMounted

基本的なルール

  • Reactコンポーネントは1ファイルに1つだけにしてください。
  • 常にJSXの構文を使用してください。
  • JSXでないファイルでアプリを初期化している場合を除き、React.createElementは使用しないでください。

クラス vs React.createClass vs ステートレス

// bad
const Listing = React.createClass({
  // ...
  render() {
    return <div>{this.state.hello}</div>;
  }
});

// good
class Listing extends React.Component {
  // ...
  render() {
    return <div>{this.state.hello}</div>;
  }
}

また、stateやrefsを使わない場合、クラスよりも通常の関数(アロー関数ではない) が好まれます。

// bad
class Listing extends React.Component {
  render() {
    return <div>{this.props.hello}</div>;
  }
}

// bad - 関数名の推測が必要となる
const Listing = ({ hello }) => (
  <div>{hello}</div>
);

// good
function Listing({ hello }) {
  return <div>{hello}</div>;
}

ミックスイン

ミックスインは暗黙の依存関係により、名前の衝突を引き起こし、雪だるま式に複雑になります。ミックスインの使用事例のほとんどは、コンポーネント、高次コンポーネント、またはユーティリティモジュールを介して、より良い方法で実装できます。

命名規則

  • 拡張子: .jsxを使用してください。
  • ファイル名: パスカルケースを使用してください。(例: ReservationCard.jsx)
  • 参照名: Reactコンポーネントにはパスカルケースを使用し、それらのインスタンスにはキャメルケースを使用してください。(eslint: react/jsx-pascal-case)
// bad
import reservationCard from './ReservationCard';

// good
import ReservationCard from './ReservationCard';

// bad
const ReservationItem = <ReservationCard />;

// good
const reservationItem = <ReservationCard />;
  • コンポーネントの命名規則: コンポーネント名としてファイル名を使ってください。例えば、ReservationCard.jsxは、ReservationCardという参照名を持つ必要があります。ただし、ディレクトリのルートコンポーネントの場合は、ファイル名としてindex.jsxを使用し、コンポーネント名としてディレクトリ名を使ってください。
// bad
import Footer from './Footer/Footer';

// bad
import Footer from './Footer/index';

// good
import Footer from './Footer';
  • 高次コンポーネントの命名規則: 高次コンポーネントの名前と渡されたコンポーネントの名前を合わせたものを、生成されたコンポーネントのdisplayNameとして使用します。例えば、高次コンポーネントwithFoo()がコンポーネントBarを渡すとき、displayNamewithFoo(Bar)のコンポーネントを生成すべきです。

コンポーネントのdisplayNameは開発者ツールやエラーメッセージで使用され、この関係をはっきりと示す値を持つことで、何が起こっているのかを理解するのに役立ちます。

// bad
export default function withFoo(WrappedComponent) {
  return function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }
}

// good
export default function withFoo(WrappedComponent) {
  function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }

  const wrappedComponentName = WrappedComponent.displayName
    || WrappedComponent.name
    || 'Component';

  WithFoo.displayName = `withFoo(${wrappedComponentName})`;
  return WithFoo;
}
  • 引数の命名規則: 異なる目的でDOMコンポーネントの引数名を使用しないでください。

一般的にstyleclassNameのような引数は、特定のものを意味すると考えます。アプリのサブセットに対してこのAPIを変更すると、コードの読みやすさと保守性が低下し、バグが発生する可能性があります。

// bad
<MyComponent style="fancy" />

// good
<MyComponent variant="fancy" />

宣言

  • コンポーネントの命名にdisplayNameは使わないでください。代わりに、参照によってコンポーネントに命名してください。
// bad
export default React.createClass({
  displayName: 'ReservationCard',
  // stuff goes here
});

// good
export default class ReservationCard extends React.Component {
}

アラインメント

// bad
<Foo superLongParam="bar"
     anotherSuperLongParam="baz" />

// good
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
/>

// 一行に収まるならば、その行にまとめてください
<Foo bar="bar" />

// 子要素はインデントを付けます
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
>
  <Quux />
</Foo>

引用符

  • JSXの引数にはダブルクオート(")を使用し、その他のJSにはシングルクオート(')を使用してください。(eslint: jsx-quotes)

通常のHTMLの属性は一般的にシングルクオートの代わりにダブルクオートを使用するため、JSXの属性もこの慣習に倣っています。

// bad
<Foo bar='bar' />

// good
<Foo bar="bar" />

// bad
<Foo style={{ left: "20px" }} />

// good
<Foo style={{ left: '20px' }} />

空白

// bad
<Foo/>

// very bad
<Foo                 />

// bad
<Foo
 />

// good
<Foo />
// bad
<Foo bar={ baz } />

// good
<Foo bar={baz} />

引数

  • 引数名はキャメルケースを使ってください。
// bad
<Foo
  UserName="hello"
  phone_number={12345678}
/>

// good
<Foo
  userName="hello"
  phoneNumber={12345678}
/>
// bad
<Foo
  hidden={true}
/>

// good
<Foo
  hidden
/>

// good
<Foo hidden />
  • <img>タグにはalt引数を含めてください。もし、画像が表現的なコンテンツである場合、altを空の文字列するか、role="presentation"を含めてください。(eslint: jsx-a11y/alt-text)
// bad
<img src="hello.jpg" />

// good
<img src="hello.jpg" alt="Me waving hello" />

// good
<img src="hello.jpg" alt="" />

// good
<img src="hello.jpg" role="presentation" />
  • <img>alt引数には、"image"、"photo"、"picture"のような言葉を使用しないでください。(eslint: jsx-a11y/img-redundant-alt)

スクリーンリーダーはimg要素を既に画像として知らせているので、altテキストにこの情報を含める必要がありません。

// bad
<img src="hello.jpg" alt="Picture of me waving hello" />

// good
<img src="hello.jpg" alt="Me waving hello" />
// bad - ARIA roleでない
<div role="datepicker" />

// bad - 抽象的なARIA role
<div role="range" />

// good
<div role="button" />

スクリーンリーダーやキーボードを使う人々が使用するキーボードショートカットとキーボードコマンド間の不一致は、アクセシビリティを複雑にしてしまいます。

// bad
<div accessKey="h" />

// good
<div />
  • 配列のインデックスをkey引数に使うのは避けてください。一意のIDが好まれます。(理由)
// bad
{todos.map((todo, index) =>
  <Todo
    {...todo}
    key={index}
  />
)}

// good
{todos.map(todo => (
  <Todo
    {...todo}
    key={todo.id}
  />
))}
  • 必須ではないすべての引数に対して常に明示的なdefaultPropsを定義してください。

propTypesはドキュメントの一種であり、defaultPropsを提供するということは、あなたのコードの読者がそれほど多くを引き受ける必要がないことを意味します。また、コードで特定の型チェックを省略することもできます。

// bad
function SFC({ foo, bar, children }) {
  return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
  foo: PropTypes.number.isRequired,
  bar: PropTypes.string,
  children: PropTypes.node,
};

// good
function SFC({ foo, bar, children }) {
  return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
  foo: PropTypes.number.isRequired,
  bar: PropTypes.string,
  children: PropTypes.node,
};
SFC.defaultProps = {
  bar: '',
  children: null,
};

参照

// bad
<Foo
  ref="myRef"
/>

// good
<Foo
  ref={(ref) => { this.myRef = ref; }}
/>

括弧

// bad
render() {
  return <MyComponent className="long body" foo="bar">
           <MyChild />
         </MyComponent>;
}

// good
render() {
  return (
    <MyComponent className="long body" foo="bar">
      <MyChild />
    </MyComponent>
  );
}

// good - 一行なので
render() {
  const body = <div>hello</div>;
  return <MyComponent>{body}</MyComponent>;
}

タグ

// bad
<Foo className="stuff"></Foo>

// good
<Foo className="stuff" />
// bad
<Foo
  bar="bar"
  baz="baz" />

// good
<Foo
  bar="bar"
  baz="baz"
/>

メソッド

  • ローカル変数を閉じるようにアロー関数を使用してください。
function ItemList(props) {
  return (
    <ul>
      {props.items.map((item, index) => (
        <Item
          key={item.key}
          onClick={() => doSomethingWith(item.name, index)}
        />
      ))}
    </ul>
  );
}
  • コンストラクタ内では、renderメソッドに対してイベントハンドラをバインドしてください。

render内でのバインドは、レンダリングのたびに新しい関数を作成してしまいます。

// bad
class extends React.Component {
  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv.bind(this)} />;
  }
}

// good
class extends React.Component {
  constructor(props) {
    super(props);

    this.onClickDiv = this.onClickDiv.bind(this);
  }

  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv} />;
  }
}
  • Reactコンポーネントのメソッドにアンダースコアの接頭辞を使わないでください。

アンダースコアのプレフィックスは、privateを示すために他の言語の規約として使用されることがあります。しかし、これらの言語とは異なり、JavaScriptではprivateのネイティブサポートはなく、すべてがpublicです。あなたの意図にかかわらず、あなたのプロパティにアンダースコアのプレフィックスを追加しても、それを実際にprivateにするわけではなく、任意のプロパティ(アンダースコアのプレフィックス付きかどうか)をpublicとして扱うべきです。詳細は、#1024#490を参照してください。

// bad
React.createClass({
  _onClickSubmit() {
    // do stuff
  },

  // other stuff
});

// good
class extends React.Component {
  onClickSubmit() {
    // do stuff
  }

  // other stuff
}
// bad
render() {
  (<div />);
}

// good
render() {
  return (<div />);
}

順序

  • class extends React.Component用の順序
  1. 任意のstaticメソッド
  2. constructor
  3. getChildContext
  4. componentWillMount
  5. componentDidMount
  6. componentWillReceiveProps
  7. shouldComponentUpdate
  8. componentWillUpdate
  9. componentDidUpdate
  10. componentWillUnmount
  11. onClickSubmit()onChangeDescription()のようなクリックハンドラまたはイベントハンドラ
  12. getSelectReason()getFooterContent()のようなrender用のゲッターメソッド
  13. renderNavigation()renderProfilePicture()のような任意のレンダリングメソッド
  14. render
  • propTypesdefaultPropscontextTypesなどの定義方法
import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
  id: PropTypes.number.isRequired,
  url: PropTypes.string.isRequired,
  text: PropTypes.string,
};

const defaultProps = {
  text: 'Hello World',
};

class Link extends React.Component {
  static methodsAreOk() {
    return true;
  }

  render() {
    return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>;
  }
}

Link.propTypes = propTypes;
Link.defaultProps = defaultProps;

export default Link;
  1. displayName
  2. propTypes
  3. contextTypes
  4. childContextTypes
  5. mixins
  6. statics
  7. defaultProps
  8. getDefaultProps
  9. getInitialState
  10. getChildContext
  11. componentWillMount
  12. componentDidMount
  13. componentWillReceiveProps
  14. shouldComponentUpdate
  15. componentWillUpdate
  16. componentDidUpdate
  17. componentWillUnmount
  18. onClickSubmit()onChangeDescription()のようなクリックハンドラまたはイベントハンドラ
  19. getSelectReason()getFooterContent()のようなrender用のゲッターメソッド
  20. renderNavigation()renderProfilePicture()のような任意のレンダリングメソッド
  21. render

isMounted

isMountedはアンチパターンであり、ES6では使用できず、今後正式に廃止される予定です。