React

ESLintでAirbnbのルールを使って、Reactコンポーネントをリファクタリングする

ESLintを入れておくと、タイポやイレギュラーな書き方をすぐに見つけることができて便利です。 ということで、ReactコンポーネントをESLintのAirbnbルールセットでリファクタリングしてみましょう。 リント対象 […]

広告ここから
広告ここまで

ESLintを入れておくと、タイポやイレギュラーな書き方をすぐに見つけることができて便利です。
ということで、ReactコンポーネントをESLintのAirbnbルールセットでリファクタリングしてみましょう。

リント対象コードを作る

対象のファイルを作っておきましょう。
今回は以前Jestの記事書いた時に使ったコードをネタにしてみます。

ExampleBtn.js

import React from 'react'

export default class ExampleBtn extends React.Component {
  render () {
    return (
      <button
        type={this.props.type}
      >
        {this.props.children}
      </button>
    )
  }
}

Jestでスナップショットテストを用意する

リファクタリング時に大規模なコードの変更が入る可能性もあります。
念のためJestでスナップショットを作成して、出力内容が変わってしまっていないかを確認するようにします。
Jestのセットアップ方法は過去の記事を参考にしてください。

__tests__/ExampleBtn/test.js

import React from 'react'
import ExampleBtn from '../../ExampleBtn'
import renderer from 'react-test-renderer'

test('render test', () => {
  const component = renderer.create(
    <ExampleBtn type="text">Test</ExampleBtn>
  )
  let tree = component.toJSON()
  expect(tree).toMatchSnapshot()
})

test('render without type props', () => {
  const component = renderer.create(
    <ExampleBtn>Test</ExampleBtn>
  )
  let tree = component.toJSON()
  expect(tree).toMatchSnapshot()
})

ESLintとAirbnbのルールセットを入れる

$ npm i -D eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y 

.eslintrc

{
  "extends": "airbnb"
}

実行する

$ ./node_modules/eslint/bin/eslint.js ./ExampleBtn.js

ExampleBtn.js
   1:26  error  Missing semicolon                               semi
   3:16  error  Component should be written as a pure function  react/prefer-stateless-function
   4:9   error  Unexpected space before function parentheses    space-before-function-paren
   6:7   error  JSX not allowed in files with extension '.js'   react/jsx-filename-extension
   7:26  error  'type' is missing in props validation           react/prop-types
  10:21  error  'children' is missing in props validation       react/prop-types
  12:6   error  Missing semicolon                               semi

✖ 7 problems (7 errors, 0 warnings)

なおしていく

[1:26][12:6] error Missing semicolon

行末にセミコロンを入れましょう。
宗教戦争の気配を感じたら、「Airbnbはセミコロン入れるやり方してるので」って言っておきましょう。

[4:9] error Unexpected space before function parentheses

render ()のように()の前にスペースを入れると出るメッセージです。
render()とくっつけておきましょう。

[6:7] error JSX not allowed in files with extension ‘.js’

「JSX書くなら拡張子.jsはおかしいよね」ということみたいです。

mv ExampleBtn.js ExampleBtn.jsxとかで.jsxにリネームしておきましょう。

[3:16] error Component should be written as a pure function

Reactやってて対応に手こずるのがこいつかなと思います。

「Stateの変更がないコンポーネントはclassで定義するな」というものなのですが、開発途中だったりすると「今はないけどあとでステート変更組むんだよ」って時にもこのエラーがでてきます。

ちなみに今回のコードでこのエラーを消そうとするとおおよそこんな感じになります。

import React from 'react';

const ExampleBtn = (props) => {
  const { type, children } = props;

  return (
    <button
      type={type}
    >
      Hoge |
      {children}
    </button>
  );
};

module.exports = ExampleBtn;

かなり豪快に書き換えることになるので、Jestのスナップショットテストなどを使って出力に変更が出ないか確認しながらやりましょう。

$ ./node_modules/jest/bin/jest.js  
 PASS  __tests__/ExampleBtn/test.js
  ✓ render test (13ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        1.464s
Ran all test suites.

[7:26][10:21]error ‘type’ is missing in props validation

これはコンポーネントが受け取るプロパティのバリデーションを要求しているものです。

prop-typesというパッケージが必要になりますので、追加しましょう。

$ npm i -S prop-types

React.PropTypesはdeprecated as of React v15.5なので要注意です。

インポートは以下のように行います。

import React from 'react';
import PropTypes from 'prop-types';

今回バリデーションが必要なプロパティはchildrentypeです。
ということで以下のようなコードを追加します。

ExampleBtn.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.element,
  ]).isRequired,
  type: PropTypes.string,
};
ExampleBtn.defaultProps = {
  type: 'submit',
};

typeはボタンのタイプを指定するものなので、文字列のみ受け入れることにしましょう。また、プロパティの指定がなかった場合はtype="submit"になるようにdefaultPropsを設定しています。
children<ExampleBtn>ここに入れる文字</ExampleBtn>という使われ方なので、これも文字列を受け入れます。ただしstringのみの受け入れだとReactコンポーネントも拒否されてしまうので、oneOfTypestringまたはelementのいずれかを受け入れるように設定しておきましょう。

プロパティはdefaultPropssでデフォルト値を設定するか、isRequiredで必須とするかのどちらかを選べます。

リント結果

ということで変更後は以下のようになりました。

import React from 'react';
import PropTypes from 'prop-types';

const ExampleBtn = (props) => {
  const { type, children } = props;

  return (
    <button
      type={type}
    >
      Hoge |
      {children}
    </button>
  );
};

ExampleBtn.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.element,
  ]).isRequired,
  type: PropTypes.string,
};
ExampleBtn.defaultProps = {
  type: 'submit',
};

module.exports = ExampleBtn;

今回の例のように、コードによってはかなり大規模なリファクタリングが必要になる場合があります。
そのためJestでスナップショットをとっておいて、出力に変化がないかを確認するくらいの事前準備をしておいた方が良いかなと思います。

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts