forwardRef API を使う書き方と使わない書き方

## 概要

React v16.3 で forwardRef API が追加されました。これを使った ref フォワーディングで、HOC を適用したコンポーネントの要素を取得することができます。しかしこの API が登場する前から、同様の機能は実装可能でした。この記事では、forwardRef API を使う書き方と使わない書き方を比較します。

## 課題

公式ドキュメントの [Forwarding Refs](https://reactjs.org/docs/forwarding-refs.html) のページに "This technique can also be particularly useful with higher-order components (also known as HOCs)" とあります。そこで、次のような課題を設定しました。

HOC を使う場面では、HOC の他に、３つのコンポーネントが登場します。HOC の定義は ["Concretely, a higher-order component is a function that takes a component and returns a new component."](https://reactjs.org/docs/higher-order-components.html) とのこと。

- HOC: 共通の処理などを書く
- HOC の入力コンポーネント
- HOC の出力コンポーネント
- 「HOC の出力コンポーネント」を render するコンポーネント

この場面で、『「HOC の出力コンポーネント」を render するコンポーネント』で、「HOC の入力コンポーネント」の ref を取得するコードを検討します。

## forwardRef API の基本
まずは、forwardRef API がどのようなものか、HOC ではないコンポーネントで見てゆきます。
具体例として、App コンポーネントが CustomTextInput という input タグをもつコンポーネントを render し、App でのその input タグの入力内容を取得するコードを考えます。
forwardRef API を使わない場合は、以下のようになります。

```.js
class App extends Component {
  constructor(props){
    super(props)
    this.childRef = createRef()
  }
  log = () => console.log(this.childRef.current.value)
  render() {
    return (
      <>
        {/* JSX の特別な属性(props にならない)である ref ではなく、普通の属性(props) として inputRef を作成し、そこで ref を渡している */}
        <CustomTextInput inputRef={this.childRef} />
        <button onClick={this.log}>入力値</button>
      </>
    )
  }
}

const CustomTextInput = props => {
  return (
   <>
      CustomTextInput コンポーネントにある input タグ
      {/* 呼び出し元の ref は、CustomTextInput の JSX の inputRef 属性として受け取っているので、それをセット */}
      <input ref={props.inputRef} />
    </>
  )
}
```

forwardRef API を使う場合は、以下のようになります。

```.js
export default class App extends Component {
  constructor(props){
    super(props)
    this.childRef = createRef()
  }
  log = () => console.log(this.childRef.current.value)
  render(){
    return (
      <>
        {/* ref 属性で渡す */}
        <CustomTextInput ref={this.childRef} />
        <button onClick={this.log}>入力値</button>
      </>
    )
  }
}

const CustomTextInput = forwardRef((props, ref) => { // <= ここに ref がある
  return (
    <>
      CustomTextInput コンポーネントにある input タグ
      {/* 呼び出し元の ref を input の ref として設定している */}
      <input ref={ref} />
    </>
  )
})
```

## HOC と forwardRef API

forwardRef API の基本をおさえたところで、冒頭の課題に使ってゆきましょう。冒頭の課題とは、『「HOC の出力コンポーネント」を render するコンポーネント』で、「HOC の入力コンポーネント」の ref を取得する方法の検討です。
forwardRef は引数のコールバック関数の第二引数として、「HOC の出力コンポーネントを render するコンポーネント」から ref を受け取ることができます。それを「HOC を適用するコンポーネント」の属性として渡すとうまくゆきます。実装をみた方がわかりやすいです。

```.js
import React, { Component, createRef, forwardRef } from 'react'

// HOC
function withCustom(WrappedComponent) {
  class Custom extends React.Component {
    render() {
      // ここで ref={XXX} としたくても、XXX 情報がない
      return <WrappedComponent {...this.props} />
    }
  }
  // return Custom

  // そこで、`return Custom` ではなく、 以下のように forwardRef を適用する
  return forwardRef((props, ref) => {
    return <Custom {...props} forwardedRef={ref} />
  })
}

// HOC を適用するコンポーネント
const TextInput = (props) => (
  <input ref={props.forwardedRef}/>
)
// HOC を適用した
const CustomTextInput = withCustom(TextInput)

// HOC を render するコンポーネント
export default class App extends Component {
  constructor(props){
    super(props)
    this.ref = createRef()
  }
  log = () => console.log(this.ref.current.value)
  render(){
    return (
      <>
        <CustomTextInput ref={this.ref}/>
        <button onClick={this.log}>入力値</button>
      </>
    )
  }
}
```

しかし、この課題では、forwardRef API を使わなくても以下のように実装できます。

```.js
import React, { Component, createRef, forwardRef } from 'react'

// HOC
function withCustom(WrappedComponent) {
  class Custom extends React.Component {
    componentDidMount() {
      console.log('old props')
    }
    render() {
      // this.props.inputRef を横流し
      return <WrappedComponent {...this.props} />
    }
  }
  return Custom

  // return React.forwardRef((props, ref) => {
  //   return <Custom {...props} forwardedRef={ref} />
  // })
}

// HOC を適用するコンポーネント
const TextInput = (props) => (
  <input ref={props.inputRef}/>
)

const CustomTextInput = withCustom(TextInput)


// HOC を render するコンポーネント
export default class Parent extends Component {
  constructor(props){
    super(props)
    this.ref = createRef()
  }
  log = () => alert(this.ref.current.value)

  // ref= をやめて、inputRef= にした
  render(){
    return (
      <>
        <CustomTextInput inputRef={this.ref}/>
        <button onClick={this.log}>入力値</button>
      </>
    )
  }
}
```

## 所感

こんな感想を持ちました。

- 「これまで工夫で解決していた課題に対し、React.forwardRef という語彙を用意しました、便利でしょ」くらいの印象
- せっかくなのでこれからは使ってゆこう
- わざわざ既存実装を置き換えるほど重要ではなさそう

## リファレンス

- https://reactjs.org/blog/2018/03/29/react-v-16-3.html#forwardref-api
- https://reactjs.org/docs/forwarding-refs.html
- https://solutionware.jp/blog/2018/07/25/react-%EF%BC%93%E3%81%A4%E3%81%AEref/