🍟

tsconfig.jsonのstrict

2024/03/23に公開

概要

stricttrue にすると、以下のオプションが true になり、より厳密に型がチェックされます。

create next-app で作成した Next.js プロジェクトで strictfalse が設定されています。しかし、より厳密な型チェックをするためには stricttrue を設定し、厳密な型チェックを強制することを強くお勧めします。

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
  },
}

公式の説明はこちらです。

https://www.typescriptlang.org/ja/tsconfig#strict

本記事では動作確認を行います。

事前環境の構築

作業するための Next.js プロジェクトを作成します。長いので、折り畳んでおきます。

新規プロジェクト作成と初期環境構築の手順詳細

プロジェクトを作成

create next-app@latestでプロジェクトを作成します。

$ pnpm create next-app@latest next-tsconfig-strict --typescript --eslint --import-alias "@/*" --src-dir --use-pnpm --tailwind --app
$ cd next-tsconfig-strict

Peer Dependenciesの警告を解消

Peer dependenciesの警告が出ている場合は、pnpm installを実行し、警告を解消します。

 WARN  Issues with peer dependencies found
.
├─┬ autoprefixer 10.0.1
│ └── ✕ unmet peer postcss@^8.1.0: found 8.0.0
├─┬ tailwindcss 3.3.0
│ ├── ✕ unmet peer postcss@^8.0.9: found 8.0.0
│ ├─┬ postcss-js 4.0.1
│ │ └── ✕ unmet peer postcss@^8.4.21: found 8.0.0
│ ├─┬ postcss-load-config 3.1.4
│ │ └── ✕ unmet peer postcss@>=8.0.9: found 8.0.0
│ └─┬ postcss-nested 6.0.0
│   └── ✕ unmet peer postcss@^8.2.14: found 8.0.0
└─┬ next 14.0.4
  ├── ✕ unmet peer react@^18.2.0: found 18.0.0
  └── ✕ unmet peer react-dom@^18.2.0: found 18.0.0

以下を実行することで警告が解消されます。

$ pnpm i -D postcss@latest react@^18.2.0 react-dom@^18.2.0

不要な設定を削除し、プロジェクトを初期化します。

styles

CSSなどを管理するstylesディレクトリを作成します。globals.cssを移動します。

$ mkdir -p src/styles
$ mv src/app/globals.css src/styles/globals.css

globals.cssの内容を以下のように上書きします。

src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

初期ページ

app/page.tsxを上書きします。

src/app/page.tsx
import { type FC } from "react";

const Home: FC = () => {
  return (
    <div className="">
      <div className="text-lg font-bold">Home</div>
      <div>
        <span className="text-blue-500">Hello</span>
        <span className="text-red-500">World</span>
      </div>
    </div>
  );
};

export default Home;

レイアウト

app/layout.tsxを上書きします。

src/app/layout.tsx
import "@/styles/globals.css";
import { type FC } from "react";
type RootLayoutProps = {
  children: React.ReactNode;
};

export const metadata = {
  title: "Sample",
  description: "Generated by create next app",
};

const RootLayout: FC<RootLayoutProps> = (props) => {
  return (
    <html lang="ja">
      <body className="">{props.children}</body>
    </html>
  );
};

export default RootLayout;

TailwindCSSの設定

TailwindCSSの設定(tailwind.config.ts)を上書きします。

tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  plugins: [],
}
export default config

TypeScriptの設定

TypeScriptの設定(tsconfig.json)を上書きします。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

スクリプトを追加

型チェックのスクリプトを追加します。

