React, TypeScriptでenvを読み込みたいが型がundefinedになる

create-react-app -- template typescript で作ったプロジェクトに環境変数を読み込んで使いたい時の対処法です。

下記のように環境変数を読み込ませたいのですが、この場合WEB_URLの部分はstring | undefinedと推論されます。

const hoge = process.env.WEB_URL;
// const hoge: string | undefined

process.envからコードジャンプを使って型を追っていくと、node_modules/@types/node/global.d.tsの中に、このような記述があることがわかります。

interface Dict<T> {
   [key: string]: T | undefined;
}

値が T または undefinedとなっています。

const hoge = process.env.WEB_URL || '';のようにdefault値を用意する手もありますが、今回はこちらで使われている型を拡張していきます。

create-react-appを実行したときに/src直下にreact-app-env.d.tsというファイルが追加されています。 こちらに型を書いていきます。

最終的な形はこのようになります。

/// <reference types="react-scripts" />

declare namespace NodeJS {
  interface ProcessEnv {
    readonly REACT_APP_WEB_URL: string;
  }
}

prefixとしてREACT_APP_環境変数につける必要があります。

create-react-app.dev

.envの中身は下記のようになります。

REACT_APP_WEB_URL=https://example.com

これで最初に書いた方がstringと推論されるようになりました。

const hoge = process.env.WEB_URL;
// const hoge: string;

余談 react-app-env.d.tsについて

react-app-env.d.tsですが、init直後はこのようになっていました。

/// <reference types="react-scripts" />

この記述はTypeScriptのTriple-Slash Directivesというもので、

何種類かありますが、今回使用されているのは /// <reference types="..." />型ですね。

ドキュメントには下記のように書かれています。

a /// directive declares a dependency on a package. ... For example, including /// in a declaration file declares that this file uses names declared in @types/node/index.d.ts;

今回の例だと、/// <reference types="react-scripts" />とあるので、react-app-env.d.ts内で node_modules/react-scripts/lib/react-app.d.tsで宣言されている名前を使用することを表しています。

この記事の最初に記述したようなコードと同じようなことをnode_modules/react-script/lib/react-app.d.tsの中でも行っていることがわかります。

declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV: 'development' | 'production' | 'test';
    readonly PUBLIC_URL: string;
  }
}

このために、vscodeなどのエディターでは最初から型補完が効いているみたいですね。

f:id:hagaahiro:20220106224743p:plain