tsconfig.jsonのstrict
概要
strict
を true
にすると、以下のオプションが true
になり、より厳密に型がチェックされます。
noImplicitAny
strictNullChecks
strictFunctionTypes
strictBindCallApply
strictPropertyInitialization
noImplicitThis
useUnknownInCatchVariables
alwaysStrict
create next-app
で作成した Next.js プロジェクトで strict
は false
が設定されています。しかし、より厳密な型チェックをするためには strict
に true
を設定し、厳密な型チェックを強制することを強くお勧めします。
{
"compilerOptions": {
"strict": true,
},
}
公式の説明はこちらです。
本記事では動作確認を行います。
事前環境の構築
作業するための 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
の内容を以下のように上書きします。
@tailwind base;
@tailwind components;
@tailwind utilities;
初期ページ
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
を上書きします。
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
)を上書きします。
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
)を上書きします。
{
"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"]
}
スクリプトを追加
型チェックのスクリプトを追加します。
{
"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
strictNullChecks
strictFunctionTypes
strictBindCallApply
strictPropertyInitialization
noImplicitThis
useUnknownInCatchVariables
alwaysStrict
noImplicitAny
noImplicitAny
を true
にすると、暗黙的な any
型が許可されなくなります。よって、以下のように value
の方が暗黙的に any
となるコードはエラーになります。
export const add = (value) => {
return value + 1;
};
明確に型を指定する必要はありますが、暗黙的な any
を許容しないことで、型安全性が向上し、予測できないバグを減らすことができます。
動作を把握するために noImplicitAny
を無効化しておきます。
{
"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
export const add = (value) => {
return value + 1;
};
型チェックしますが、noImplicitAny
が false
で、暗黙的な any
型が許可されているためエラーは出ません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: noImplicitAnyを無効化"
"noImplicitAny": false
を削除し、strict
により noImplicitAny
を有効化します。
{
"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
noImplicitAny
を true
にすると、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
の型を指定します。
-export const add = (value) => {
+export const add = (value:number) => {
return value + 1;
};
型チェックします。エラーは出ません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: noImplicitAnyを有効化"
strictNullChecks
strictNullChecks
を false
にすると、null
と undefined
は言語により事実上無視されます。 例えば以下のようなコードが事実上無視され、実行時の予期しないエラーの原因となります。
const value1: number = null;
const value2: number = undefined;
console.log(value1+value2);
strictNullChecks
が true
のとき、null
や undefined
による予期せぬエラーをコンパイル時に検知が可能となります。
null
や undefined
を厳密に型チェックすることで、予測できないバグを減らすことができます。
動作を把握するために strictNullChecks
を無効化しておきます。
{
"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
const value1: number = null;
const value2: number = undefined;
console.log(value1+value2);
型チェックしますが、strictNullChecks
が false
で、null
と undefined
は言語により事実上無視されるためエラーは出ません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: strictNullChecksを無効化"
"strictNullChecks": false
を削除し、strict
により strictNullChecks
を有効化します。
{
"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
問題があったコードは修正します。
-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
strictFunctionTypes
を true
にすると、関数の引数の型の変化を厳しくチェックします。より詳しく説明します。
例えば以下のようなコードがあります。
// 関数に対してより範囲の広い引数の型の代入
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 つです。
-
func1
とfunc2
はどちらも関数です。 -
func1
はより範囲の広い型を代入しています。func1 の引数の型はnumber
ですが、number | null
,number | null | undefined
を代入しています。 -
func2
はより範囲の狭い型を代入しています。func2 の引数の型はnumber | null | undefined
ですが、number | null
、number
を代入しています。
strictFunctionTypes
を true
にするとより範囲の広い型を代入できますが、以下のように狭い型を代入するコードはエラーになります。
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
を無効化しておきます。
{
"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
// 関数に対してより範囲の広い引数の型の代入
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) => {}
};
型チェックしますが、strictFunctionTypes
が false
によりエラーは出ません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: strictFunctionTypesを無効化"
"strictFunctionTypes": false
を削除し、strict
により strictFunctionTypes
を有効化します。
{
"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
strictFunctionTypes
を true
にすると, func2
のように関数に対してより狭い引数の型を代入するとエラーになります。一方で、m1
と m2
はより狭い方を代入していますが、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
問題があったコードはコメントアウトしておきます。
// 関数に対してより範囲の広い引数の型の代入
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
strictBindCallApply
を false
にすると、bind
、call
、apply
の引数の型をチェックしません。よって、strictBindCallApply
が false
の時、以下のように期待する型と入力された型が異なるコードはエラーになりません。
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のため型エラーがある
strictBindCallApply
は true
にすると、bind
、call
、apply
の引数の型を厳しくチェックされ、上記のようなコードがエラーになります。
bind
、call
、apply
の使用頻度は高くありませんが、strictBindCallApply
を true
にする弊害はあまりません。
動作を把握するために strictBindCallApply
を無効化しておきます。
{
"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
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
を有効化します。
{
"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
strictBindCallApply
を true
にすると, 型チェックが行われ、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
問題があったコードは無効化しておきます。
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
strictPropertyInitialization
を true
にすると、Class プロパティが宣言されているがコンストラクターで値が設定されていないときに、エラーとして検知できます。よって、以下のようなコードはエラーとなります。
class User {
name: string;
}
そして、以下のようなコードはコンストラクターで値が設定されているためエラーになりません。
class User {
name: string;
constructor() {
this.name = "山田 太郎";
}
}
プロパティの値の初期化を担保しておくことで、予測できないバグを減らすことができます。
動作を把握するために strictPropertyInitialization
を無効化しておきます。
{
"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
class User {
name: string;
}
型チェックします。何もエラーは出ません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: strictPropertyInitializationを無効化"
"strictPropertyInitialization": false
を削除し、strict
により strictPropertyInitialization
を有効化します。
{
"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
strictPropertyInitialization
を true
にすると, 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
コンストラクターを追加しプロパティに値を設定します。
class User {
name: string;
+ constructor() {
+ this.name = "山田 太郎";
+ }
}
型チェックします。エラーが出ません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: strictPropertyInitializationを有効化"
noImplicitThis
noImplicitThis
を true
にすると、暗黙的に any 型となる this 式でエラーを発生させます。言葉だとわかりにくいですが、以下のコードですと、return this.width * this.height;
の this
がエラーになります。
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
を無効化しておきます。
{
"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
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
を有効化します。
{
"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
noImplicitThis
を true
にすると, 暗黙的に 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 に対して型を付与し修正します。
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
useUnknownInCatchVariables
を true
にすると、例外捕捉 catch(err)
の err
が unknown
型になります。よって以下のようなコードはエラーとなります。
例外捕捉の型安全性が向上し、予期せぬバグを回避できます。
try {
// ...
} catch (err) { // err は unknown 型
console.log(err.message);
}
以下のようなコードはエラーになりません。
try {
// ...
} catch (err) {
if (err instanceof Error) {
console.log(err.message);
}
}
useUnknownInCatchVariables
は false
にすると、例外捕捉 catch(err)
の err
が any
型になります。よって以下のようなコードはエラーとなりません。
try {
// ...
} catch (err) { // err は any 型
console.log(err.message);
}
動作を把握するために useUnknownInCatchVariables
を無効化しておきます。
{
"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
try {
// ...
} catch (err) {
console.log(err.message);
}
型チェックします。err
は any
型と解約され、エラーになりません。
$ pnpm run typecheck
コミットします。
$ git add .
$ git commit -m "feat: useUnknownInCatchVariablesを無効化"
"useUnknownInCatchVariables": false
を削除し、strict
により useUnknownInCatchVariables
を有効化します。
{
"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
useUnknownInCatchVariables
を true
にするとより,例外捕捉 catch(err)
の err
が unknown
型として扱われます。これにより、エラーが発生します。
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 に対して型を付与し修正します。
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
alwaysStrict
を true
にすると、ECMAScript の strict モードで解釈します。具体的には各ファイルの先頭に use strict
が付与されます。
Next.jsの観点から
create next-app
で作成した Next.js プロジェクトで strict
は false
が設定されています。しかし、より厳密な型チェックをするためには strict
に true
を設定し、厳密な型チェックを強制することを強くお勧めします。
まとめ
概要
strict
を true
にすると、以下のオプションが true
になり、より厳密に型がチェックされます。
noImplicitAny
strictNullChecks
strictFunctionTypes
strictBindCallApply
strictPropertyInitialization
noImplicitThis
useUnknownInCatchVariables
alwaysStrict
Next.jsの観点から
create next-app
で作成した Next.js プロジェクトで strict
は false
が設定されています。しかし、より厳密な型チェックをするためには strict
に true
を設定し、厳密な型チェックを強制することを強くお勧めします。
{
"compilerOptions": {
"strict": true,
},
}
補足
公式の説明はこちらです。
以下が作業リポジトリです。
Discussion