【React - ContextAPI】Consumerの正体は、イケイケなコンポーネントだった

# TL; DR

```tsx
<Consumer>
  {contextValue => /* contextValueを使ってReactNodeをレンダリングする */}
</Consumer>
```

ContextAPIに出てくるConsumerコンポーネントの内側で宣言する関数は、

- children prop
- render prop

を組み合わせて作られているもので、Consumerコンポーネントはイケイケなやつだった

---

**今回は、ContextAPIの一部であるConsumerコンポーネントについて説明します。**
**一目ではややこしいので、 children prop/render prop を理解した上での腹落ちを目指します！**

# Context APIとは

React v16.3でアップデートされた [Context API](https://ja.reactjs.org/docs/context.html) は、簡単に説明すると

- Providerに状態を注入すると、
- Consumerに状態が渡され、
- それを利用して画面が描画される

というものです。以下は公式のチュートリアルに載っているコードです

```tsx
const ThemeContext = React.createContext('light'); // Contextを生成する

class App extends React.Component {
  render() {
    return (
      {/* Providerに状態を注入するとConsumerに状態が渡される */}
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

const ThemedButton = (props) =>
  <ThemeContext.Consumer>
    {/* Consumerから渡される状態を引数に取って、画面を描画する　*/}
    {theme => <Button {...props} theme={theme} />}
  </ThemeContext.Consumer>
```

公式では、テーマカラーやログイン中ユーザーなど、状態が変わらないが様々なコンポーネントから参照される者に対して使うことが推奨されていますが、
状態を様々なコンポーネントに上手に渡すために使ってもいいと思っています。

# Consumerのわかりづらい記法は、なんだ

上のサンプルを見てわかる通り、Consumerを使う際は以下のように記述します

```tsx
<Consumer>
  {contextValue => /* contextValueを使ってReactNodeをレンダリングする */}
</Consumer>
```

コード中の`{contextValue => /* contextValueを使ってReactNodeをレンダリングする */}` 👈この無名関数ちょっとよくわからないですよね、書かなくてもいいのかな？ 
... いえ、書かないと怒られてしまいます。

なぜなら、Consumerコンポーネントがpropsとして `「ReactNodeを返す関数」` を要求しているからです。

※ ReactNodeというのは文字列だったり、JSXエレメントだったり、booleanだったり。とりあえず表示できるものという理解で大丈夫です

試しに以下のようなコードを書くと怒られます。(ConsumerからcontextValueを受け取りたいのでこんなの全く意味ないですが...w)

```tsx
<Consumer>
  <p>{/* ReactNodeを返す関数を記述しないと怒られます */}</p>
</Consumer>
```

**_Consumerコンポーネントがpropsとして「ReactNodeを返す関数」を要求している_** と書きましたが、

普通はコンポーネントがpropsを受け取る時って、こんな感じでタグの中に記述していきますよね

```tsx
<Component props1={} props2={} ... />
```

Consumerコンポーネントでは開始タグと閉じタグの間にpropsを記述していますが、
なぜこうなっているかというと、`children prop`が使われているからです。

ContextAPIは脇においておき、`children prop` について説明します

## children propについて

参考URL

[公式チュートリアル](https://reactjs.org/docs/composition-vs-inheritance.html)
[react-in-patterns: Passing a child as a prop](https://krasimir.gitbooks.io/react-in-patterns/content/chapter-4/#higher-order-component)

children propはReact.ComponentのAPIで、React.Componentを継承したコンポーネントが利用できます。
何気なく使っているタグも、children propを使っています。

```tsx
<div>
  <p>{/* <div>はこれらを */}</p>
  <span>{/* children propとして受け取っています */}</p>
<div>
```

children propを受け取るコンポーネント側の内部がどうなっているか知るために、適当なコンポーネントを作ってみます

```tsx
import * as React from "react"
    
class App extends React.Component {
  render() {
    return(
      <div>
        {/* this.orops.childrenに、Appタグに挟まれたコンポーネントが渡ってくる */}
        {this.props.children}
      </div>
    )
  }
}
    
<App>
  <p>{/* children propとなるやつ */}</p>
</App>
```

Appコンポーネントタグに挟まれたコンポーネントが `this.props.children` として利用できるのがわかりますね。
さて、children propについて理解した上でもう一度 Consumerコンポーネントの利用例を見てみます

```tsx
<Consumer>
  {contextValue => /* contextValueを使ってReactNodeをレンダリングする */}
</Consumer>
```

...あら、Consumerのタグに挟まれた要素はchildren propsとして渡されることはわかりますが、
コンポーネントではなく、関数の形になっていますね。

ここで、children propの次に理解する必要があるのが、 `render prop`です。
(ContextAPIについては再度置いておきます)

## render propについて

参考URL

[公式チュートリアル](https://reactjs.org/docs/render-props.html)
[react-in-patterns: Function as a children, render prop](https://krasimir.gitbooks.io/react-in-patterns/content/chapter-4/#higher-order-component)

render propは簡単に言うと 「コンポーネントのrenderメソッドを外部から定義するためのテクニック」です。

色々な使い方があるので「こういうケースで使うべき！」とは言えないですが、
個人的には以下の状況が重なった時に便利そうだと思います(とはいえ実戦で使ったことないです)

- 状態と画面描画の分離をしたいとき
- 上で言った分離を1つのパターンとして共通利用したいとき

例えば、ローディング時のViewとローディング完了時のViewを出し分けるというのが様々な画面で重複していた場合、
render propで以下のように書くことができます (Typescriptで書いています)

```tsx
interface LoadingProps {
  url: string,
  render: (isLoading: boolean) => JSX.Element
}

interface LoadingStates {
  isLoading: boolean
  data: any[]
}

class LoadingState extends React.Component<Props, States> {
  state: States = {
    isLoading: false,
    data: null
  };

  fetch = () => {
    // ... this.props.urlを使ってapiからデータ取得する処理を記述
    this.setState({
      isLoading: false,
      data: /* 取得したデータ */
    });
  }

  componentDidMount() {
    // ... 通信処理の実行
    this.setState({isLoading: true}, this.fetch);
  }

  render() {
    // ... render propに状態を渡すだけで、描画については関与しない
    return this.props.render(...this.state);
 }
}

// apiのurlと、通信していない時に表示したいコンポーネントを引数に渡している
const WithLoadingState = ({url, component}: {url: string, Component: JSX.Element}) =>
  <LoadingState
    url
    render={state: LoadingStates) =>
      state.isLoading
        ? <p>Loading...</p>
        : <Component data={state.data} />
    } />;

```

上のコードでは、

- 状態については全て `LoadingState` コンポーネントにお任せして、
- 何をどう表示するかに関しては `WithLoadingState` コンポーネントにお任せする

という「分離」を表現しています


## 本題: Consumerタグに挟まれる関数の正体とは

render prop は、通例で _render_ という名前にしていますが、名前はなんでも構いません。

名前は何でもいいどころか、**実は名前をつけない方法**もあります。

それがまさに、children propを render propとして使う方法です。

そしてそれが**Consumerコンポーネントの関数の正体**です。

```tsx
<Consumer>
  {contextValue => /* contextValueを使ってReactNodeをレンダリングする */}
</Consumer>
```

このコードは、

Consumerコンポーネントが、 `「コンポーネントを返す関数(render prop)」` を `「children propとして」` 要求している
という意味でした。

先ほどのrender propの例でお見せした **状態と画面描画の分離** をConsumerコンポーネントに当てはめると、

- Consumerコンポーネントが状態を持ち、
- その状態をもとに何を描画するかはchildren prop(関数)に任せる

と言えそうです。

最後に、ContextAPIの型定義を見ると、Provider側 で 受け取る状態(value: T) をConsumer側のchildren propで利用しているのがわかります。

```tsx

    interface ProviderProps<T> {
    		value: T;
    		children?: ReactNode;
    }
    
    interface ConsumerProps<T> {
    		children: (value: T) => ReactNode;
    		unstable_observedBits?: number;
    }
```

なので、Consumerタグに挟まれるのはコンポーネントではなく、

`状態を受け取りReactNodeを返す関数`でないといけないということになります。

なんと、ここまで説明に時間かかりましたが、
結論、
**Consumerはイケイケなコンポーネントですね！！！**

# さいごに

記事中で紹介したrender propは比較的新しいテクニックで、日本語の解説を見つけるのも難しいです。

なにか理解の進む記事があれば、ぜひご紹介ください！