React + TypeScript: useRefの3つの型指定と初期値の使い方

ReactとTypeScriptの使い方がまとめられた「React + TypeScript Cheatsheets」の「[useRef](https://github.com/typescript-cheatsheets/react#useref)」の情報にもとづいて、このフックの型づけと初期値の与え方について3つの定めを解説します。[`useRef`](https://ja.reactjs.org/docs/hooks-reference.html#useref)フックの型には、試しやすいように`number`を用いました。

```react
const nullRef = useRef<number>(null);
const nonNullRef = useRef<number>(null!);
const nullableRef = useRef<number | null>(null);
```


# `null`で初期化したとき
`useRef`フックの初期値に`null`を与えると、戻り値のrefオブジェクトは読み取り専用です。つまり、`current`プロパティは書き替えられません。

```react
// 読み取り専用。
const nullRef = useRef<number>(null);
// 読み取り専用プロパティであるため、'current' に代入することはできません。
// nullRef.current = 1;
```

読み取り専用にする典型は、`useRef`をHTML要素(`HTMLElement`)で型づけし、要素の`ref`属性に与える場合です(「[useRef](https://ja.reactjs.org/docs/hooks-reference.html#useref)」参照)。このとき、`current`プロパティは書き替えないでしょう。

たとえば、つぎのように`useRef`に`null`を渡して`HTMLDivElement`で型づけると、その`current`プロパティ(`div`)は`HTMLDivElement | null`のユニオン型で推論されます。すると、要素の参照として扱う前に、必ず`null`でないことを確かめなければなりません。そうしなければ、つぎのようなエラーが示されてしまうのです。

> オブジェクトは 'null' である可能性があります。

```react
function App() {
	const divRef = useRef<HTMLDivElement>(null);
	useEffect(() => {
		const div = divRef.current;
		if (!div) throw Error("divRef is not assigned");  // nullでないことを確かめる
		console.log(div.clientWidth);
	}, []);
	return (
		<div ref={divRef} />
	);
}
```

`useRef`の引数に定めるHTML要素の型は、`ref`を与える要素に応じて適切に絞り込んでください。あえて広めの型を定めると、エラーメッセージから適切な型が示されることもあります。

```react
function App() {
	// const inputRef = useRef<HTMLInputElement>(null);  // ←正しくは
	const inputRef = useRef<HTMLElement>(null);
	return (
		/* 型 'RefObject<HTMLElement>' を型 'LegacyRef<HTMLInputElement> | undefined' に割り当てることはできません。
		型 'RefObject<HTMLElement>' を型 'RefObject<HTMLInputElement>' に割り当てることはできません。
		型 'HTMLElement' には 型 'HTMLInputElement' からの次のプロパティがありません: accept, align, alt, autocomplete、49 など。 */
		<input ref={inputRef} type="text" />
	);
}
```


# 初期値`null`に非nullアサーション演算子`!`を添える
初期値の`null`に[非nullアサーション演算子(non-null assertion operator)](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator)`!`を添えて`null!`とすると、今度はrefオブジェクトの`current`プロパティが書き替えられます。もちろん、代入できるのは指定した型のみで、`null`は入れられません。

```react
// 指定した型の値で書き替えられる。nullは不可。
const nonNullRef = useRef<number>(null!);
console.log(nonNullRef.current);  // null
// 型 'null' を型 'number' に割り当てることはできません。
// nonNullRef.current = null;
nullableRef.current = 1;
console.log(nullableRef.current);  // 1
```

`null`でないことを型アサーションすれば、要素の`ref`に定めたときも、参照する前に`null`かどうか確かめる必要がありません。ただし、`null`でないことは、コードを書く側に責任が委ねられます。たとえば、要素に`ref`が与えられていないなど、参照が正しく得られなければ、ランタイムエラーになるでしょう。

```react
function App() {
	const divRef = useRef<HTMLDivElement>(null!);
	useEffect(() => {
		const div = divRef.current;
		// nullでないことは確かめなくてよい
		console.log(div.clientWidth);
	}, []);
	return (
		<div ref={divRef} />
	);
}
```

`useRef`フックの役割は、refオブジェクトを要素の`ref`属性に与えることだけではありません(「[React: useRefフックで状態変数のひとつ前の値や最新の値を得る](https://qiita.com/FumioNonaka/items/cbcd3e57d6ae5af43fa7)」参照)。初期値は`null`としつつ、`current`プロパティを書き替えたい場合に用います。


# 型指定に`null`を含める
`useRef`フックに書き替え可能の初期値`null`を渡したうえで、あえて`null`も代入値に含めたいときは、型指定に[ユニオン型](https://www.fumiononaka.com/Business/html5/FN2104003.html)(union type)`|`で加えます。

```react
// 型指定にnullを含める。
const nullableRef = useRef<number | null>(null);
nullableRef.current = 1;
console.log(nullableRef.current);  // 1
nullableRef.current = null;
console.log(nullableRef.current);  // null
```


# まとめ
`useRef`の3つの型指定と初期値の与え方で、`current`プロパティの扱いが少しずつ違ってくるということです。それらを意識して、用途に応じて使いましょう。

```react
// 読み取り専用。
const nullRef = useRef<number>(null);
// 指定した型の値で書き替えられる。nullは不可。
const nonNullRef = useRef<number>(null!);
// 型指定にnullを含める。
const nullableRef = useRef<number | null>(null);
```