Reactガイドを読んでいくその19

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

React の流儀

昨日の続きです。

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

昨日はコンポーネントの階層まで決まりました。
アプリケーションの実装に入っていきます。

まずはデータモデルを受け取ってUIの描画だけをし、ユーザ入力なしバージョンを作成するのが良いと書いてあります。 表示の実装とユーザ操作の実装を分けるのは重要な事項であり、静的なバージョンはタイプ量が多い代わりに考えることが少なく、実装を組み立てやすい。

静的なバージョンを作る際は他のコンポーネントを再利用しつつそれらにpropsを通じてデータを渡酢ようなものを作る。

静的なバージョンを作る時にはstateを使わず、propsで組むことに心がける。
stateはユーザ操作や時間経過などで動的に変化するデータを扱うものなので、静的なバージョンでは不要。

コンポーネントの組み方はボトムアップでもトップダウンでもどちらでも可能。
大規模な場合は開発しながらテストを書き、ボトムアップで進める方が簡単だったりする。

ここまで検討が終わり実装できると、データモデルを描画する再利用可能なコンポーネントのライブラリが完成する。
このバージョンでは最上位のFilterableProductTableがデータをpropsとして受け取り、元データを更新して ReactDOM.render() を呼び出すものになります。

props vs state

propsもstateもプレーンなJavaScriptのオブジェクト。
いずれもrenderへ影響を及ぼす情報を持っているものの、その機能は異なる。
 ・props(properties) = 関数引数のようにコンポーネントへ渡される
 ・state = 関数内で宣言された変数のようにコンポーネントの内部で制御される

詳しくはここを見てくれと。
github.com 上に書いてあるように基本的な部分は一緒だけど、Reactでの使われ方が異なる。
表になっているのがわかりやすかった。

gyazo.com

親あるいは子コンポーネントから見たとき更新できるか否か、
コンポーネント内で更新できるかどうかが肝になっている。

明日は「Step 3: UI 状態を表現する必要かつ十分な state を決定する」です。

今日はここまで。

Reactガイドを読んでいくその18

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

React の流儀

何回かに分けて実施する。

ここでは検索可能なデータ表を作成していく。
巨大で軽快な Web アプリを開発する場合はReactが素晴らしい選択肢になることを伝えている。
背景にはInstagramFacebookがあるのでその説得力は確かなものだと考えられる。
もちろんReactに問題がないわけではないと思う。

モックから始めよう

想定としてはJSON API実装済みで、デザイナーからもデザインモックが渡された状態。
検索窓とスポーツ用品・電子機器の名前と価格が表示された簡易なもの。
JSON APIはcategory:でそれぞれ表の情報を並べただけのもの。
以下のような感じに返ってくると想定。

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

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

まずは構成要素をみて、コンポーネントそれぞれに名前をつけていく。
デザイナーと一緒に仕事をしている場合はこの作業をすでに終えている可能性があるので話しにいく。
プロダクトの設計や検討には関連チームとのコミュニケーションは大切なんだということを知れる。
デザイナの場合モックを作っている時にレイヤー等ですでに名前をつけていることがあるので、それをコンポーネントの名前にそのまま適用するのがスムーズで良い。

Single responsibility principleに則り、一つのコンポーネントは理想的には一つのことだけをするべきという考え方のもと、コンポーネントにする範囲を決めていく。
もし検討段階で単一化が難しい場合は将来的には分割することを考える必要がある。

ユーザに向けて表示する=JSONデータモデルというパターンが多いので、正しく構築されていればUIにもうまく適用することが可能。Reactにおいて良いモック、JSONモデルかどうかはこういうところで判断することが可能なのかもしれない。

例では5種類のコンポーネントがこのアプリにあることがわかる。
・全体を取り囲むもの(FilterableProductTable)
・ユーザ入力を受け付ける場所(SearchBar)
・ユーザ入力に基づくデータの集合を表示する枠(ProductTable)
・カテゴリを見出しとして表示する(ProductCategoryRow)
・各商品を一行で表示する(ProductRow)

この例では表のヘッダー部が単独のコンポーネントになっていないが、好みの問題でどちらでも良いとみなせるが、データの集合を描画する一環としてProductTableを定義したため。
将来的にコンポーネントの役割が肥大化してきた時にはProductTableHeaderとして分割してあげるのが良い。

今回わかったコンポーネントを階層構造にすると以下のようになる。

・FilterableProductTable
 - SearchBar
    - ProductTable
       - ProductCategoryRow
       - ProductRow

今日はここまで。

Reactガイドを読んでいくその17

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

コンポジション vs 継承

Reactは継承よりもコンポジションを推奨している。
本章では継承を使った場合を例に出してコンポジションではどう解決できるかを示す。

子要素の出力 (Containment)

SidebarやDialogといった汎用的な入れ物を表すようなコンポーネントでは事前に子要素を知らないものがある。
こういったコンポーネントではchildrenというpropsを使って子要素を出力することが可能。

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

