LoginSignup
11
1

More than 1 year has passed since last update.

React で ReferenceError: Cannot access 'XXX' before initialization が発生する

Last updated at Posted at 2022-03-14

現象

コンポーネント間で相互に参照しているような関係で発生します。
このエラーは React に限りません。

例えば以下の状況で発生します。

  • ComponentA.tsxComponentB.tsx を表示
  • ComponentB.tsxComponentA.tsx の定数を利用
ComponentA.tsx
import React, { VFC } from 'react'
import ComponentB from '~/components/ComponentB'

export const widthA = 30

const ComponentA: VFC = () => {
  return (
    <>
      <div style={{ width: widthA }}>This is ComponentA.</div>
      <ComponentB />
    </>
  )
}

export default ComponentA
ComponentB.tsx
import React, { VFC } from 'react'
import { widthA } from '~/components/ComponentA'

const widthB = widthA + 10

const ComponentB: VFC = () => {
  return <div style={{ width: widthB }}>This is ComponentB.</div>
}

export default ComponentB

エラー

アプリケーションを実行するとエラーが発生します。

Uncaught ReferenceError: Cannot access 'widthA' before initialization

このエラーについて調べると、Mozila のページ では以下のように説明されています。

JavaScript の例外 "can't access lexical declaration `variable' before initialization" は、変数が初期化前にアクセスされたときに発生します。
これはブロック文内で、 let または const 宣言が定義される前にアクセスされたときに発生します。

どうやら変数が初期化される前に widthA にアクセスしてしまっているようです。

原因

widthA が初期化される前にアクセスしていることが直接的な原因です。

では、なぜ widthA の初期化前にアクセスしてしまっているのでしょうか?
widhtAconst widthA 文で初期化されているのではないでしょうか?

今回の根本的な原因は、ファイルを循環参照していること です。

  • ComponentA.tsxComponentB.tsx

なぜこのエラーが発生するかを理解するには、 JavaScript の Module の評価順序 を知る必要があります。
Module では、ファイルに import 文が書かれていた場合、 そのファイルの評価を一旦止めて import 先のファイルを評価します。
(リファレンスを調べきれなかったので若干推測が入っていますが、大きくは外していないはずです。修正依頼は大歓迎です。)

今回示したコード例では、以下の順序でコードを評価します。

  1. ComponentA.tsx を1行目から評価
  2. ComponentA.tsximport ComponentB from '~/components/ComponentB' を評価
  3. ComponentB.tsx を1行目から評価
  4. ComponentB.tsximport { widthA } from '~/components/pages/ComponentA' を評価
  5. ComponentA.tsxwidthA 変数を探しに行くが、宣言されておらずエラー
    a. この時点では ComponentA.tsx の評価は2で止まっている
    b. const widthA 文が評価されておらず、そのような変数は存在していない

そのため、変数初期化前にアクセスエラー が発生します。

解決策

解決策としては循環参照を解消します。
1つの方法として、循環参照している箇所をファイルに切り出すことが有効です。

以下の例では新しく const.ts ファイルに定数を定義しています。
widthAbaseWidth として抽象化しています。

  • ComponentA.tsxconst.ts の方向に依存
  • ComponentB.tsxconst.ts の方向に依存
const.ts
export const baseWidth = 30
ComponentA.tsx
import React, { VFC } from 'react'
import ComponentB from '~/components/ComponentB'
import { baseWidth } from '~/consts/const'

const ComponentA: VFC = () => {
  return (
    <>
      <div style={{ width: baseWdith }}>This is ComponentA.</div>
      <ComponentB />
    </>
  )
}

export default ComponentA
ComponentB.tsx
import React, { VFC } from 'react'
import { baseWidth } from '~/consts/const'

const widthB = baseWidth + 10

const ComponentB: VFC = () => {
  return <div style={{ width: widthB }}>This is ComponentB.</div>
}

export default ComponentB

これで循環参照はなくなり、エラーが解決されました。

最後に

循環参照はかなりやっかいな問題ですが、銀の弾丸のような対処法はないと感じます。
スケーラブルなディレクトリ構成を決め、依存関係を明確にすることが設計の1つとして重要になります。

11
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1