61
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Reactのrefを理解する@Typescript

Last updated at Posted at 2017-12-17

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

61
39
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
61
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?