React のカスタムフックをテストするために @testing-library/react-hooks
をインストールしました。
そして tsc
を実行すると types 周りがコケる現象が発生しました。
$ tsc --noEmit
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3092,14): error TS2300: Duplicate identifier 'LibraryManagedAttributes'.
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3103,13): error TS2717: Subsequent property declarations must have the same type. Property 'a' must be of type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>', but here has type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
...
本記事はこのエラーの考察と解決方法を記録したものです。
TL;DR
違うバージョンの @types/*
が解決されたので
-
npm
ならnpm dedupe && npm install
-
yarn
ならnpx yarn-deduplicate && yarn install
1
を実行して一個にまとめること。
発生経緯
説明用のサンプルを公式から拝借しました。
こんな感じにテストケースを書いています。
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('should increment counter', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
このテストケースを jest
から実行するときはなんの問題もありませんが、tsc
を実行したときだけコケました。
問題の原因
プロジェクトの構造はこんな感じにしています。
├── README.md
├── src
│ ├── components <- React のコンポーネントを置くディレクトリ
│ │ └── MyComponent.ts
│ └── models <- カスタムフックとかを置くディレクトリ
│ ├── useCounter.ts
│ ├── useCounter.spec.ts
tests
フォルダを別に切り出すのではなく、テストファイルをテストしたいもののすぐ近くに置く構成にしています。
そして tsconfig.json
の include
フィールドに src
以下を追加すると
{
"include": [
"src/**/*"
]
}
tsc
を実行する時ファイルパスを指定しなくてもOKになります。
$ tsc --noEmit
この構成にしているため、tsc
の実行時は src
以下のソースファイルとテストファイルの両方を見るようになっています。
今回のエラーメッセージ
$ tsc --noEmit
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3092,14): error TS2300: Duplicate identifier 'LibraryManagedAttributes'.
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts(3103,13): error TS2717: Subsequent property declarations must have the same type. Property 'a' must be of type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>', but here has type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
...
はずばり @types/react/index.d.ts
を読み込んだ時、型が二重定義されていたため起こったエラーです。
型の二重定義
node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts
を見ればわかりますが、
global
以下に定義されたものがコンフリクトしています。
これが起こったのは @types/react
が複数存在しているためです。
$ yarn list @types/react
yarn list v1.22.11
warning Filtering by arguments is deprecated. Please use the pattern option instead.
├─ @testing-library/react-hooks@7.0.2
│ └─ @types/react@17.0.27
└─ @types/react@17.0.15
プロジェクトからは @types/react@17.0.15
を直接利用していますが、
@testing-library/react-hooks@7.0.2
からは違うバージョンの @types/react@17.0.27
が間接的に import されています。
tsc
を実行するときは、プロジェクトから直接依存している types
は自動的に読み込まれますが、テストファイルの
import { renderHook, act } from '@testing-library/react-hooks'
のこれも @testing-library/react-hooks
内の @types/react@17.0.27
を読み込んでいます。
この2つの @types/react
は同時に global
に型を定義しているため、同じ名前の型が二重定義されてコンフリクトしています。
複数のnode_modules
npm
/ yarn
を利用するプロジェクトはこのように、node_modules
以下にさらに node_modules
を保つことができます。
./node_modules
./node_modules/make-dir/node_modules
./node_modules/npm/node_modules/resolve/test/shadowed_core/node_modules
./node_modules/eslint-plugin-react/node_modules/resolve/test/resolver/symlinked/_/node_modules
例えば a
と b
から複数のライブラリのから依存されているライブラリ c
はもし同じバージョンに解決できたら、共通でルート以下の ./node_modules
にその共用のライブラリを置かれますが、
同じバージョンに解決されなかった場合はそれぞれのディレクトリ以下
./node_modules/a/node_modules/c
./node_modules/b/node_modules/c
に置くことになります。
今回の @testing-library/react-hooks
でいうと、実は "@types/react": ">=16.9.0",
を満たせば問題ないので、プロジェクトの @types/react
と同じバージョンに解決することができたはずです。
解決方法
ここに解決方法が書かれています。
かいつまんで言うと、
-
npm
ならnpm dedupe && npm install
-
yarn
ならnpx yarn-deduplicate && yarn install
1
を実行したら、2つバージョンが違った @types/react
を一個に解決できて、今回のエラーを修正できました。
$ yarn list @types/react
yarn list v1.22.11
warning Filtering by arguments is deprecated. Please use the pattern option instead.
└─ @types/react@17.0.27