コンポジション vs 継承

今回は、コンポジション vs 継承を学びます。

子要素の出力

SidebarDialog のような汎用的なコンテナコンポーネントでよく使われる方法。

children を使い、受け取った子要素を出力。

function FancyBorder(props) {
  return <div className={'FancyBorder FancyBorder-' + props.
color}>{props.children}</div>;
}

他のコンポーネントから JSX をネストすることで任意の子要素を渡すことができる。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">Welcome</h1>
      <p className="Dialog-message">Thank you for visiting our 
spacecraft!</p>
    </FancyBorder>
  );
}

スタイリングを分離します。

.FancyBorder {
  padding: 10px 10px;
  border: 10px solid;
}

.FancyBorder-blue {
  border-color: blue;
}

.Dialog-title {
  margin: 0;
  font-family: sans-serif;
}

.Dialog-message {
  font-size: larger;
}

複数の箇所に子要素を追加するケース。 独自の props を渡すことができる。一般的ではない。

<link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      function Contacts() {
        return <div className="Contacts" />;
      }

      function Chat() {
        return <div className="Chat" />;
      }

      function SplitPane(props) {
        return (
          <div className="SplitPane">
            <div className="SplitPane-left">{props.left}</div>
            <div className="SplitPane-right">{props.right}</div>
          </div>
        );
      }

      function App() {
        return <SplitPane left={<Contacts />} right={<Chat />} />;
      }

      ReactDOM.render(<App />, document.getElementById('root'));
    </script>
  </body>

left={<Contacts />}right={<Chat />} が独自の propsコンポーネントprops として渡せるものに制限はない。

スタイリングを分離します。

html,
body,
#root {
  width: 100%;
  height: 100%;
}

.SplitPane {
  width: 100%;
  height: 100%;
}

.SplitPane-left {
  float: left;
  width: 30%;
  height: 100%;
}

.SplitPane-right {
  float: left;
  width: 70%;
  height: 100%;
}

.Contacts {
  width: 100%;
  height: 100%;
  background: lightblue;
}

.Chat {
  width: 100%;
  height: 100%;
  background: pink;
}

行く通りかの書き方ができるCSS。それぞれのメリット・デメリットがわかっていない。

コンポジションとは何かがわかりましたね。ページの各々の構成要素をどのように組み立てるのか。

React を使った組み立ての方法論と捉えればよさそうです。

特化したコンポーネント

WelcomeDialogDialog の特別なケースと捉える。これはコンポジションで実現できる。

      function FancyBorder(props) {
        return <div className={'FancyBorder FancyBorder-' + props.color}>{props.children}</div>;
      }

      function Dialog(props) {
        return (
          <FancyBorder color="blue">
            <h1 className="Dialog-title">{props.title}</h1>
            <p className="Dialog-message">{props.message}</p>
          </FancyBorder>
        );
      }

      function WelcomeDialog() {
        return <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" />;
      }

      ReactDOM.render(<WelcomeDialog />, document.getElementById('root'));

継承はどうなの?

Reactの流儀

MAIN CONCEPTS 最後の章 [Reactの流儀[(https://ja.reactjs.org/docs/thinking-in-react.html) です。

さしずめ「ハンズオン React アプリ」といった内容です。

JSON API実装済かつデザインモックも入手済み。

Step1

UI をコンポーネントの階層構造に落とし込む

  1. モックを形作っている各コンポーネントを四角で囲み、名前を付ける
  2. 一責任の原則に沿って、単一のコンポーネントを見つける

Step2

Reactで静的なバージョンを作成する

表示の実装を行った後に、ユーザ操作の実装を行うのがよい。 UI の描画だけを行い、ユーザの操作をできない静的なバージョンを作ることからスタートする。

静的なバージョンを作る際には、

Step3

UI 状態を表現する必要かつ十分な state を決定する

最初に更新可能な状態の最小構成を考える。データモデルの更新は state で実現できる。

必要なものが出てきたら、その都度実装する。

state として扱うもの - 時間の経過の中で変化する - 算出することもできない

Step4

state をどこに配置するべきなのかを明確にする

どのコンポーネントがどんな state を持つべきなのかが、最も難しい問題。

filterTextball を設定し、フィルター動作を確認する。

    this.state = {
      filterText: 'ball',
      inStockOnly: false,
    };

Step5

逆方向のデータフローを追加する

SearchBar コンポーネントから FilterableProductTable コンポーネントを更新できるようにする。

ユーザがフォームを更新するたびに、入力を反映し state を更新する。

state 自身で更新するばきなので、 FilterableProductTableSearchBar にコールバックを渡す。このコールバックを onChange イベントで発火させる。

完成したコードは公式サイトからリンクを辿ってください。

私は早々に挫折した。

コードリーディング

挫折したのでコードリーディングしてお茶を濁しておきます。

Step1 で行ったコンポーネント抽出に従いクラス形式でコンポーネントを宣言します。

class ProductCategoryRow extends React.Component {
  ...
}

class ProductRow extends React.Component {
  ...
}

class ProductTable extends React.Component {
  ...
}

class SearchBar extends React.Component {
  ...
}

class FilterableProductTable extends React.Component {
  ...
}

ProductCategoryRow コンポーネントはセルを横方向に結合。レンダーします。

JSON API のデータ構造より childcategory を割り当てます。

  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }

ProductRow コンポーネントPRODUCTS リストを1行で表示する。変数と name を格納する変数が必要です。

name は変数に格納するのに、 price は変数に価格のしない理由は何? namestock の値によりスタイリングするからだろうと思います。

    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );

ProductTable コンポーネントはインプットボックスやチェックボックスの状態により、表示をフィルタリングする。

私はここで挫折した。

      if (product.category !== lastCategory) {
        rows.push(
          <ProductCategoryRow
            category={product.category}
            key={product.category} />
        );
      }

答えを見て厳密不等価演算子を厳密等価演算子に書き換えてどういう結果になるかは確認したが、理解はできない。

コーディングはここで終了とさせてください。

終わりに

終わりに として React のストロングポイントを抜粋する。

  • モジュール化されていて明示的であるコードはより読みやすい
  • コードを再利用が増えるに従い、書くコードの行数は減る

MAIN CONSETPS を通して学べたこと。

  • propsstate の違い
  • コンポーネントの組み立てと考え方
  • イベント処理
  • 要素のレンダー
  • フォームの扱い

これらを読み解き理解できるようになった。しかし、ハンズオンで作成した規模のアプリをスムーズにコーディングできるわけではない。

チュートリアルに沿って学んだので、 create-react-app でアプリを作る具体的な方法は不明なままだ。

さしずめ、素振りをするらめのバットの握り方を覚えた状態だろうか。

さらなる学習が必要です。