eslint-plugin-reactのno-exhaustive-depsは何故propsのプロパティの関数を使う際にpropsも依存に要求するのか
TL;DR
- オブジェクトのpropetyの関数をコールする際にthisが渡されるため。
- propsは常に分割代入しておくのがよいとされる(react/destructuring-assingmentでESLintに設定可能)。
背景
eslint-plugin-reactのno-exhaustive-depsはprops
のpropertyの関数を使う際に下記のようにprops
自身も要求します
(正確にはprops
に限らずオブジェクトのpropertyを関数として使用するとオブジェクト自身も要求します)。
useEffect( () => props.updateNumber(5), [props.updateNumber], // React Hook useEffect has a missing dependency: 'props'. );
props
内のプロパティとしてはprops.updateNumber
しか使用していないのに何故 props
を要求するのでしょうか?
理由
no-exhaustive-depsが追加された時にフィードバックを募るIssueで同様の質問がありました。
Not sure if this is intentional or not: When you call a function from props, the linter suggests adding the entire props object as a dependency.
和訳
意図的なのかわからないのですが、
props
から関数を読んだ時、リンターが依存としてprop
オブジェクト全体を追加することを提案してきます。
それに対する回答は次のとおりです。
This is because technically
props.foo()
passesprops
itself asthis
tofoo
call. Sofoo
might implicitly depend onprops
. We'll need a better message for this case though. The best practice is always destructuring.
和訳
それは厳密に言うと
props.foo()
はthis
としてprops
自身をfoo
呼び出しに渡すためです。foo
は暗黙的にprops
に依存している可能性があります。このケースについてより良いメッセージが必要ですね。ベストプラクティスは常に分割代入することです。
つまり、オブジェクトのプロパティを関数として呼び出すとthis
としてオブジェクト自身を使用するため、オブジェクトにも依存してしまうのでexhaustive-depsはオブジェクトを依存のリストに追加することを提案します。
挙動の確認
FirefoxのConsoleでthis
への依存について動作確認してみます。
>> function func() { return this.a; } << undefined >> func(); // 関数をそのまま呼ぶとthisはグローバルオブジェクトであるウィンドウオブジェクトで、this.aはwindow.aでありundefinedです << undefined >> const x = { a: 'aaa', f: func }; << undefined >> x.f(); // xのプロパティとして呼ぶとthisはxになるので、this.aはx.aであり'aaa'です。 << "aaa" >> const { f } = x; << undefined >> f(); // 分割代入をして呼び出すと、thisはウィンドウオブジェクトになりthis.aはundefinedになります。 << undefined
次のことがわかります。
- オブジェクトのプロパティを関数として呼び出すとオブジェクトの状態に依存して結果が変わる場合がある
- 分割代入をしてプロパティとしての呼び出しでないようにすると
this
によるオブジェクトへのアクセスはできなくなる
対処法
props内の関数を使用したいけどprops自身をdepsに加えたくない場合はどのようにすればよいかというと、理由として引用したコメントに書かれているとおり、常にpropsを分割代入するようにすればよいです。
常にpropsを分割代入することを推奨するためルールも存在するので、設定しておくとスタイルを統一できます。