現象
コンポーネント間で相互に参照しているような関係で発生します。
このエラーは React に限りません。
例えば以下の状況で発生します。
-
ComponentA.tsx
:ComponentB.tsx
を表示 -
ComponentB.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
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
の初期化前にアクセスしてしまっているのでしょうか?
widhtA
は const widthA
文で初期化されているのではないでしょうか?
今回の根本的な原因は、ファイルを循環参照していること です。
-
ComponentA.tsx
↔ComponentB.tsx
なぜこのエラーが発生するかを理解するには、 JavaScript の Module の評価順序 を知る必要があります。
Module では、ファイルに import
文が書かれていた場合、 そのファイルの評価を一旦止めて import
先のファイルを評価します。
(リファレンスを調べきれなかったので若干推測が入っていますが、大きくは外していないはずです。修正依頼は大歓迎です。)
今回示したコード例では、以下の順序でコードを評価します。
-
ComponentA.tsx
を1行目から評価 -
ComponentA.tsx
のimport ComponentB from '~/components/ComponentB'
を評価 -
ComponentB.tsx
を1行目から評価 -
ComponentB.tsx
のimport { widthA } from '~/components/pages/ComponentA'
を評価 -
ComponentA.tsx
のwidthA
変数を探しに行くが、宣言されておらずエラー
a. この時点ではComponentA.tsx
の評価は2で止まっている
b.const widthA
文が評価されておらず、そのような変数は存在していない
そのため、変数初期化前にアクセスエラー が発生します。
解決策
解決策としては循環参照を解消します。
1つの方法として、循環参照している箇所をファイルに切り出すことが有効です。
以下の例では新しく const.ts
ファイルに定数を定義しています。
widthA
を baseWidth
として抽象化しています。
-
ComponentA.tsx
←const.ts
の方向に依存 -
ComponentB.tsx
←const.ts
の方向に依存
export const baseWidth = 30
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
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つとして重要になります。