React useRefのパターン ~どのようなときに使う?~
useRefとは
公式サイトによれば、
useRef は、.current プロパティが渡された引数 (initialValue) に初期化されているミュータブルな ref オブジェクトを返します。返されるオブジェクトはコンポーネントの存在期間全体にわたって存在し続けます。
まずは ref のことを DOM にアクセスする手段として理解しているかもしれません。<div ref={myRef} /> のようにして React に ref オブジェクトを渡した場合、React は DOM ノードに変更があるたびに .current プロパティをその DOM ノードに設定します。
しかしながら useRef() は ref 属性で使うだけではなく、より便利に使えます。これはクラスでインスタンス変数を使うのと同様にして、あらゆる書き換え可能な値を保持しておくのに便利です。
と書いています。useRefの使いみちに2つあることがわかります。
- 生domを保持し、それを使い回す。生domを入れる変数のような使い方。
- レンダリングを起こさず値を保持する使い方(
useState
はsetState
するたびにレンダリングが走るので、レンダリングさせたくないときはuseRef
を使えばよい)。
1つ目は生domが提供するAPIを直接使いたいときに使います。例えばフォームのフォーカスはReactだけの機能では実現できないので、フォームのdomを取得して、focus
を実行します。
賛否両論あるとは思いますが、2つ目の使い方はuseState
とメモ化である程度回避できると思います。React wayに沿わない(宣言的よりも命令的なコードになりがち)というイメージから僕はあまり積極的にuseState
よりもuseRef
をこの方法で使おうとは思いません。ネットには2つ目の使い方の記事がたくさんあるのでそちらを参照してみてください。
今回は1つ目の使い方のサンプルコードをいくつか例示してみたいと思います。
Reactだけでは実現できない生domの機能を使う
以下が代表的な例でしょうか。
- フォームにフォーカス
- ある要素までスクロールする
フォームにフォーカス
これはinput
タグが提供するfocus
という機能を使います。
以下はコンポーネントがマウントされたときに、テキストエリアにフォーカスするサンプルです。
const App: React.VFC = () => {
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
ref.current.focus();
}, []);
return (
<div>
<input ref={ref} type="textarea" />
</div>
);
};
ある要素までスクロール
ある要素までスクロールするにはscrollIntoView
という機能を使います。
const List = () => {
const ref = useRef<HTMLDivEelement>(null);
const scroll = () => {
// 以下はtypescriptの書き方。jsの場合は
// if(scrollBottomRef && scrollBottomRef.current) {
// scrollBottomRef.current.scrollIntoView()
// }
ref?.current?.scrollIntoView();
}
const items: string[] = [];
return (
<div>
<div>aaa</div>
<div>aaa</div>
<div ref={ref}>ここにスクロール</div>
<div>aaa</div>
<div>aaa</div>
<button type="button" onClick={scroll}>スクロール</button>
</div>
)
}
よければこちらも参考にしてください。
標準で提供されているカスタマイズ性の乏しいフォームを隠して、独自デザインのものを提供する
以下が代表的な例でしょうか。
- ファイルアップロードボタン
- チェックボックス
ファイルアップロードボタン
- まずは
input
タグを隠します。 -
input
タグの生domを保持します。 -
Button
がクリックされたら、保持していたinput
タグの生domをクリックします。 - すると
input
タグのコールバックに選択したファイルが渡されます。
import React, { useRef } from "react";
import { render } from "react-dom";
const App: React.FC = () => {
const inputRef = useRef(null);
const onFileInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.files[0]);
};
const fileUpload = () => {
console.log(inputRef.current);
inputRef.current.click();
};
return (
<div>
<button onClick={fileUpload}>ファイルアップロード</button>
<input
hidden
ref={inputRef}
type="file"
accept="image/*"
onChange={onFileInputChange}
/>
</div>
);
};
render(<App />, document.getElementById("root"));
こちらに詳しくかいたのでよければ見てください。
チェックボックス
標準のチェックボックスを隠し、spanタグなどに、チェックボックスの見た目をもたせます。こちらの記事が詳しいですね。
記事中のチェックボックスの隠し方について補足です。アクセシビリティの観点からチェックボックスを完全に隠すのではなく、縦横1pxにして存在はするけど、見えないようにしています。こうすることでスクリーンリーダーがチェックボックスを扱えるようになります。
/* taken from https://css-tricks.com/inclusively-hidden/ */
input[type="checkbox"] {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
レンダリングされたあとのwidthやheightなどを知って、それらを計算して新しい機能を提供する
Reactが流行る以前のjsのライブラリといえば、生domの実際の値を計測して、再調整することが多かったですね。React上でこれらを実現するには、やはりref
を使います。
例えば以前投稿したMinimapでは
- ミニマップにする前のdomの子domの高さを取得
- 画面スクロール
を実現するために、複数のrefをMinimapコンポーネントに渡しています。
Discussion