28
Help us understand the problem. What are the problem?

posted at

updated at

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

ReactとTypeScriptの使い方がまとめられた「React + TypeScript Cheatsheets」の「useRef」の情報にもとづいて、このフックの型づけと初期値の与え方について3つの定めを解説します。useRefフックの型には、試しやすいようにnumberを用いました。

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

nullで初期化したとき

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

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

読み取り専用にする典型は、useRefをHTML要素(HTMLElement)で型づけし、要素のref属性に与える場合です(「useRef」参照)。このとき、currentプロパティは書き替えないでしょう。

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

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

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を与える要素に応じて適切に絞り込んでください。あえて広めの型を定めると、エラーメッセージから適切な型が示されることもあります。

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)!を添えてnull!とすると、今度はrefオブジェクトのcurrentプロパティが書き替えられます。もちろん、代入できるのは指定した型のみで、nullは入れられません。

// 指定した型の値で書き替えられる。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が与えられていないなど、参照が正しく得られなければ、ランタイムエラーになるでしょう。

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フックで状態変数のひとつ前の値や最新の値を得る」参照)。初期値はnullとしつつ、currentプロパティを書き替えたい場合に用います。

型指定にnullを含める

useRefフックに書き替え可能の初期値nullを渡したうえで、あえてnullも代入値に含めたいときは、型指定にユニオン型(union type)|で加えます。

// 型指定に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プロパティの扱いが少しずつ違ってくるということです。それらを意識して、用途に応じて使いましょう。

// 読み取り専用。
const nullRef = useRef<number>(null);
// 指定した型の値で書き替えられる。nullは不可。
const nonNullRef = useRef<number>(null!);
// 型指定にnullを含める。
const nullableRef = useRef<number | null>(null);
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
28
Help us understand the problem. What are the problem?