TL;DR: すぐに使える解決策
納期が迫っていますか?コードをすぐに動かすには、以下の3つの方法のいずれかを使用してください。
- ソースを編集する: インターフェースまたは型定義を探し、
readonlyキーワードを削除します。 - 強制的に変更する: 型アサーションを使用してチェックをバイパスします:
(myObject as any).property = 'new value'; - as constを外す: オブジェクトを
as constで定義している場合は、そのサフィックスを削除してオブジェクトを再び変更可能(mutable)にします。
なぜTypeScriptは編集をブロックするのか
このエラーは、コンパイラが明示的に不変(immutable)としてマークされた値を変更しようとする試みを検出したときに発生します。TypeScriptは、予期しない状態変化によるバグを防ぐためにこれを行います。通常、原因は次の3つのシナリオのいずれかです。
- インターフェースまたは型のプロパティに
readonlyプレフィックスが付いている。 - オブジェクトがConstアサーション(
as const)で宣言されており、すべてのフィールドが不変になっている。 - コンストラクタ以外で
readonlyとマークされたクラスプロパティを更新しようとしている。
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 101, name: 'Alice' };
// ❌ エラー: Cannot assign to 'id' because it is a read-only property.
user.id = 202;
エラーを解決する4つの方法
1. インターフェース定義を変更する
最も簡単な方法は、プロパティの変更を許可することです。retryCountやlastLoginタイムスタンプのように、ロジック上で値を更新する必要がある場合、そもそもreadonlyフラグを付けるべきではありません。
// 変更前: 不変(Immutable)
interface AppConfig {
readonly port: number;
}
// 変更後: 可変(Mutable)
interface AppConfig {
port: number;
}
2. 型アサーションを使用する(脱出ハッチ)
ソースコードを編集できないサードパーティ製ライブラリを使用している場合に、この問題に遭遇することがあります。このような場合、オブジェクトをキャストすることで、TypeScriptに「自分を信じて」と伝えることができます。これは、特定のデータ状態をモックする必要があるユニットテストでよく使われます。
interface Profile {
readonly email: string;
}
const userProfile: Profile = { email: 'test@example.com' };
// オプションA: anyにキャストする
(userProfile as any).email = 'new@example.com';
// オプションB: Writableユーティリティ型を作成する
type Writable<T> = { -readonly [P in keyof T]: T[P] };
(userProfile as Writable<Profile>).email = 'admin@internal.com';
3. "as const" アサーションを削除する
as constを使用するのはリテラル型を作成する便利な方法ですが、ネストされたすべてのプロパティをロックしてしまいます。theme設定を'dark'から'light'に変更する必要がある場合は、オブジェクト全体にconstアサーションを使用しないでください。
// このオブジェクトは100%ロックされています
const UI_SETTINGS = {
theme: 'dark',
sidebarWidth: 250
} as const;
// UI_SETTINGS.theme = 'light'; // ❌ 失敗します
// 解決策: 標準のオブジェクトまたは特定の型を使用する
const UI_SETTINGS = {
theme: 'dark',
sidebarWidth: 250
};
4. Readonly配列を変換する
配列がreadonly string[]として定義されている場合、push()、pop()、splice()などのメソッドはこのエラーを引き起こします。これらのメソッドは元の配列を変更するため、TypeScriptはそれらをブロックします。これを修正するには、スプレッド演算子を使用して変更可能なコピーを作成します。
const roles: readonly string[] = ['admin', 'editor'];
// roles.push('viewer'); // ❌ エラー
// 解決策: 新しい配列に展開(スプレッド)する
const activeRoles = [...roles];
activeRoles.push('viewer'); // ✅ 正常に動作します
修正内容を再確認する方法
修正を適用したら、次の3つの手順で確認してください。
- 赤い波線を探す: VS Codeから赤い波線の波線がすぐに消えるはずです。
- ビルドを実行する: ターミナルを起動し、
npx tscを実行します。エラーなしで終了すれば、その修正は型安全です。 - JSを検証する:
as anyを使用した場合は、TypeScriptが実行時に助けてくれないことを忘れないでください。ログやブラウザのコンソールをチェックして、値が実際に更新されたことを確認してください。
プロのヒント: 可能な限り不変(Immutable)を保つ
エラーを消すためだけにreadonlyを削除しないでください。不変性はデバッグを容易にします。ReactやReduxを使用している場合は、読み取り専用プロパティを強制的に変更するのではなく、常に新しいオブジェクトを返してください:const updated = { ...oldObject, status: 'active' };。これにより、データフローの予測可能性が維持され、副作用を避けることができます。

