React v16.3 の Context と Fragment

よく使いそうな Context と Fragment についてメモ。

Context

そもそも、 Context はあらゆる階層のコンポーネント間で、データを共有する機能を持ちます。

しかし、v16.3 以前の React における Context には以下の注意書きがありました。

If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.

アプリを安定させたいなら使わないでね。実験的 な API なので、将来的に使えなくなるかもよ。

この注意書きは v16.3 で Context が変更されると共に削除されています。v16.3 で刷新された Context の使い方を見ていきましょう:

<Family /> を飛ばして、 <Person /> に値が渡されていることがわかります。

import React, { Component } from "react";
import { render } from "react-dom";

const Context = React.createContext();

function Family() {
  return <Person />;
}

function Person() {
  return (
    <Context.Consumer>
      {({ name, age }) => (
        <React.Fragment>
          <p>Name: {name}</p>
          <p>Age: {age}</p>
        </React.Fragment>
      )}
    </Context.Consumer>
  );
}

class App extends Component {
  state = {
    name: "Mark",
    age: 12
  };
  render() {
    return (
      <Context.Provider
        value={{
          name: this.state.name,
          age: this.state.age
        }}
      >
        <Family />
      </Context.Provider>
    );
  }
}

render(<App />, document.getElementById("root"));

新しい Context は次の 3 つの API から構成されます:

createContext()

const { Provider, Consumer } = React.createContext(defaultValue);

Consumer と Provider を返す関数で、任意のデフォルト値を引数に取ります。

Provider

<Provider value={/* some value */}>

高レベル階層で使われ、value プロパティを受け取ります。

Consumer

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

Provider 以下の階層で使われ、value を受け取り、JSX を返す関数を受け取ります。 Provider の value が変更された場合、このコンポーネントは再描画されます。

Fragment

上記の例で使用されている Fragment は v16 から追加されたビルトインコンポーネントで、リストを返すときにすごく便利です。

例えばテーブルを作りたい場合:

class App extends Component {
  render() {
    return (
      <table>
        <tbody>
          <tr>
            <Columns />
          </tr>
        </tbody>
      </table>
    );
  }
}

この Columns コンポーネントが複数の td 要素を返すとき、次の div のようなラッパーを用意する必要がありました:

function Columns(props) {
  return (
    <div>
      <td>Hello</td>
      <td>World</td>
    </div>
  );
}

これでは警告が出てしまいます。実際のアウトプットは次のようになるためです:

class App extends Component {
  render() {
    return (
      <table>
        <tbody>
          <tr>
            <div>
              <td>Hello</td>
              <td>World</td>
            </div>
          </tr>
        </tbody>
      </table>
    );
  }
}
// Warning: validateDOMNesting(...): <div> cannot appear as a child of <tr>.

個人的には次のように配列を返すようにしてたりしたんですが、正しい作法なのかどうかわからないし、読みにくい:

function Columns() {
  return [<td>Hello</td>, <td>World</td>];
}

v16 以後は次のように書くことが出来ます:

function Columns(props) {
  return (
    <React.Fragment>
      <td>Hello</td>
      <td>World</td>
    </React.Fragment>
  );
}

便利ですね。