【React.js】useRefとuseImperativeHandleの使い方をご紹介(React hooksを学ぶ)
目次
今回は、DOM要素の参照と値の保持が可能なuseRefと、親コンポーネントから子コンポーネントの関数を呼び出すことができるuseImperativeHandleの使い方をご紹介します。
また、今回useRefを紹介するにあたって、refを他コンポーネントへ渡せるようにするforwardRefも紹介します。
前提として、今回紹介するuseRefやuseImperativeHandleは手続的なコードになってしまうため、基本的には使用は避けるべきだそうです。
なので、今回は機能としてこのようなものがあるという認識でご覧いただければと思います。
今まで、React hooksに関する記事を複数に分けて公開してきました。
下記はこれまでのRect hooksの記事になります。
【React.js】useStateとuseEffectの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1888/
【React.js】useContext、useReducerの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1926/
【React.js】useCallback、useMemoの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1956/
useRefについて
useRefには、DOM要素への参照を行うフックです。
DOMを保持して、それを使い回す、DOMの変数のような使い方です。
また、他に値を保持するような使い方もあります。
レンダリングを起こさずに値を保持したい場合などにuseRefでそれを実現することができます。
DOMを参照する
まず、DOMを参照する使い方です。
実際の例を見てみましょう。
「components」フォルダ内にUseRef.tsxファイルを作成し、下記のように記述してください。
import { useRef } from 'react';
const UseRef = () => {
const inputEl = useRef<HTMLInputElement>(null!)
const handleClick = () => {
inputEl.current.focus()
console.log("inputEl.current:", inputEl.current)
}
return (
<>
<input ref={inputEl} type="text" />
<button onClick={handleClick}>入力エリアをフォーカスする</button>
</>
)
}
export default UseRef
上記は、ボタンクリックでinputフォームにフォーカスを当てる例です。
useRefは、引数に初期値を入れます。
今回は、初期値をnullにしています。
DOMを参照するので、型を<HTMLInputElement>を指定しています。
初期値のnullには、非アサーション演算子(!)をつけることで、型エラーを未然に防いでいます。
この引数の値は、.currentプロパティにDOMの値が保持されます。
なので、DOMを参照する際は、「inputEl.current.focus()」といった風に記述しています。
今回は、focus()メソッドを使ってフォームにフォーカスしています。
このようにDOM参照することができます。
また、よく使われる例としては、特定の箇所までスクロールさせる際などにも使われます。
レンダリングを起こさず値を保持する
もう一つのレンダリングを起こさず値を保持する使い方についてです。
こちらに関しては、useRefの例としてよく紹介されますが、前述の通りuseRefはできるだけ使用を避けたほうがいいとのことなので、useCallbackやuseMemoなどで実装できるのであれば、そのほうがいいかもしれません。
実際の例を見てみましょう。
先ほどのuseRef.tsxの内容を下記のように書き換えてください。
import { useRef, useState } from 'react';
const UseRef = () => {
const InputEl = useRef<HTMLInputElement>(null!);
const [text, setText] = useState("");
const handleClick = () => {
setText(InputEl.current.value)
}
console.log("レンダリング!!");
return (
<>
<input ref={InputEl} type="text" />
<button onClick={handleClick}>set text</button>
<p>テキスト:{text}</p>
</>
)
}
export default UseRef
この例は、フォームにテキストを入力し、ボタンを押した時にのみ、レンダリングを発生させるといった例です。
useRefでDOMの参照を行い、「InputEl」をinputタグのrefに指定しています。
こうすることでstateの更新時にのみコンポーネントの再レンダリングが発生するように制御しています。
useRefを使うことで、フォームの値が変更しても、コンポーネントの再レンダリングは発生しないです。
コンポーネントの再レンダリングは起こしたくないが、保持している値に変更を加えたい場合は、useRefを使うのも一つの手です。
先ほどの例をブラウザで確認してみてください。
コンソールを見てみると、フォームに値を入力してボタンをクリックした時にしかレンダリングが発生していないことが確認できると思います。
forwardRefについて
forwardRefは先ほど紹介したuseRefに発生した機能です。
基本的にDOMは、コンポーネント間を超えて参照することができません。
ですが、親コンポーネントから子コンポーネントのDOMにアクセスしたいといった事があるかもしれません。
そのような場合に、forwardRefを使うことでそれを実現することができます。
前述のinputフォームにフォーカスする例で、実際にforwardRefを使ってみましょう。
UseRef.tsxの内容を下記のように書き換えてください。
import React, { useRef } from 'react';
const MyInput = React.forwardRef<HTMLInputElement, {}>((props, ref) => {
return <input type="text" ref={ref} />
})
const UseRef = () => {
const ref = useRef<HTMLInputElement>(null!)
const handleClick = () => {
ref.current.focus();
}
return (
<>
<MyInput ref={ref} />
<button onClick={handleClick}>フォーカスする</button>
</>
)
}
export default UseRef
上記例では、親コンポーネントであるUseRefから、子コンポーネントであるMyInputのDOMにアクセスしています。
MyInputコンポーネントの方では、React.forwardRefを使って、第二引数でrefを受け取り、inputタグのref属性にセットして、DOMを返しています。
親コンポーネントのUseRefの方では、先ほどと同じように、useRefを設定して、ref.currentにfocus関数を使って、フォーカスする関数を用意しています。
そして、MyInputコンポーネントを呼び出し、refをセットしています。
これで、子コンポーネントのDOMにアクセスできました。
子コンポーネントのDOMであるinputをフォーカスさせることができます。
実際にブラウザで動作確認してみましょう。
下記のように、ボタンクリックでフォームにフォーカスできればOKです。
useImperativeHandleについて
最後にuseImperativeHandleについても紹介します。
このフックもRef同様に、基本的には使用を避けるべきフックのようです。
なので、出番はほぼないかもしれませんが、こういう機能もあるということを知る目的で今回勉強していきたいと思います。
useImperativeHandleは、子コンポーネントで定義した関数を親コンポーネントで使えるようにするフックです。
早速例を見てみましょう。
今回もフォームにフォーカスする例でuseImperativeHandleを使ってみたいと思います。
UseRef.tsxを下記のように書き換えてください。
import React, { useImperativeHandle, useRef } from 'react';
type HandleClick = {
setFocus(): void;
}
const MyInput = React.forwardRef<HandleClick>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null!);
useImperativeHandle(ref, () => {
return {
setFocus() {
inputRef.current.focus()
}
}
})
return <input ref={inputRef} type="text" />
})
const UseRef = () => {
const ref = useRef<HandleClick>(null!)
return (
<>
<MyInput ref={ref} />
<button onClick={() => {
ref.current.setFocus();
}}>フォーカスする</button>
</>
)
}
export default UseRef
子コンポーネントであるMyInputコンポーネントでは、まずrefを渡す必要があるため、forwardRefを使用しています。
useImperativeHandle関数は、第一引数に親コンポーネントから受け取ったRefオブジェクトを入れ、第二引数には、親コンポーネントに渡す関数を定義しています。
今回は、フォームにfocusするsetFocus関数を記述しています。
そして、あとは簡単で親コンポーネントでは、子コンポーネントを呼び出し、Refオブジェクトを渡し、ボタンクリックで、「ref.current.setFocus()」のように子コンポーネントの関数を呼び出すように記述すればOKです。
実際にブラウザで開き、今までのフォーカスの例と同じように、ボタンクリックでフォームにフォーカスが当たれば成功です。
最後に
今回は、DOM要素への参照や値の保持を行うUseRef、DOMを他コンポーネントへ渡すことができるforwardRef、子コンポーネントの関数を親コンポーネントで利用できるようにするuseImperativeHandleを紹介しました。
繰り返しになりますが、これらRef関連のフックはどれも公式によれば、「ref を使った手続き的なコードはほとんどの場合に避けるべきです」とあるため、基本的には使用は避けたほうが良さそうです。
ですが、このような機能があることを念頭に入れておくことで、使い道が見えてくるかもしれません。
前回:【React.js】useCallbackとuseMemoの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1956/#chapter-3
次回:【React.js】useLayoutEffectとuseDeferredValueの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/2005/