Skip to content

React useRefでcurrentが読み取り専用になる問題の解決策

問題の説明

ReactでuseRefフックを使用する際、次のようなTypeScriptエラーが発生することがあります:

typescript
Cannot assign to 'current' because it is a read-only property

このエラーは主に、useRefの型定義が不適切な場合に発生します。特に以下のようなコードで顕著です:

tsx
const unblockRef = useRef<() => void | null>(null);

// 後に実行するとエラーが発生
unblockRef.current = null;

エラーが発生する原因

  1. 型定義の解釈ミス: () => void | nullという型は「voidまたはnullを返す関数」と解釈されます
  2. 読み取り専用プロパティ: この型定義だと、TypeScriptはcurrentを変更不可と判断
  3. 関数型の優先順位: TypeScriptの型演算子|の優先順位が原因で意図しない解釈が発生

なぜ「読み取り専用」になるのか?

ReactのuseRefには2種類の型が存在します:

  • RefObject: currentプロパティが読み取り専用(主にDOM参照用)
  • MutableRefObject: currentプロパティが変更可能(可変値保持用)

解決方法

方法1: 型定義を正確に括弧で囲む(推奨)

tsx
// 修正前(エラー発生)
const unblockRef = useRef<() => void | null>(null);

// 修正後(動作する)
const unblockRef = useRef<(() => void) | null>(null);

変更点の解説:

  • 関数型() => voidを括弧()で明確に囲む
  • | nullで「関数またはnull」という意図を正確に表現
  • TypeScriptがMutableRefObjectを正しく推論するようになる
tsx
// エラーが発生するパターン
const unblockRef = useRef<() => void | null>(null);

useEffect(() => {
  unblockRef.current = null; // Error!
}, []);
tsx
// 正しい型定義で動作
const unblockRef = useRef<(() => void) | null>(null);

useEffect(() => {
  unblockRef.current = null; // OK
  unblockRef.current = () => console.log("Fixed!"); // OK
}, []);

方法2: MutableRefObjectを明示的に指定する

型推論に依存せず、直接MutableRefObject型を宣言する方法もあります:

tsx
import { MutableRefObject, useRef } from 'react';

const unblockRef: MutableRefObject<(() => void) | null> = useRef(null);

どちらの方法を選ぶべきか?

  • 一般的には方法1が簡潔で推奨されます
  • 複雑な型の場合は方法2で明示的に型指定すると可読性が向上

技術的な背景

TypeScriptの型解釈の仕組み

下記の型定義は、TypeScriptによって異なる方法で解釈されます:

ts
() => void | null   // 解釈: () => (void | null)【間違い】
(() => void) | null // 解釈: (関数型) または null【正しい】

Reactのrefオブジェクトの種類

currentの性質主な用途
RefObject読み取り専用DOM要素の参照
MutableRefObject変更可能コンポーネント内の可変値保持

型推論の自動決定ルール

useRefの初期値と型パラメータからReactが自動決定:

  • null許容型 → MutableRefObjectと推論
  • 非許容型 → RefObjectと推論

よくある関連エラーと対処法

関数以外の型での同様のエラー

文字列など他の型でも発生する可能性があります:

tsx
// 問題のある例
const countRef = useRef<number>(0);

// 修正方法
const countRef = useRef<number | null>(null);

DOM参照での注意点

DOM要素を参照する場合はRefObject(読み取り専用)が適切です:

tsx
// DOM参照では変更不可であるべき
const divRef = useRef<HTMLDivElement>(null);

// 以下は実行すべきでない(Reactが管理するDOMを直接操作)
divRef.current = anotherElement; // 非推奨

複合型の正しい定義例

tsx
// 複数の型を使う場合の適切な定義
const multiRef = useRef<string | (() => void) | null>(null);

// 更新例
multiRef.current = "text"; // OK
multiRef.current = () => alert("Hello"); // OK
multiRef.current = null; // OK

まとめ

useRefcurrentプロパティへの代入エラーが発生する主な原因と解決策は:

  1. 型定義が不正確関数型は( )で明確に囲む
    ts
    useRef<(() => void) | null>(null)
  2. 読み取り専用プロパティ問題null許容型でMutableRefObjectを推論させる

TypeScriptの型演算子の優先順位とReactのrefの特性を理解することで、このようなエラーを効果的に回避できます。