Reactのべからず集

Reactは便利なライブラリですが、ライブラリの想定を外れる使い方をするとうまく動かないとか、進化が続いているのですでに古くなってしまった書き方とかが存在します。

ここでは、そのような、Reactでやるべきでない書き方についてまとめてみました。

## ★★★★

この「★★★★」は、「守らないと正常動作しなくなる」レベルのものについて扱います。

### JSXを使うファイルで`React`を用意し忘れる

JSXは`React.createElement`に変換されますので、直接アクセスしているように見えなくても、名前空間内に`React`が必要です。

* [eslint react/react-in-jsx-scope](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md)

### コンポーネント内部から`prop`を書き換える

あくまで`props`は親から子へ渡されるものなので、子の側で書き換えることはReactの枠組みの想定外となります。

### `setState`以外の手段で`state`を書き換える

クラスコンポーネントのコンストラクタは別ですが、それ以外の場所で`state`を書き換えたい場合は`this.setState`を使いましょう。直接書き換えると、後の`setState`で別な値が上書きされるなど、正常でない動作の原因となります。

* [eslint react/no-direct-mutation-state](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md)

### React外の手段で、Reactが生成したDOMを書き換える

ReactのDOMは差分更新を行いますので、React外で書き換えると差分更新が破綻してしまいます。

### メモ化したコンポーネントで、`prop`や`state`内のオブジェクトを破壊的に変更する

`PureComponent`や`React.memo`は、破壊的変更がないことを前提として処理を効率化するものですので、破壊的変更を行うものに使うと前提が崩壊します。破壊的変更が避けられないのであれば、`PureComponent`などを使わないようにしましょう。

### `key`の重複

コンポーネントの配列で、各要素を識別するために`key`を入れますが、`key`同士が重複すると正常に動作しません。

### HTMLのつもりで属性を書く

