React.js の render-props と Function as child の紹介

この記事は Meguro.es #17 のプレゼン資料です。SpeackerDeck に PDF をアップしましたが、ドキュメントのメンテは Qiita で行います。

---

## 目的

- render-props とは何かを理解すること
- Function as child とは何かを理解すること
- render-props を使ったコードが読めるようになること
- Function as child を使ったコードが読めるようになること

---

## 省略すること

- SFC。すべてクラス構文に統一しました。
- 「値」、「参照」といった用語の厳密な使い方
- props の型

---

## コンポーネント呼び出しの素朴な例

親コンポーネントが子コンポーネントを呼び出す、もっとも単純な例です。

```js
class Parent extends Component {
  render() {
    return <Child />
  }
}

class Child extends Component {
  render() {
    return '子コンポーネント'
  }
}
```

---

## 親から子へ文字列を渡す

親コンポーネントは子コンポーネントに、JSX の属性として、文字列を渡すことができます。
`JSX の属性` として指定することで、`this.props` でそれを参照することができます。

```js
import React, { Component } from 'react'

class Parent extends Component {
  render() {
    const something = 'aaa'
    return <Child something={something} />
  }
}

class Child extends Component {
  render() {
    return <>something は {this.props.something}</>
  }
}
```

---

## 親から子へオブジェクトを渡す

親コンポーネントは子コンポーネントに、JSX の属性として、オブジェクトを渡すことができます。たとえば、`{ keyA: 'aaa'}` です。

```js
class Parent extends Component {
  render() {
    const something = { keyA: 'aaa'}
    return <Child something={something} />
  }
}

class Child extends Component {
  render() {
    return <>something.keyA は {this.props.something.keyA}</>
  }
}
```

---

## 親から子へ関数を渡す

親コンポーネントは子コンポーネントに、JSX の属性として、関数を渡すことができます。
JSX の属性が関数なら、コールバックとして使えるということです。

```js
class Parent extends Component {
  something() {
    console.log('aaaaaa')
  }
  render() {
    return <Child something={this.something} />
  }
}

class Child extends Component {
  render() {
    this.props.something()
    return <>子コンポーネント</>
  }
}
```

---

## 親から子へコンポーネントを返す関数を渡す

先程の例では、JSX の属性として渡した関数は、console.log を実行しただけでした。
コンポーネント(React.Element) を返すという使い方がもっと有益です。

たとえば、親コンポーネントで DOM 要素を返す関数を定義して、子コンポーネントの props として渡します。
そして、子コンポーネントの render メソッドの定義中でその関数を実行します。
次の例では、prop の名前を something から render に変更し、雰囲気をだしました。

---

```js
class Parent extends Component {
  something() {
    return <>親コンポーネント</>
  }
  render() {
    return <Child render={this.something} />
  }
}

class Child extends Component {
  render() {
    return this.props.render()
  }
}
```

---

## 親から子へコンポーネントを渡す1

親コンポーネントは子コンポーネントに、JSX の属性として、コンポーネントを渡すことができます。

```js
class WanderingComponents extends Component {
  render() {
    return <>WanderingComponents</>
  }
}

class Parent extends Component {
  render() {
    return <Child myComponent={<WanderingComponents />} />
  }
}

class Child extends Component {
  render() {
    return <>{this.props.myComponent}</>
  }
}
```

---

## 親から子へコンポーネントを渡す2

先程の例は、JSX の属性名を children にすると、Child コンポーネントはよくみるイディオムになります。

```js
class WanderingComponents extends Component {
  render() {
    return <>WanderingComponents</>
  }
}

class Parent extends Component {
  render() {
    return <Child children={<WanderingComponents />} />
  }
}

class Child extends Component {
  render() {
    return <>{this.props.children}</>
  }
}
```

---

## コンポーネントのネストと children 属性

親から子にコンポーネントを渡すとき、JSX の属性としてではなく、コンポーネントをネストさせる形で渡すこともできます。ネストさせると呼び出し側から属性名の記述が消えますが、`children` という名前で受け取れると[規定されています](https://reactjs.org/docs/jsx-in-depth.html#children-in-jsx)。
以下の書き方で、WanderingComponents は Child の子コンポーネントになるので、Child では this.props.children で WanderingComponents を取得できます。

---

```js
class WanderingComponents extends Component {
  render() {
    return <>WanderingComponents</>
  }
}

class Parent extends Component {
  render() {
    return (
      <Child>
        <WanderingComponents />
      </Child>
    )
  }
}

class Child extends Component {
  render() {
    return this.props.children
  }
}
```

---

## Function as child パターン

コンポーネントのネスト部が children 属性に対応するということを確認しました。
このコンポーネントのネスト部は、コンポーネントそのものだけではなく関数にすることもできます。
children 属性がコールバックとして使えるということです。
その場合、render での children 属性の呼び出しは `{this.props.children}` ではなく `{this.props.children()}` です。
これを「Function as child パターン」と呼びます。

---

```js
class Parent extends Component {
  something() {
    return <>something</>
  }
  render() {
    return (
      <Child>
        {this.something}
      </Child>
    )
  }
}

class Child extends Component {
  render() {
    return this.props.children() // () をつけて関数を実行した
  }
}
```

---

## Functions as Children と render-props の関係

一つ前の子コンポーネントの reunder メソッドは `return this.props.children()` という定義でした。
children 属性が関数である必要があると読み取れます。
これは JSX の属性として関数を渡す、いわゆる render-props です。

コンポーネントを返す関数を prop とする JSX の属性名を render から children に変更してみましょう。

---

render 版

```js
import React, { Component } from 'react'

class Parent extends Component {
  something() {
    return <>something</>
  }
  render() {
    return <Child render={this.something} />
  }
}

class Child extends Component {
  render() {
    return this.props.render()
  }
}
```

---

children 版

```js
import React, { Component } from 'react'

class Parent extends Component {
  something() {
    return <>something</>
  }
  render() {
    return <Child children={this.something} />
  }
}

class Child extends Component {
  render() {
    return this.props.children()
  }
}
```

挙動は同一です。
Functions as Children は render-props の一種だとみなせることがわかりました。

---

## リファレンス

- https://reactjs.org/docs/render-props.html
- https://reactjs.org/docs/jsx-in-depth.html#children-in-jsx
- https://reactjs.org/docs/jsx-in-depth.html#functions-as-children
- https://reactjs.org/docs/react-component.html#render