Edited at

Reactのrefを理解する@Typescript

More than 1 year has passed since last update.

https://reactjs.org/docs/refs-and-the-dom.html が微妙によくわからなかったので、refを使用していない場合と使用した場合の違いを確かめたメモ。確か、refはreduxのtodolist exampleに唐突に出てきて1、「えっ」ってなってしまった人もいそう。

上記のリンクの例題に即しています。

問題設定としては、ボタンと入力フォームがあり、ボタンをクリックすると、入力フォームにフォーカスが移るようにしたいというものです。

環境構築周りは、https://www.typescriptlang.org/docs/handbook/react-&-webpack.html あたりを参考に。


準備

本題で、コンポーネントであるCustomTextInput.tsxを用意するのだが、ここでは必要なファイルを準備。


src/components/App.tsx

import * as React from 'react';

import CustomTextInput from './CustomTextInput';

function app(): JSX.Element {
return (
<div>
<CustomTextInput />
</div>
);
}

export default app;



index.tsx

import * as React from 'react';

import * as ReactDOM from 'react-dom';

import App from './components/App';

ReactDOM.render(
<App />,
document.getElementById('root'),
);


index.htmlは、https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html に従って用意。


state

ただ単に、submit時に文字を受け取るだけならば、refを使う必要はない。


src/components/CustomTextInput.tsx

import * as React from "react";

class CustomTextInput extends React.Component<{}, {textInput: string}> {
constructor(props: {}) {
super(props);
this.state = {
textInput: '', // if undefined or null, error
};
}

handleChange = (e: React.FormEvent<HTMLInputElement>): void => {
this.setState(
{textInput: e.currentTarget.value}
);
}

handleTextSubmit = (e: React.FormEvent<HTMLInputElement>): void => {
alert(this.state.textInput);
}
render() {
return (
<div>
<input
// 'string | number | string[]'
value={this.state.textInput}
onChange={this.handleChange}
/>
<input
type="button"
value="the text input"
onClick={this.handleTextSubmit}
/>
</div>
);
}
}

export default CustomTextInput;


textInputはstring型だが、focusしたいときはHTMLElement型である必要がある。しかしながら、inputタグのvalue attributeでは、string | number | string[]型しか受け付けないので、ボタンを押してもfocusできない状況である。

ちなみにhandleTextSubmitとかhandleChangeはmethodでなく、function proptetyになっている.

methodにすると、イベントハンドラー配下のthisキーワードがオブジェクトインスタンスでなく、イベントの要素自体を示してしまい、undefinedとなってしまうためである2


ref

で、本題なんだけど、inputタグでref attributeを利用して、このように書けば良い:


src/components/CustomTextInput.tsx

import * as React from "react";

class CustomTextInput extends React.Component<{}, {}> {
// Once the component mounts,
// React will call the ref callback with the DOM element, and will call it with null when it unmounts.
// see also https://stackoverflow.com/questions/33796267/how-to-use-refs-in-react-with-typescript
private textInput: HTMLInputElement;
constructor(props: {}) {
super(props);
}

focusTextInput = (): void => {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}

render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div>
<input
type="text"
ref={(input: HTMLInputElement) => { this.textInput = input } }
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}

export default CustomTextInput;


refの右辺はクロージャーになっているが、inputの型がHTMLElementのサブクラスであることに注意する。なので、focusメソッドが使えるわけだ。

注意点としては、Typescriptでは型を明記する必要があるので、Refs and the Domのコードをそのまま拝借しても型不一致でコンパイルに失敗する。

こういうときは、private textInput: HTMLInputElement;とするとうまくいく3

普通はrefは多用すべきでないことに留意する4。(といっても、シンプルなものに関しては、ref使って良いんじゃない、っていうのをhttps://reactjs.org/docs/uncontrolled-components.html 内のリンク this article on controlled versus uncontrolled inputsで言っていたりする)





  1. https://redux.js.org/docs/basics/UsageWithReact.html#containersaddtodojs のこと。 



  2. Javascript本格入門改訂新版 6.7節 p.352の解説がわかりやすい。Typescriptでは、function proptetyのthisは意図通り、CustomTextInputのインスタンス自体を指す。詳しくは、https://www.typescriptlang.org/docs/handbook/functions.htmlthis parameters in callbacksの節を見れば良い。 



  3. https://stackoverflow.com/questions/33796267/how-to-use-refs-in-react-with-typescript を参照 



  4. https://reactjs.org/docs/refs-and-the-dom.html#dont-overuse-refs