`id`や`name`などはJSXでもHTMLでも同じですが、JSXはJavaScriptということもあって、[JavaScriptから見えるproperty](https://qiita.com/jkr_2255/items/66a16bd969454ee8b114)としての名前が必要となります。たとえば、`class`ではなく`className`、`readonly`ではなく`readOnly`などです。

なお、カスタムデータ属性の`data-***`や、アクセシビリティの`aria-***`属性（[React公式](https://ja.reactjs.org/docs/accessibility.html#wai-aria)）は、そのままハイフン区切りで書きます。

* [eslint react/no-unknown-property](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md)

### `value`のデフォルト値を`undefined`のまま放置する

Reactの入力コンポーネントには[ControlledとUncontrolled](https://qiita.com/jkr_2255/items/61b7ade23b30922b975e)がありますが、これは`value == null`かで判定されます[^dda]。ということで、オブジェクトの存在しない値を参照して`undefined`となっているものをそのまま`value`に供給してしまうと、Uncontrolled扱いとなってしまいます。あとから`Controlled`に切り替われば警告を発します[^hidden]。

[^dda]: `value`に、プリミティブでもない[`document.all`](https://qiita.com/jkr_2255/items/f9b7218d7a2b54424c12)なんてものを与える人は無視することにします。
[^hidden]: どういうわけか、ControlledもUncontrooledも関係なさそうな`<input type="hidden">`でも警告されます。

もちろん値をきっちり管理する必要もあるのですが、文字列が入るところなら`value={this.state.someValue || ''}`のように強制的に空文字列に変換してクリアする方法も考えられます。


### `render`関数を、何も返さずに`return`する

`render`関数（あるいは関数コンポーネント）の返り値として、JSXやその配列以外に`null`も許容されていますが、`undefined`は適切ではありません。何も描画したくないときは、`return;`ではなく`return null;`で脱出してください。また、[うっかり`return`を書き忘れない](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md)ように注意も必要です。

### Hooksの掟を守らない

これは[React公式](https://ja.reactjs.org/docs/hooks-rules.html)や[自分の過去記事](https://qiita.com/jkr_2255/items/40203693aa8a60bbd9ef)にもありますが、Hooksは「呼ばれる順番」でデータを紐づけています。そのため、「条件分岐」「ループ」「途中脱出」などを入れず、毎回同じ順序で同じ回数だけのHookを呼ぶ必要があります。

なお、これは[eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)が警告してくれます。

### Hooksの`deps`配列に必要なものを渡さない

`useCallback`や`useMemo`には依存する変数の配列を渡す必要がありますが、必要な変数を入れ忘れると、クロージャが値を束縛したままになるので、適切な値が入らないことになります。自分自身、[`state`値を入れ忘れた](https://qiita.com/jkr_2255/items/2c3900d2c03646e8bbd2)ことがあります。

なお、これも[eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)が警告してくれます。

## ★★★

この「★★★」は、「一応動作はするけど、できるかぎり避けたほうがいい」レベルのものを挙げていきます。

### `ReactDOM.render`の返り値を使う

いまのところ、`ReactDOM.render`はコンポーネントへの参照を返しますが、この値を使うのは非推奨です。コンポーネントインスタンスが必要な場合、`ref`を利用しましょう。

* [eslint react/no-render-return-value](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md)

### `findDOMNode`の利用

仮想DOMに`ref`を付けることで代用できますし、関数コンポーネントに対しては利用できません。`findDOMNode`を使わないようにしましょう。

* [eslint react/no-find-dom-node](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md)

### 古いライフサイクルメソッドの使用

React 16.3以降、`componentWillMount`、`componentWillUpdate`、`componentWillReceiveProps`といったライフサイクルコールバックが、「将来の非同期化に対応できない」ということで非推奨となりました。

* [eslint react/no-unsafe](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unsafe.md)

### `React.Component`の継承

Reactチームによれば、コンポーネントの再利用はコンポジションによってなされるべきであって、FacebookでReactを使っていても継承が必要な場面は見つからなかった、とのことです（[公式](https://ja.reactjs.org/docs/composition-vs-inheritance.html#so-what-about-inheritance)）。

### `forceUpdate()`の利用

クラスコンポーネントには、`forceUpdate()`というメソッドがありますが、そもそもこれが必要になる例というのが、

* `props`や`state`以外で、外部で変化する値を参照していた場合
* `props`や`state`の一部を破壊的に変更した結果、更新が見えなくなった場合
* `shouldComponentUpdate`で必要な更新まで弾いてしまう場合

など、コンポーネントの設計として良くないパターンが大半です。きちんと作れば、必要になることはまずありません。

### `key`に配列のインデックスを使う（あるいは、`key`自体を与えない）

全く書き換わらない配列から`map`して生成する場合など問題のない場面もありますが、多くの場合配列のインデックスと中身の関係性は実行中に変化しうるものです。インデックスをキーとすると、中身の変化についていけない場面が考えられます。

* [eslint react/jsx-key](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md)
* [eslint react/no-array-index-key](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md)

### `this.setState`内で`this.state`を参照する

現在の値を`this.state`から参照すると、複数の`setState`が重なった際に適切でない値となる危険があります。`setState`を関数形式にすればこの問題は完全に回避できるので、そう書きましょう。

* [eslint react/no-access-state-in-setstate](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-access-state-in-setstate.md)


## ★★

この「★★」は、「動くけどパフォーマンスに悪影響するもの」あるいは「問題はそこまで大きくないけど、容易に回避できるもの」を取り上げていきます。

### JSXに即時関数を直書きする

`onClick={() => someFunction()}`のようにJSXに与える値の内部で即時関数を作ってしまうと、実行のたびに関数インスタンスが違ってくるため、メモ化が意味をなさなくなってしまいます。クラスコンポーネントであればプロパティに、関数コンポーネントであれば`useCallback`などで関数インスタンスを固定しましょう。

※ `ref`はReact内部で処理され、子コンポーネントに渡されないため、[Callback Refでも問題ありません](https://qiita.com/mpyw/items/33a76b9f937f3ccbc507)。

* [eslint react/jsx-no-bind](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md)

### JSXにスタイルを直書きする

JSXでは`style`はオブジェクト形式で与えることとなります。そして、上の関数のときと同様、オブジェクトは毎回インスタンスが変化します。ということで、「固定のスタイルを書いたら、そのスタイルオブジェクトが毎回変化してメモ化が効かなくなる」という愉快なことになってしまいます。

そもそも論を言えば、変化しないスタイルはCSSで与えるべきでしょう。

### JSXの`props`に固定的なJSXを直書きする

これも上2つと同様に「同じでいいオブジェクトが変化し続ける」事態とはなりますが、これは[`@babel/plugin-transform-react-constant-elements`](https://babeljs.io/docs/en/babel-plugin-transform-react-constant-elements)を使うことで、記法を変えずに解決可能です（[自分の過去記事](https://qiita.com/jkr_2255/items/fe1c14f7257f6f13f0d5)）。

### `useMemo`や`useCallback`を1引数で呼ぶ

これらの`hooks`を`deps`なしで呼び出した場合、メモ化は何も行われず、引数がそのまま返ってきます。メモ化が不要なら、`useMemo`や`useCallback`を使わない形で問題ないでしょう。

（`useEffect`を同様な形で呼んだ場合、「毎回実行する」という意味を持つものとなります）

### `useMemo`や`useCallback`の`deps`に、その場で生成するオブジェクトを入れる

`deps`の配列は`Object.is`相当で要素ごとに比較されますので、同じものを再生成できるプリミティブや、実行を超えて引き継がれるオブジェクト（別に`useMemo`、`prop`、`state`、外部変数など）ならいいのですが、その場で生成したオブジェクトを入れるとメモ化が無効となります。

### `children`を取るコンポーネントをメモ化する

これは[自分が以前に書きました](https://qiita.com/jkr_2255/items/2e40f7c9ecf300e8d945#children%E3%81%A8reactmemo)が、`children`の内部構造の関係もあって、メモ化が有効な例がごく限られてしまいます。

### デバッグ時に`displayName`をセットしない

コンポーネントに`displayName`があるのとないのとで、デバッグ時のやりやすさが格段に違ってきます。

なお、関数宣言（`function Component()`）やクラスの場合、名前が`displayName`として自動で使われますが、圧縮すると吹き飛びますのでその点は要注意です。

## ★

この「★」は、「ある程度考えておいたほうがいいけど、そう簡単に実装できない場合もある」ものを中心に触れていきます。

### `componentDid***`で`setState`する

もちろん必要な場面もあるかもしれませんが、描画完了時のコールバックで改めて`setState`を行うと、そこから再度動き出してしまいます。できれば避けたい書き方です。

### 副作用のある`constructor()`

これも[自分が以前に書きました](https://qiita.com/jkr_2255/items/b154f502d714a3b32e86)が、React 17の非同期レンダリングでは、コンストラクタも1コンポーネントに対して複数回実行されうるものとなります。外部になにか影響するものは`componentDidMount`に移動しましょう。

### 関数コンポーネントで、他の`state`を参照しての`setState`

関数コンポーネントで、複数の`state`が絡む更新を行いたい場合、あまりよくないのですが`state`の値を直接参照することとなってしまいます。できることなら、関連する複数の`state`を1つのオブジェクトにまとめて、更新関数や`reducer`などで一括処理させるようにするほうがいいでしょう。