メインコンテンツまでスキップ

React - Ref

Refs and the DOM

「宣言的」と「命令的」の違い

  • 宣言的 (declarative)
    • プロパティで制御する isOpenなど
    • Controlled Component という
  • 命令的 (imperative)
    • メソッドで制御する open()など
    • 子側で勝手に操作される
    • Uncontrolled Component という

宣言的に子コンポーネントを操作できる場合は、必ずそうすること。 宣言的には操作できない場合や、フォームのネイティブの動作を尊重したい場合など、ごく限られた場面においては命令的に子コンポーネントを操作する。この場合に使うのが、Ref である。

いつ Ref を使うべきか

  • フォーカスを扱う時、テキストを選択するとき、メディアを再生するとき
  • アニメーションを発動するとき
  • サードパーティの DOM ライブラリを React で使う時

Ref の作成

React.createRef()で作成し、要素のref属性に設定する。ref属性はclassNameなどと同じように、他のカスタムな属性とは異なる特別な役割を持っている。

コンポーネント全体で利用できるように、インスタンスプロパティに代入しておくのが一般的。

class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}

Ref へのアクセス

ref を 要素に設定すると、this.myRef.currentのような形で要素にアクセスできるようになる。この値は、ref を設定した要素の種類によって異なってくる。

  • HTML 要素の場合(divなど) --- HTML 要素自体
  • クラスコンポーネントの場合(MyComponentなど) --- クラスコンポーネントのインスタンス
  • ファンクションコンポーネントの場合 --- 使用不可、ただしHook を使用すれば可能

タイミング

コンポーネントがマウントされると React はcurrentに設定された変数に値を割り当て、マウント解除されるとそれを null に戻す。

currentの値の更新は、componentDidMount または componentDidUpdate ライフサイクルメソッドのに行われる。

Callback Refs

Ref を設定するもう一つの方法。より細かい制御が可能。

ref属性に、createRef()で作った値ではなく、関数を渡す。

この関数は、コンポーネントがマウント・アンマウントされたときに呼ばれる。マウントされたときは DOM 要素またはインスタンスとともに呼ばれる。アンマウントされたときはnullとともに呼ばれる。

Ref は、componentDidMount や componentDidUpdate が呼ばれる前に実行されるので、常に最新であることが保証される。

class CustomTextInput extends React.Component {
constructor(props) {
super(props);

this.textInput = null;
}

setTextInputRef = (element) => {
this.textInput = element;
};

focusTextInput = () => {
if (this.textInput) this.textInput.focus();
};

render = () => {
return (
<div>
<input type="text" ref={this.setTextInputRef} />
<input onClick={this.focusTextInput} />
</div>
);
};
}

子コンポーネントの Ref を取得する

例えば下記の例では、inputRefという属性に設定している Callback Ref により、子の Ref を取得できる。

なお、inputRefのようなカスタムな属性ではなく、ref属性で子コンポーネントの特定の要素(インスタンスではない)を取得したい場合は、後述の Ref Forwarding を使用すること。

function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}

class Parent extends React.Component {
render() {
return <CustomTextInput inputRef={(el) => (this.inputElement = el)} />;
}
}

Ref Forwarding

Ref を DOM に転送する

あるコンポーネントが、受け取ったrefをコンポーネント内の別の DOM 要素に割り当てる(転送する)場合に使う。React.forwardRef()を使うことで実現する。ごく限られた場面で使用し、乱用しないこと。

下記の例では、FancyButtonは、さもbutton要素であるかのように振る舞うことができる。

const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref}>{props.children}</button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

なお、クラスコンポーネントでは下記のようにする。

class FancyButton extends Component {
render() {
return <button ref={this.props.innerRef}>{this.props.children}</button>;
}
}
export default React.forwardRef((props, ref) => (
// カスタム属性で渡している点に注意する。
// もし`ref`属性に設定すると、ref=FancyButtonクラスのインスタンスになってしまう。
<FancyButton innerRef={ref} {...props} />
));

ライブラリ作成者に向けての注意

  • forwardRef を使い始めるときは、Breaking Change として扱うべし
  • forwardRef の適用を Conditional に決定することは避けるべし

HOC と forwardRef を組み合わせて使う方法

HOC で forwardRef を使いたい場合は、ref属性を手動で設定する必要がある。

function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}

render() {
const { forwardedRef, ...rest } = this.props;

// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />;
}
}

return React.forwardRef((props, ref) => {
// forwardedRefというカスタム属性を使っている点に注目。
// ここでref属性として渡してしまうと、ref=LogPropsのインスタンスになってしまう。
return <LogProps {...props} forwardedRef={ref} />;
});
}