上記のような操作をしたことで他のコンポーネントからJSXをネストすることで任意の子要素を渡すことが可能になる。

特化したコンポーネント (Specialization)

コンポーネントを他のコンポーネントの特別なケースとして考える場合がある。
Dialogというコンポーネントがあった時に、WelcomDialogやGoodbyeDialogは特別なケースと言える。
そういった特化したコンポーネントコンポジションで表現すると以下のようになります。以下のケースでは表現していませんが、クラスとして定義されたコンポーネントでも同様の動作をします。

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!" />

  );
}

継承はどうするの?

Facebookでは何千というコンポーネントでReactプロジェクトを運用しているが、継承が推奨されるような場所はまったくない。
推奨というより、継承は使うなに近いニュアンスがあると考えられる。

今日はここまで。

Reactガイドを読んでいくその16

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

state のリフトアップ

stateのリフトアップ

いよいよ本章のテーマにきました。

昨日までの状態ではTemperatureInputは独立してローカルのstateを保持しています。
理想は入力フィールドは同期されていて、片方を入力するともう片方が埋まる状態にしておきたいです。

Reactでstateを共有したい場合は両者の共通の親コンポーネントに移動することで実現が可能。
これをリフトアップと呼ぶ。

TemperatureInputのstateをCalculatorに移動させる。
Calculatorが両者の共通の存在になることで、摂氏変換、華氏変換いずれもアクセスできるようになり、同期されるようになる。

リフトアップを行なっていきます。
TemperatureInputのthis.state.temperatureをthis.props.temperatureに変更。

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    <b>// this.state.temperature</b>
    const temperature = <b>this.props.temperature</b>;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

このままだとpropsは読み取り専用であり、propsであるがゆえにTemperatureInputは制御できない状態になっている。
なので更新のためにthis.props.onTemperatureChangeを呼び出すように修正。

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
  <b>// this.setState({temperature: e.target.value});を変更</b>
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

onTemperatureChangeはCalculatorからtemperatureプロパティと共に渡されるように作ります。
現在のCalculatorは以下のようになっています。

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

temperatureとscaleをローカルなstateに保存するようにします。

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />

        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />

        <BoilingVerdict
          celsius={parseFloat(celsius)} />

      </div>
    );
  }
}

これで両者の同期が取れ、片方を入力すると変換された値がもう一つの方にセットされるようになりました。

今日はここまで。

Reactガイドを読んでいくその15

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

state のリフトアップ

変換関数の作成

昨日の続きです。 摂氏と華氏、いずれかの値に入力があった時に、変換してどちらかの表示をするようにします。

華氏の入力を摂氏に変換
function toCelsius(fahrengeit) {
    return (fahrenheit - 32) * 5 / 9;
}

摂氏の入力を華氏に変換
function toFahrenheit(celsius) {
    return (celsius * 9 / 5) + 32;
}

単純な計算では見栄えが悪い計算結果が変えてくることが考えられるので、
いい感じに四捨五入する機能も追加します。

function tryConvert(tempreature, convert) {
    const input = parseFloat(temperature);
    // コンバートできない場合は空の文字列を返す
    if (Number.isNaN(input)) {
        return ' ';
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
}

明日は仕上げになります。

今日はここまで。

Reactガイドを読んでいくその14

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

state のリフトアップ

2 つ目の入力を追加する

摂氏だけでなく華氏も対応できるようにしていきます。
昨日作ったCalculatorクラスからtemperatureを抜き出してTemperatureInputクラスにします。

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

華氏と摂氏入力を用意します。

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

また、以下のようにレンダーを二つ分用意することで、摂氏と華氏両方のレンダリングが可能になります。

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

ただ、今のままではただ数値が入力できるようになっているだけで片方の数値を入力しても
もう片方は更新されず、また沸騰したかどうかの判断もしません。
次回から両者の値が更新できるように対応ししていきます。

今日はここまで。

Reactガイドを読んでいくその13

これは

Reactのガイドを読んでいく記事です。

ガイドのリンク

ja.reactjs.org

state のリフトアップ

台風前の準備で大変というのと、頭痛がひどいので何回かに分けます。

stateのリフトアップとは一つの値を複数のコンポーネントが参照したい場合の解決策(子コンポーネントの変更を子同士で参照できない)として
リフトアップして親コンポーネント経由で子コンポーネントの変更を参照できるようにする。

se-tomo.com

この章では与えられた数値で水が沸騰するかどうかを判断するアプリを作成します。

まずは100℃で沸騰するような判定をする関数を作成します。

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

Calculator関数を作成する。
ここで入力のレンダーと入力値を保持する。

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />

        <BoilingVerdict
          celsius={parseFloat(temperature)} />

      </fieldset>
    );
  }
}

少ないですが、今日はここまで。