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でスナップショットをとっておいて、出力に変化がないかを確認するくらいの事前準備をしておいた方が良いかなと思います。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark