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';
今回バリデーションが必要なプロパティはchildren
とtype
です。
ということで以下のようなコードを追加します。
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コンポーネントも拒否されてしまうので、oneOfType
でstring
または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でスナップショットをとっておいて、出力に変化がないかを確認するくらいの事前準備をしておいた方が良いかなと思います。