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
を用意するのだが、ここでは必要なファイルを準備。
import * as React from 'react';
import CustomTextInput from './CustomTextInput';
function app(): JSX.Element {
return (
<div>
<CustomTextInput />
</div>
);
}
export default app;
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を使う必要はない。
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を利用して、このように書けば良い:
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で言っていたりする)
-
https://redux.js.org/docs/basics/UsageWithReact.html#containersaddtodojs のこと。 ↩
-
Javascript本格入門改訂新版 6.7節 p.352の解説がわかりやすい。Typescriptでは、function proptetyの
this
は意図通り、CustomTextInputのインスタンス自体を指す。詳しくは、https://www.typescriptlang.org/docs/handbook/functions.html のthis parameters in callbacks
の節を見れば良い。 ↩ -
https://stackoverflow.com/questions/33796267/how-to-use-refs-in-react-with-typescript を参照 ↩
-
https://reactjs.org/docs/refs-and-the-dom.html#dont-overuse-refs ↩