🥏

react nativeでRequire cycles are allowed, but can result in uninitia...

2022/08/17に公開

概要

ウォーニングの全文はこんな感じ。

Require cycle: src/stores/masterStore.ts -> src/classes/models/Preference.ts -> src/stores/masterStore.ts

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.
at node_modules/metro-runtime/src/polyfills/require.js:116:6 in metroRequire
at src/classes/models/Preference.ts:1:0 in <global>
at node_modules/metro-runtime/src/polyfills/require.js:339:11 in loadModuleImplementation
at src/stores/masterStore.ts:1:0 in <global>
以下スタックトーレスが続く...

問題の内容はエラー文の通りですが・・・

// masterStore.ts
// 実際のパスはもっとディレクトリ構造があるので複雑ですが省略します。
import Preference from './Preference'

class MasterStore {...}

// 一つのインスタンスを全体で使い回すためnewして返す。
const masterStore = new MasterStore();
export default masterStore;
// Preference.ts
import masterStore from './masterStore'
class Preference {...}

export default Preference;

このように相互にimportして循環参照が起きてるわけですね。

解決策

今回取った解決策は最後に書きますが、一般的な循環参照の解決策を載せておきます。

型の参照だけだったら

型の参照だけだったら簡単です。

import type Preference from './Preference';

に変えてやれば警告は消え問題なく動きます。ただし今回はmasterStoreの中でnewしてるので無理でした。

設計を見直す

そんな無茶な・・・w

まあ、色々試してみたんですけど、責任的にめちゃめちゃコードが汚くなったので、途中で断念。

一つのファイルにまとめちゃう

今回はこれでお茶を濁しました。強引ですよね・・・ファイル読みづらくなりました。

// masterStore.ts
import Preference from './Preference'

class MasterStore {...}

// 一つのインスタンスを全体で使い回すためnewして返す。
const masterStore = new MasterStore();
export default masterStore;

// Preference.ts
import masterStore from './masterStore'
export class Preference {...}

解説

この警告ですが、循環参照すると必ず起きるのか?というとそうでもなくて、class定義どうしだと出ないんですね。

// MasterStore.ts
import Preference from './Preference'

class MasterStore {...}

export default MasterStore;
// Preference.ts
import MasterStore from './MasterStore'

class Preference {...}

export default Preference;

こういう状態ですね。これだと警告は出ません。なので、シングルトンインスタンスを返すファイルを別ファイルにして直接参照しなければいけるか?と思い・・

// MasterStore.ts
import Preference from './Preference'

class MasterStore {...}

export default MasterStore;
// masterStore.ts
import MasterStore from './MasterStore';

const masterStore = new MasterStore();
export default masterStore;

とファイルを二つに分け・・・

// Preference.ts
import masterStore from './masterStore'

class Preference {...}

export default Preference;

としたら

Require cycle: src/stores/masterStore.ts -> src/classes/MasterStore.ts -> src/classes/models/Preference.ts -> src/stores/masterStore.ts

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.

結局but can result in uninitialized valuesな訳なんでまあ、そりゃあダメですよね。

なので、今回は一つのファイルにまとめるという強引な手法で解決しました。なんか、webpackのフックでトランスパイル時に自動で纏めさせるとかできないんですかね?

もしかしたら・・・

今回expoがどうやって循環参照を見つけてるのかwebpack.configを調べてて、下記のファイルと見つけました。

https://github.com/expo/expo-cli/blob/bf8edaea7b9bda705ffe0bcc666203bc4879f96b/packages/xdl/src/internal.ts

コメントにあるように循環参照を回避するために利用してるみたいで、似た感じを試してみたけど、ダメでした。惜しいところまできてる感じはしたのですが、根本的な問題点と回避方法を見つけられませんでした。

今回は、一つにまとめたファイルがあまり大きくなく、数も少ないので、この方法で甘んじ先に進めます。良い方法知ってたら教えてくただけると助かります。

Discussion