package.json
{
  "name": "next-tsconfig-strict",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
+   "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "next": "14.1.4"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.4",
    "postcss": "^8.4.37",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

動作確認

ローカルで動作確認します。

$ pnpm run dev

コミットして作業結果を保存しておきます。

$ git add .
$ git commit -m "feat:新規にプロジェクトを作成し, 作業環境を構築"

動作確認

以下のそれぞれ動作を確認していきます。

noImplicitAny

noImplicitAnytrue にすると、暗黙的な any 型が許可されなくなります。よって、以下のように value の方が暗黙的に any となるコードはエラーになります。

export const add = (value) => {
  return value + 1;
};

明確に型を指定する必要はありますが、暗黙的な any を許容しないことで、型安全性が向上し、予測できないバグを減らすことができます。

動作を把握するために noImplicitAny を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "noImplicitAny": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

暗黙的に型が any となる変数を含むコードを作成します。name は暗黙的に any となります。

$ mkdir src/lib
$ touch src/lib/no-implicit-any.ts
src/lib/no-implicit-any.ts
export const add = (value) => {
  return value + 1;
};

型チェックしますが、noImplicitAnyfalse で、暗黙的な any 型が許可されているためエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: noImplicitAnyを無効化"

"noImplicitAny": false を削除し、strict により noImplicitAny を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "noImplicitAny": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

noImplicitAnytrue にすると、name の型が暗黙的に any のためエラーが出ます。

src/lib/no-implicit-any.ts:1:21 - error TS7006: Parameter 'value' implicitly has an 'any' type.

1 export const add = (value) => {
                      ~~~~~

Found 1 error in src/lib/no-implicit-any.ts:1

name に明示的に number の型を指定します。

src/lib/no-implicit-any.ts
-export const add = (value) => {
+export const add = (value:number) => {

  return value + 1;
};

型チェックします。エラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: noImplicitAnyを有効化"

strictNullChecks

strictNullChecksfalse にすると、nullundefined は言語により事実上無視されます。 例えば以下のようなコードが事実上無視され、実行時の予期しないエラーの原因となります。

const value1: number = null;
const value2: number = undefined;

console.log(value1+value2);

strictNullCheckstrue のとき、nullundefined による予期せぬエラーをコンパイル時に検知が可能となります。

nullundefined を厳密に型チェックすることで、予測できないバグを減らすことができます。

動作を把握するために strictNullChecks を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "strictNullChecks": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

非 null 型への null の代入、非 undefined 型への undefined の代入を含むコードを作成します。

$ mkdir src/lib
$ touch src/lib/strict-null-checks.ts
src/lib/strict-null-checks.ts
const value1: number = null;
const value2: number = undefined;

console.log(value1+value2);

型チェックしますが、strictNullChecksfalse で、nullundefined は言語により事実上無視されるためエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictNullChecksを無効化"

"strictNullChecks": false を削除し、strict により strictNullChecks を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "strictNullChecks": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

非 null 型への null の代入、非 undefined 型への undefined の代入によってエラーが出ます。

src/lib/strict-null-checks.ts:1:7 - error TS2322: Type 'null' is not assignable to type 'number'.

1 const value1: number = null;
        ~~~~~~

src/lib/strict-null-checks.ts:2:7 - error TS2322: Type 'undefined' is not assignable to type 'number'.

2 const value2: number = undefined;
        ~~~~~~

Found 2 errors in the same file, starting at: src/lib/strict-null-checks.ts:1

問題があったコードは修正します。

src/lib/strict-null-checks.ts
-const value1: number = null;
-const value2: number = undefined;
+const value1: number = 1;
+const value2: number = 2;

console.log(value1+value2);

型チェックします。エラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictNullChecksを有効化"

strictFunctionTypes

strictFunctionTypestrue にすると、関数の引数の型の変化を厳しくチェックします。より詳しく説明します。

例えば以下のようなコードがあります。

// 関数に対してより範囲の広い引数の型の代入
let func1: (n: number) => any;
func1 = (n: number | null) => {};
func1 = (n: number | null | undefined) => {};

// 関数に対してより範囲の狭い引数の型の代入
let func2: (n: number | null | undefined) => any;
func2 = (n: number | null) => {};
func2 = (n: number) => {};

ポイントは 3 つです。

  • func1func2 はどちらも関数です。
  • func1 はより範囲の広い型を代入しています。func1 の引数の型は number ですが、number | null, number | null | undefined を代入しています。
  • func2 はより範囲の狭い型を代入しています。func2 の引数の型は number | null | undefined ですが、number | nullnumber を代入しています。

strictFunctionTypestrue にするとより範囲の広い型を代入できますが、以下のように狭い型を代入するコードはエラーになります。

let func2: (n: number | null | undefined) => any;
func2 = (n: number | null) => {};
func2 = (n: number) => {};

注意点として、strictFunctionTypes はメソッドで記述された関数の引数には適応されません。よってメソッドで記述された関数により範囲の広い型を代入してもエラーになりません。

// メソッドに対してより範囲の狭い引数の型の代入
type MethodType = {
  func3(n: number | null | undefined): any;
};
const m1: MethodType = {
  func3: (n: number | null) => {}
};
const m2: MethodType = {
  func3: (n: number) => {}
};

では動作確認をします。

動作を把握するために strictFunctionTypes を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "strictFunctionTypes": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

コードを作成します。

$ mkdir src/lib
$ touch src/lib/strict-function-types.ts
src/lib/strict-function-types.ts
// 関数に対してより範囲の広い引数の型の代入
let func1: (n: number) => any; 
func1 = (n: number | null) => {}; 
func1 = (n: number | null | undefined) => {};

// 関数に対してより範囲の狭い引数の型の代入
let func2: (n: number | null | undefined) => any;
func2 = (n: number | null) => {};
func2 = (n: number) => {};

// メソッドに対してより範囲の狭い引数の型の代入
type MethodType = {
  func3(n: number | null | undefined): any;
};
const m1: MethodType = {
  func3: (n: number | null) => {}
};
const m2: MethodType = {
  func3: (n: number) => {}
};

型チェックしますが、strictFunctionTypesfalse によりエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictFunctionTypesを無効化"

"strictFunctionTypes": false を削除し、strict により strictFunctionTypes を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "strictFunctionTypes": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

strictFunctionTypestrue にすると, func2 のように関数に対してより狭い引数の型を代入するとエラーになります。一方で、m1m2 はより狭い方を代入していますが、func2 のように関数型ではなく、メソッド型のためエラーになりません。

src/lib/strict-function-types.ts:8:1 - error TS2322: Type '(n: number | null) => void' is not assignable to type '(n: number | null | undefined) => any'.
  Types of parameters 'n' and 'n' are incompatible.
    Type 'number | null | undefined' is not assignable to type 'number | null'.
      Type 'undefined' is not assignable to type 'number | null'.

8 func2 = (n: number | null) => {};
  ~~~~~

src/lib/strict-function-types.ts:9:1 - error TS2322: Type '(n: number) => void' is not assignable to type '(n: number | null | undefined) => any'.
  Types of parameters 'n' and 'n' are incompatible.
    Type 'number | null | undefined' is not assignable to type 'number'.
      Type 'undefined' is not assignable to type 'number'.

9 func2 = (n: number) => {};
  ~~~~~

Found 2 errors in the same file, starting at: src/lib/strict-function-types.ts:8

問題があったコードはコメントアウトしておきます。

src/lib/strict-function-types.ts
// 関数に対してより範囲の広い引数の型の代入
let func1: (n: number) => any; 
func1 = (n: number | null) => {}; 
func1 = (n: number | null | undefined) => {};

// 関数に対してより範囲の狭い引数の型の代入
let func2: (n: number | null | undefined) => any;
-func2 = (n: number | null) => {};
-func2 = (n: number) => {};
+// func2 = (n: number | null) => {};
+// func2 = (n: number) => {};

// メソッドに対してより範囲の狭い引数の型の代入
type MethodType = {
  func3(n: number | null | undefined): any;
};
const m1: MethodType = {
  func3: (n: number | null) => {}
};
const m2: MethodType = {
  func3: (n: number) => {}
};

型チェックします。エラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictFunctionTypesを有効化"

strictBindCallApply

strictBindCallApplyfalse にすると、bindcallapply の引数の型をチェックしません。よって、strictBindCallApplyfalse の時、以下のように期待する型と入力された型が異なるコードはエラーになりません。

src/lib/strict-bind-call-apply.ts
function fn(x: string) {
  x.toUpperCase();
}
const x1 = fn.call(undefined, "hello");   // 引数の型がstringで問題はない
const x2 = fn.call(undefined, true);      // 引数の型がstringでなく、booleanのため型エラーがある
const x3 = fn.call(undefined, 123456789); // 引数の型がstringでなく、numberのため型エラーがある

strictBindCallApplytrue にすると、bindcallapply の引数の型を厳しくチェックされ、上記のようなコードがエラーになります。

bindcallapply の使用頻度は高くありませんが、strictBindCallApplytrue にする弊害はあまりません。

動作を把握するために strictBindCallApply を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "strictBindCallApply": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

コードを作成します。今回は、call を利用します。

$ mkdir src/lib
$ touch src/lib/strict-bind-call-apply.ts
src/lib/strict-bind-call-apply.ts
function fn(x: string) {
  x.toUpperCase();
}
const x1 = fn.call(undefined, "hello");   // 引数の型がstringで問題はない
const x2 = fn.call(undefined, true);      // 引数の型がstringでなく、booleanのため型エラーがある
const x3 = fn.call(undefined, 123456789); // 引数の型がstringでなく、numberのため型エラーがある

型チェックします。何もエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictBindCallApplyを無効化"

"strictBindCallApply": false を削除し、strict により strictBindCallApply を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "strictBindCallApply": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

strictBindCallApplytrue にすると, 型チェックが行われ、call の引数の型チェックが行われ、型が違うためエラーとなります。

src/lib/strict-bind-call-apply.ts:5:31 - error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string'.

5 const x2 = fn.call(undefined, true);
                                ~~~~

src/lib/strict-bind-call-apply.ts:6:31 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

6 const x3 = fn.call(undefined, 123456789);
                                ~~~~~~~~~


Found 2 errors in the same file, starting at: src/lib/strict-bind-call-apply.ts:5

問題があったコードは無効化しておきます。

src/lib/strict-bind-call-apply.ts
function fn(x: string) {
  x.toUpperCase();
}
const x1 = fn.call(undefined, "hello");
-const x2 = fn.call(undefined, true);
-const x3 = fn.call(undefined, 123456789);
+// const x2 = fn.call(undefined, true);
+// const x3 = fn.call(undefined, 123456789);

型チェックします。何もエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictBindCallApplyを有効化"

strictPropertyInitialization

strictPropertyInitializationtrue にすると、Class プロパティが宣言されているがコンストラクターで値が設定されていないときに、エラーとして検知できます。よって、以下のようなコードはエラーとなります。

class User {
  name: string;
}

そして、以下のようなコードはコンストラクターで値が設定されているためエラーになりません。

class User {
  name: string;

  constructor() {
    this.name = "山田 太郎";
  }
}

プロパティの値の初期化を担保しておくことで、予測できないバグを減らすことができます。

動作を把握するために strictPropertyInitialization を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "strictPropertyInitialization": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

Class プロパティが宣言されているがコンストラクターで値が設定されていないコードを作成します。

$ mkdir src/lib
$ touch src/lib/strict-property-initialization.ts
src/lib/strict-property-initialization.ts
class User {
  name: string;
}

型チェックします。何もエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictPropertyInitializationを無効化"

"strictPropertyInitialization": false を削除し、strict により strictPropertyInitialization を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "strictPropertyInitialization": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

strictPropertyInitializationtrue にすると, Class プロパティが宣言されているがコンストラクターで値が設定されていないとエラーになります。

src/lib/strict-property-initialization.ts:2:3 - error TS2564: Property 'name' has no initializer and is not definitely assigned in the constructor.

2   name: string;
    ~~~~

Found 1 error in src/lib/strict-property-initialization.ts:2

コンストラクターを追加しプロパティに値を設定します。

src/lib/strict-bind-call-apply.ts
class User {
  name: string;

+ constructor() {
+   this.name = "山田 太郎";
+ }
}

型チェックします。エラーが出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: strictPropertyInitializationを有効化"

noImplicitThis

noImplicitThistrue にすると、暗黙的に any 型となる this 式でエラーを発生させます。言葉だとわかりにくいですが、以下のコードですと、return this.width * this.height;this がエラーになります。

src/lib/no-implicit-this.ts
class Rectangle {
  width: number;
  height: number;
 
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
 
  getAreaFunction() {
    return function () {
      return this.width * this.height; // このthisがエラーになります。
    };
  }
}

this による予測できないバグを減らすことができます。

動作を把握するために noImplicitThis を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "noImplicitThis": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

暗黙的に any 型となる this 式を含むコードを作成します。

$ mkdir src/lib
$ touch src/lib/no-implicit-this.ts
src/lib/no-implicit-this.ts
class Rectangle {
  width: number;
  height: number;
 
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
 
  getAreaFunction() {
    return function () {
      return this.width * this.height;
    };
  }
}

型チェックします。何もエラーは出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: noImplicitThisを無効化"

"noImplicitThis": false を削除し、strict により noImplicitThis を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "noImplicitThis": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

noImplicitThistrue にすると, 暗黙的に any 型となる this 式でエラーを発生するようになります。

src/lib/no-implicit-this.ts:12:14 - error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.

12       return this.width * this.height;
                ~~~~

  src/lib/no-implicit-this.ts:11:12
    11     return function () {
                  ~~~~~~~~
    An outer value of 'this' is shadowed by this container.

src/lib/no-implicit-this.ts:12:27 - error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.

12       return this.width * this.height;
                             ~~~~

  src/lib/no-implicit-this.ts:11:12
    11     return function () {
                  ~~~~~~~~
    An outer value of 'this' is shadowed by this container.


Found 2 errors in the same file, starting at: src/lib/no-implicit-this.ts:12

this に対して型を付与し修正します。

src/lib/no-implicit-this.ts
class Rectangle {
  width: number;
  height: number;
 
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
 
  getAreaFunction() {
-   return function () {
+   return function (this: Rectangle) {
      return this.width * this.height;
    };
  }
}

型チェックします。エラーが出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: noImplicitThisを有効化"

useUnknownInCatchVariables

useUnknownInCatchVariablestrue にすると、例外捕捉 catch(err)errunknown 型になります。よって以下のようなコードはエラーとなります。

例外捕捉の型安全性が向上し、予期せぬバグを回避できます。

try {
  // ...
} catch (err) {  // err は unknown 型
  console.log(err.message);
}

以下のようなコードはエラーになりません。

try {
  // ...
} catch (err) {
  if (err instanceof Error) {
    console.log(err.message);
  }
}

useUnknownInCatchVariablesfalse にすると、例外捕捉 catch(err)errany 型になります。よって以下のようなコードはエラーとなりません。

try {
  // ...
} catch (err) {  // err は any 型
  console.log(err.message);
}

動作を把握するために useUnknownInCatchVariables を無効化しておきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
+   "useUnknownInCatchVariables": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

例外捕捉 catch(err)err が暗黙的に any 型となるコードを作成します。

$ mkdir src/lib
$ touch src/lib/use-unknown-in-catch-variables.ts
src/lib/use-unknown-in-catch-variables.ts
try {
  // ...
} catch (err) {
  console.log(err.message);
}

型チェックします。errany 型と解約され、エラーになりません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: useUnknownInCatchVariablesを無効化"

"useUnknownInCatchVariables": false を削除し、strict により useUnknownInCatchVariables を有効化します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
-   "useUnknownInCatchVariables": false,
    "plugins": [
      {
        "name": "next"
      }
    ],
  },
  "include": [
    ".eslintrc.cjs",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.cjs",
    "**/*.mjs",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

型チェックします。

$ pnpm run typecheck

useUnknownInCatchVariablestrue にするとより,例外捕捉 catch(err)errunknown 型として扱われます。これにより、エラーが発生します。

src/lib/use-unknown-in-catch-variables.ts:4:15 - error TS18046: 'err' is of type 'unknown'.

4   console.log(err.message);
                ~~~

Found 1 error in src/lib/use-unknown-in-catch-variables.ts:4

this に対して型を付与し修正します。

src/lib/use-unknown-in-catch-variables.ts
try {
  // ...
} catch (err) {
- console.log(err.message);
+ if (err instanceof Error) {
+   console.log(err.message);
+ }
}

型チェックします。エラーが出ません。

$ pnpm run typecheck

コミットします。

$ git add .
$ git commit -m "feat: useUnknownInCatchVariablesを有効化"

alwaysStrict

alwaysStricttrue にすると、ECMAScript の strict モードで解釈します。具体的には各ファイルの先頭に use strict が付与されます。

Next.jsの観点から

create next-app で作成した Next.js プロジェクトで strictfalse が設定されています。しかし、より厳密な型チェックをするためには stricttrue を設定し、厳密な型チェックを強制することを強くお勧めします。

まとめ

概要

stricttrue にすると、以下のオプションが true になり、より厳密に型がチェックされます。

Next.jsの観点から

create next-app で作成した Next.js プロジェクトで strictfalse が設定されています。しかし、より厳密な型チェックをするためには stricttrue を設定し、厳密な型チェックを強制することを強くお勧めします。

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
  },
}

補足

公式の説明はこちらです。

https://www.typescriptlang.org/ja/tsconfig#strict

以下が作業リポジトリです。

https://github.com/hayato94087/next-tsconfig-strict

Discussion