リファレンス(Ref)

PreactがレンダリングしたDOM要素やコンポーネントを直接参照する必要がある時がよくあります。その時はリファレンス(Ref)を使います。

典型的なユースケースはDOM要素の画面上での実際のサイズを測りたいケースです。

コンポーネントにref属性を付与してコンポーネントインスタンスの参照を取得することは可能ですが、これは一般的に推奨されません。 親コンポーネントと子コンポーネントの間に強い結合関係が生じるため、コンポーネントの組み合わせに制限が発生してしまいます。 ほとんどの場合でクラスコンポーネントのメソッドを直接実行するよりもコールバック関数をpropsで渡す方が自然です。



createRef

createRef関数はcurrentプロパティがnullにセットされたオブジェクトを返します。 以下のようにcreateRef関数の戻り値をref属性にセットすると、renderメソッドが実行されるたびに、DOM Nodeもしくはコンポーネントインスタンスがcurrentプロパティに代入されます。

class Foo extends Component {
  ref = createRef();

  componentDidMount() {
    console.log(this.ref.current);
    // Logs: [HTMLDivElement]
  }
  
  render() {
    return <div ref={this.ref}>foo</div>
  }
}

リファレンスコールバック

要素へ参照を取得するもう1つの方法は以下のようにコールバック関数を渡すことです。 少し長くなりますがcreateRefと似たような動作をします。

class Foo extends Component {
  ref = null;
  setRef = (dom) => this.ref = dom;

  componentDidMount() {
    console.log(this.ref);
    // Logs: [HTMLDivElement]
  }
  
  render() {
    return <div ref={this.setRef}>foo</div>
  }
}

ref属性へ渡すコールバック関数がインライン関数で定義されている場合、コールバック関数は2回実行されます。1回目はnull、2回目は実際の参照が渡されます。これはよくある間違いです。createRefAPIの場合、ref.currentが定義されているかのチェックをユーザーに強制することでこのようなバグが起きにくくなっています。

まとめ

次のようなシンプルなコンポーネントがありますが、実際に計測した値を表示するように書き換えてみましょう。

class Foo extends Component {
  // ここではDOM Nodeの実際の横幅と高さを使用したいと思います
  state = {
    width: 0,
    height: 0,
  };

  render(_, { width, height }) {
    return <div>Width: {width}, Height: {height}</div>;
  }
}

計測が意味を持つのはrenderメソッドが実行されてコンポーネントがDOMにマウントされた後です。 それより前はDOM Nodeが存在していないので、測定しても意味がありません。

class Foo extends Component {
  state = {
    width: 0,
    height: 0,
  };

  ref = createRef();

  componentDidMount() {
    // 安全のためにリファレンスがあるか確認する
    if (this.ref.current) {
      const dimensions = this.ref.current.getBoundingClientRect();
      this.setState({
        width: dimensions.width,
        height: dimensions.height,
      });
    }
  }

  render(_, { width, height }) {
    return (
      <div ref={this.ref}>
        Width: {width}, Height: {height}
      </div>
    );
  }
}

完成です!これでコンポーネントはマウントされた時に常に横幅と高さを表示するようになりました。

Built by a bunch of lovely people ubitools.com