コンポジション 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 でアプリを作る具体的な方法は不明なままだ。

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

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

state のリフトアップ

公式サイトのURLは https://ja.reactjs.org/docs/lifting-state-up.html

複数のコンポーネントが同一の変化するデータを反映する必要がある場合は、最も近い共通の祖先コンポーネントへ共有されている state をリフトアップすること。

えられた温度で水が沸騰するかどうかを計算する温度計算ソフトの作成を通して、この手法を学ぶ。

  • BoilingVerdict コンポーネントを作成
  • 温度 celsiusprops から受け取る
  • 水が沸騰するのに十分な温度か判定表示する
function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}
  • Calculator コンポーネントを作成
  • 温度を入力する <input> 要素を定義
  • 入力された値を this.state.temperature に保持
  • 現在の入力値を判定する Boilingdict をレンダー
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>
    );
  }
}

2 つ目の入力を追加する

  • 温度として華氏を受け取れるようにする
  • Calculator クラスから TemperatureInput コンポーネントを抽出
  • props として c もしくは f の値をとる scale を追加
const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

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>
    );
  }
}

2つの別個の温度入力フィールドをレンダーするように変更する。

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

2つの入力フィールドが用意できた。これあの入力は連動していません。 Calculator クラスは、 TemperatureInput の温度を知らないからです。

変換関数の作成

  • 摂氏から華氏に変換する関数を作成
  • 華氏から摂氏に変換する関数を作成
function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

-文字列 temperature と変換関数を引数に取り文字列を返す関数を作成。小数第 3 位までで四捨五入し、無効な temperature は空の文字列を返す。

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

公式サイトの例をデベロッパーツールの Console タブに入力する。

tryConvert('10.22', toFahrenheit)
"50.396"
tryConvert('abc', toCelsius)
""

stateのリフトアップ

すごく丁寧に解説されている。丁寧なあまりまわりくどくて要点をつかむのに苦労するのは、日本語力も低い私だけだろな。

React は単方向データバインディング。では、複数のコンポーネントを連携して、一方の状態の変化に応じて、他方の状態を変更できないのか?

いや、相互に連携して状態を変更する方法はある。複数のコンポーネントが所属する親要素から制御すればよいのです。その方法が述べられている。

reder()props.temperature を渡します。 state から props に書き換えたことで何が変わるのか?

state : そのコンポーネントが持っている状態 props : 親コンポーネントから渡されたプロパティ

render() {
  const temperature = this.props.temperature;
  // const temperature = this.state.temperature;

まさにリフトアップしたのです。これで親要素から制御する準備が整いました。しかし、 props は読み取り専用です。新たな問題が発生しました。

TemperatureInput は、DOM の <input>valueonChange プロパティの両方を受け取るように、 親コンポーネントから値とイベントを受け取れる。

render() {
handleChange(e) {
  this.props.onTemperatureChange(e.target.value);
  // this.setState({ temperature: e.target.value });
}

TemperatureInput はここまでの変更を反映し次のようになっています。

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    // this.state = { temperature: '' };
  }
  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
    // this.setState({ temperature: e.target.value });
  }
  render() {
    const temperature = this.props.temperature;
    // 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>
    );
  }
}

<fieldset> 要素は HTML フォームの一部をグループ化し、内側の <legend> 要素で <fieldset> のキャプションを提供しています。

Calculator は同じ 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>
    );
  }
}

この章で学んだこと

理解できれば学べたはずの事柄。

  • 変化するデータは単一の"信頼出来る情報源"であるべき
  • 共通祖先コンポーネントにリフトアップすべし
  • state は「トップダウン型データフロー」で設計する
  • リフトアップはバグを減らす

state のリフトアップを学びました。種明かしされればなるほどと理解できたように錯覚してしまいます。

実際にリフトアップが必要なケースになったら何度も読み直すことになること間違いなしです。

全体のコードを知りたい場合は、 Try i on CodePen で知ることができる。

頭が疲れた。今回は短いですが、ここで区切り。

リストと key

リストと key 。まずはリストの変換方法のおさらいからスタートです。

map 関数を使ったコード。

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

配列を要素のリストに変換することが、上記とほぼ同様である。

複数コンポーネントをレンダーする

要素の週語を {} で囲み JSX の中に書くことができる。

配列 numbers の各要素を map<li> でくくり、変数 listItem に返す。

listItem 配列を <ul> 要素ではさみレンダーする。

      const numbers = [1, 2, 3, 4, 5];
      const listItems = numbers.map((number) => <li>{number}</li>);

      ReactDOM.render(<ul>{listItems}</ul>, document.getElementById('root'));

1から5までの数字の箇条書きリストが表示される。

## 基本的なリストコンポーネント

先程の例を numbers 配列を受け取り要素のリストを出力するコンポーネントに書き換える。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

コードを実行するとブラウザに期待した結果が表示されます。

Warning: Each child in a list should have a unique "key" prop.

それと同時に上記の警告も出力されます。ユニークなキーを追加する。

...
const listItem = numbers.map((number) => <li key={number.toString()}>{number}</li>);

key

key は。どの要素が要素が変更、追加もしくは削除されたのかを識別するのに役立ちます。配列内の項目に識別するために、それぞれの項目にユニークな key を与えます。

多くの場合、データ内にある IDkey として使うことになる。

const todoItems = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);

データ設計時はユニークなキーフィールドに id 名を割り当ておくように留意しとこう!

レンダーされる要素に安定したID がない場合は、項目のインデックスを代用する。

const todoItems = todo.map((todo, index) =>
  <li key={index}>
    {todo.text}
  </li>
)

todoItems のようなリストは、並び順が変更されたり、追加登録されたり削除される可能性がある。 このようなリスト要素に変更が加わった場合、変更前の index とj変更後の index は変化シます。

依存するのがどれほど危険かわかりますね。

key でリンクされている Index as a key is an anti-pattern ではユニークID 生成ライブラリとして nanoid が紹介されている。 UUID しか知らなかった。勉強になります!

nanoid

A tiny, secure, URL-friendly, unique string ID generator for JavaScript.

key のあるコンポーネントの抽出

ListItem コンポーネントを抽出する際には、 key は、配列内の <ListItem /> 要素に残しておくべき。

正しい key の使用法

      function ListItem(props) {
        // こで key を指定しない
        return <li>{props.value}</li>;
      }

      function NumberList(props) {
        const numbers = props.numbers;
        const listItems = numbers.map((number) => (
          // key は配列の中で指定せよ
          <ListItem key={number.toString()} value={number} />
        ));
        return <ul>{listItems}</ul>;
      }

      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(<NumberList numbers={numbers} />, document.getElementById('root'));

key は、それを取り囲んでいる配列側で用いる。

key の基本ルールは、map() 呼び出しの中に現れる要素に必要となる。

兄弟要素の中で一意であればよい

key は兄弟要素の中でユニークでなければならない。グローバルではユニークでなくてもよい。

次は sidebarcontent を兄弟要素として持つ Blog コンポーネントです。

      function Blog(props) {
        const sidebar = (
          <ul>
            {props.posts.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </ul>
        );

        const content = props.posts.map((post) => (
          <div key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
          </div>
        ));

        return (
          // sidebar と content は兄弟要素
          <div>
            {sidebar}
            <hr />
            {content}
          </div>
        );
      }

      const posts = [
        { id: 1, title: 'Hello World', content: 'Welcome to learning React!' },
        { id: 2, title: 'Installation', content: 'You can install React from npm.' },
      ];

      ReactDOM.render(<Blog posts={posts} />, document.getElementById('root'));

key は、コンポーネントには渡されない。 React へのヒントとして使われる。同じ値をコンポーネントでも必要となる場合は、別の名前の props として明示的に渡す。

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

Post コンポーネントprops.id は読み取れるが、 props.key は読み取れない。

mapを JSX に埋め込む

map() の結果をインラインとして記述できる。

インライン化部分。

{numbers.map((number) => (
              <ListItem key={number.toString()} value={number} />
            ))}

全体のコード。

      function ListItem(props) {
        return <li>{props.value}</li>;
      }

      function NumberList(props) {
        const numbers = props.numbers;
        return (
          <ul>
            {numbers.map((number) => (
              <ListItem key={number.toString()} value={number} />
            ))}
          </ul>
        );
      }

      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(<NumberList numbers={numbers} />, document.getElementById('root'));

map() がネストするならコンポーネントに抽出することを考慮する。

リストと key の項目はパターンが多い。頭に入り切らない。公式サイトを何度も参照することになること間違いなしです。

フォーム

9.フォーム

フォーム要素は他のDOM要素と異なる動作をする。

HTMLフォームの標準の動作は、フォームを送信した際に新しいページに移動することです。しかし、フォームの送信に応じて入力したデータにアクセスする関数があった方が便利です。

制御されたコンポーネントと呼ばれるテクニックを使うことで、実現できる。

制御されたコンポーネント

<input><textarea><select> のようなフォーム要素は、それ自身で状態を保持している。

React では、変更されうる状態は state プロパティに保持され、 setState() 関数で更新を管理する。

フォーム送信時に名前をログに残す場合を例にコンポーネントを記述する。

      class NameForm extends React.Component {
        constructor(props) {
          super(props);
          this.state = { value: '' };

          this.handleChange = this.handleChange.bind(this);
          this.handleSubmit = this.handleSubmit.bind(this);
        }

        handleChange(event) {
          // 制御された値 this.state.value を setState 関数に渡す
          // フォームに入力された値はキーストローク毎にキャプチャされる
          console.log(`[handleChange event]: ${event.target.value}`);
          this.setState({ value: event.target.value });
        }

        handleSubmit(event) {
          // 制御された値 this.state.value を setState 関数に渡す
          // submit毎にキャプチャされる
          console.log(`[handleSubmit event]: ${this.state.value}`);
          alert('A name was submitted: ' + this.state.value);
          event.preventDefault();
        }

        render() {
          return (
            <form onSubmit={this.handleSubmit}>
              <label>
                Name:
                <input type="text" value={this.state.value} onChange={this.handleChange} />
              </label>
              <input type="submit" value="Submit" />
            </form>
          );
        }
      }

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

<input> 要素から value={this.state.value} と属性値を渡している。これにより React の state が信頼できる情報源となる。

これでフォームの状態と setState() の状態を結合できる。別の言い方をすれば、フォームの状態を React が管理可能になったと理解できそうです。

handleChange はキーストローク毎に実行され、 state を更新する。

React の管理下になった値は、他の UI に渡したり、他のイベントハンドラからリセットできるようになる。

例では、テキストボックスに入力した値を送信ボタンをクリックする毎に alert に使いまわし通知している。

Hooks で書き換える

今記述した NameForm はクラスコンポーネントです。 Hooks で書き換える。

      function NameForm(props) {
        // 文字列は配列です
        const [name, setName] = React.useState([]);

        const handleSubmit = (e) => {
          alert('A name was submitted: ' + name);
          e.preventDefault();
        };

        return (
          <form onSubmit={handleSubmit}>
            <label>
              Name:
              <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }

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

<input> には文字列が入力される。文字列は配列の一種なので、初期値は空の配列にする。

preventDefault();alert より先に書きたい誘惑に駆られます。だが、公式サイトの順番を守っておく。

const handleSubmit = (e) => {
  // e.preventDefault();
  alert('A name was submitted: ' + name);
  e.preventDefault();
};

textareaタグ

<textarea value={}> を使い HTML の <textarea> の代わりとする。単一行の入力フォームと似た書き方ができる。

      class EssayForm extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            value: 'Please write an essay about your favorite DOM element.',
          };

          this.handleChange = this.handleChange.bind(this);
          this.handleSubmit = this.handleSubmit.bind(this);
        }

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

        handleSubmit(event) {
          alert('An essay was submitted: ' + this.state.value);
          event.preventDefault();
        }

        render() {
          return (
            <form onSubmit={this.handleSubmit}>
              <label>
                Essay:
                <textarea value={this.state.value} onChange={this.handleChange} />
              </label>
              <input type="submit" value="submit" />
            </form>
          );
        }
      }

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

constructor 内で this.state.value に文字列を定義シているので、表示されるページには最初からテキストが入っている。

constructor(props) {
  super(props);
  this.state = {
    value: 'Please write an essay about your favorite DOM
element.',
  };

Hooks で書き換える

Hooks への書き換えも随分と慣れてきた」などと思っていたら違った。 EssayForm コンポーネントprops 引数がなくても動作する。いつから勘違いしていたんだ!?

      // const EssayForm = (props) => {
      const EssayForm = () => {
        const [text, setText] = React.useState([
          'Please write an essay about your favorite DOM element.',
        ]);

        const handleSubmit = (e) => {
          alert('An essay was submitted: ' + text);
          event.preventDefault();
        };

        return (
          <form onSubmit={handleSubmit}>
            <label>
              Essay:
              <textarea value={text} onChange={(e) => setText(handleChange)} />
            </label>
            <input type="submit" value="submit" />
          </form>
        );
      };

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

selectタグ

HTML の selected 属性の代わりに、 親の select タグで value 属性を使う。

        constructor(props) {
          super(props);
          this.state = { value: 'coconut' };

          this.handleChange = this.handleChange.bind(this);
          this.handleSubmit = this.handleSubmit.bind(this);
        }

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

        handleSubmit(event) {
          alert('Your favorite flavor is: ' + this.state.value);
          event.preventDefault();
        }

        render() {
          return (
            <form onSubmit={this.handleSubmit}>
              <label>
                Pick your favorite flavor:
                <select value={this.state.value} onChange={this.handleChange}>
                  <option value="grapefruit">Grapefruit</option>
                  <option value="lime">Lime</option>
                  <option value="coconut">Coconut</option>
                  <option value="mango">Mango</option>
                </select>
              </label>
              <input type="submit" value="Submit" />
            </form>
          );
        }
      }

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

<input type="text"><textarea><select> の3つは、 value 属性を受け取る。

複数の入力の処理

複数の input 要素を処理する場合、各入力要素に name 属性を追加し、ハンドラに event.target.name に基づいた処理を選択できる。

      class Reservation extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            isGoing: true,
            numberOfGuests: 2,
          };

          this.handleInputChange = this.handleInputChange.bind(this);
        }

        handleInputChange(event) {
          const target = event.target;
          const value = target.type === 'checkbox' ? target.checked : target.value;
          const name = target.name;

          this.setState({
            [name]: value,
          });
        }

        render() {
          return (
            <form>
              <label>
                is Going:
                <input
                  name="isGoing"
                  type="checkbox"
                  checked={this.state.isGoing}
                  onChange={this.handleInputChange}
                />
              </label>
              <br />
              <label>
                Number of guests:
                <input
                  name="numberOfGuests"
                  type="number"
                  value={this.state.numberOfGuests}
                  onChange={this.handleInputChange}
                />
              </label>
            </form>
          );
        }
      }

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

次の [] を用いたプロパティ名は Computed property names と呼ぶ。

// Computed property name 構文(計算されたプロパティ名)
this.setState({
  [name]: value,
});

ES5 における次のコードと同じ。

var partialState = {};
partialState[name] = value;
this.setState(partialState);

JavaScript Primer では、 プロパティの追加 で説明されている。

式の評価結果をプロパティ名に使う

MDN では 計算されたプロパティ名 で説明されている。

括弧 [] の中に式を記述でき、それが計算されてプロパティ名として使用されます。

この構文のことをすっかり忘れていた。

私のモダンJavaScript戦闘力は低い!モダンJavaScriptの速習が改めて必要です。

本格的なソリューション の中で Formik が紹介されている。 フォームはユーザ入力を受け付ける窓口だ。やはりきちんと作り込まれたライブラリがある。

日本語の情報だと CodeZineReact向けライブラリを解説 が 2020/12/07 で比較的新しい記事を読むことができる。

イベント処理

イベント処理

Reactのイベント処理は DOM 要素のイベントの処理と似ているが、イベント処理文法的な違いがある。

  • camelCaseで名付ける
  • JSX では関数を渡す
  • デフォルト動作の抑止に preventDefault を使う

HTML

<button onclick="activeLases()">
  Active Lasers
</button>

React

<button onClick={activeLases}>
  Active Lasers
</button>

デフォルトの動作を抑止するには、 preventDefault が必須。新しいページを開くというリンクの動作を例に考えてみる。

React は既存のプロジェクトに徐々に追加していけるのでした。今回は、既存の書き方に React を追加してみる。 HTML

    <div id="root"></div>
    <a
      href="https://github.com/facebook/react"
      onclick="console.log('The link was clicked.'); return true"
      >Click me: Vanilla JavaScript case ture</a
    >
    <br />
    <a
      href="https://github.com/facebook/react"
      onclick="console.log('The link was clicked.'); return false"
      >Click me: Vanilla JavaScript case false</a
    >
    <script type="text/babel">
      function ActionLink() {
        function handleClick(e) {
          e.preventDefault();
          console.log('The link was clicked.');
        }

        return (
          <a href="https://github.com/facebook/react" onClick={handleClick}>
            Click me: React.js
          </a>
        );
      }

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

e は合成イベント。ブラウザ間の互換性を心配する必要はない。ネイティブのイベントと React のイベントは同様に動作するのではない。

理解できていない。初心者は細かいことを気にせず、 e を使えばいいだろう。

要素が最初にレンダーされる際にリスナを指定する。

一般的なパターンではイベントハンドラはクラスのメソッドになる。

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isToggleOn: true };
    // このバインディングは、コールバックで `this`を機能させるために必要
    this.handleClick = this.handleClick.bind(this);
    // this.handleClick = handleClick;
    console.log(this);
  }
  // Event listener
  handleClick() {
    this.setState((prevState) => ({
      isToggleOn: !prevState.isToggleOn,
    }));
  }
  render() {
    // onClick属性にイベントを添付
    return <button onClick={this.handleClick}>{this.state.isToggleOn ? 'ON' : 'OFF'}</
button>;
  }
}
ReactDOM.render(<Toggle />, document.getElementById('root'));

クラスのメソッドはデフォルトではバインドされない。

bind 呼び出しを回避する方法の1つは、クラスフィールドを使うこと。

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

パブリッククラスフィールド

パブリックスタティックフィールドとパブリックインスタンスフィールドは、書き込み可能、列挙可能、設定可能なプロパティです。

を回避する方法の2つめは、コールバック内でアロー関数を使うこと。

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

コールバックが props の下層のコンポーネントに渡される場合、余分に再描画される。コンストラクタでバインドするかクラスフィールド構文を使用して問題を避けること。

useStat を使う

ここまで見てきた this の問題は hooks の1つである useState を使って回避できる。クラスコンポーネントでは、オブジェクトであった状態管理が、 hooks を使うことによって、変数とこの変数を更新する専用関数になるからです。

const Toggle = () => {
  const [toggle, setToggle] = React.useState(true);
  return (
    <div>
      <button onClick={() => setToggle(!toggle)}>{toggle ? 'ON' : 'OFF'}</
button>
    </div>
  );
};
ReactDOM.render(<Toggle />, document.getElementById('root'));

私は初心者なので、 hooks を使えば、本当にパフォーマンスの問題を避けられているのかわからない。

イベントハンドラに引数を渡す

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上記の 2 行は等価である。

React イベントを表す引数 eID の 2 番目の引数として渡される。 bind の場合には id 以降の引数は自動的に転送される。

Function.prototype.bind が使われている下行に e がないのに引数として渡されることに違和感しかありません。 Rest parameters (残余引数) の形をとっていれば納得もできるのですが...

条件付きレンダー

条件付きレンダーJavaScript における条件分岐と同じように動作する。

if や条件演算子を使って現在の状態を表す要素を作成すれば、 React は一致するように UI を高維新する。

以下の 2 つのコンポーネントで考える。

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

ユーザがログインしているかどうかによって、これらのコンポーネントの一方だけを表示する Greeting コンポーネントを作成する。

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
ReactDOM.render(<Greeting isLoggedIn={true} />, document.
getElementById('root'));
// ReactDOM.render(<Greeting isLoggedIn={false} />, document.
getElementById('root'));

要素変数

要素を保持しておくために変数を使うことができる。出力の他の部分を変えずにコンポーネントの一部を条件付きでレンダーしたい時に役立つ。

ログアウトとログインボタンを表す2つのコンポーネントで考える。

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

LoginControl というステート付きコンポーネントを作成する。

LoginControl コンポーネントは、現在の state によって <LoginButton /> または <LogoutButton /> の一方をレンダーする。前の例の <Greeting /> もレンダーする。

      class LoginControl extends React.Component {
        constructor(props) {
          super(props);
          this.handleLoginClick = this.handleLoginClick.bind(this);
          this.handleLogoutClick = this.handleLogoutClick.bind(this);
          this.state = { isLoggedIn: false };
        }

        handleLoginClick() {
          this.setState({ isLoggedIn: true });
        }

        handleLogoutClick() {
          this.setState({ isLoggedIn: false });
        }

        render() {
          const isLoggedIn = this.state.isLoggedIn;
          let button;

          if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick} />;
          } else {
            button = <LoginButton onClick={this.handleLoginClick} />;
          }

          return (
            <div>
              <Greeting isLoggedIn={isLoggedIn} />
              {button}
            </div>
          );
        }
      }

      function UserGreeting(props) {
        return <h1>Welcome back!</h1>;
      }

      function GuestGreeting(props) {
        return <h1>Please sign up.</h1>;
      }

      function Greeting(props) {
        const isLoggedIn = props.isLoggedIn;
        if (isLoggedIn) {
          return <UserGreeting />;
        }
        return <GuestGreeting />;
      }

      function LoginButton(props) {
        return <button onClick={props.onClick}>Login</button>;
      }

      function LogoutButton(props) {
        return <button onClick={props.onClick}>Logout</button>;
      }

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

条件付きでレンダーの核心部分は以下の通り。

if (isLoggedIn) {
  button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
  button = <LoginButton onClick={this.handleLoginClick} />;
}

JSXでインラインで条件を記述するより短い構文がある。

論理 && 演算子によるインライン If

JSX に式として埋め込むに {} で囲むこと。条件に応じて要素を含めたいときに便利。

      const Mailbox = ({ unreadMessages }) => {
        const messages = unreadMessages;
        return (
          <div>
            <h1>Hello!</h1>
            {messages.length > 0 && <h2>You have {messages.length} unread messages.</h2>}
          </div>
        );
      };

      const messages = ['React', 'Re: React', 'Re:Re: React'];
      // const messages = [];
      ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));
  • true && expression --> 必ず expression が評価される
  • false && expresiion --> 必ず false が評価される
  • falsy な値の場合、 && の後の評価はスキップするが、 falsy な値そのものは返される

falsyな値は何があったかな?私を モダンJavaScript の世界に羽ばたかせてくれた JavaScript Primer のここを読み返せば解決です。

論理演算子

インライン If-Else

条件的に要素をレンダーするもうひとつの方法は condition ? true : false 条件演算子を利用すること。

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

三項演算子ですね。短く書けるので便利に使っています。

条件が複雑すぎると考えたら、コンポーネントを抽出するタイミングかもしれないとのこと。

重要な事柄がさらっと紹介されている。

コンポーネントのレンダーを防ぐ

コンポーネントが他のコンポーネントによりレンダーされているが、自コンポーネントを隠すには、レンダー出力の代わりに null を返す。

WarningBanner コンポーネントwarn プロパティの値に応じて、プロパティの値が false ならレンダーされない。

コンポーネントrender から null を返してもライフサイクルに影響はしない。

コードが随分長くなってきました。たまには、スタイリングもしてみましょう。

CodePen から CSS を拝借。

      const button = {
        height: '40px',
        width: '200px',
      };
      const warning = {
        backgroundColor: '#ff0000',
        textAlign: 'center',
        width: '100%',
        padding: '10px',
      };

      function WarningBanner(props) {
        if (!props.warn) {
          return null;
        }

        return (
          <div className="warning" style={warning}>
            Warning!
          </div>
        );
      }

      class Page extends React.Component {
        constructor(props) {
          super(props);
          this.state = { showWarning: true };
          this.handleToggleClick = this.handleToggleClick.bind(this);
        }

        handleToggleClick() {
          this.setState((prevState) => ({
            showWarning: !prevState.showWarning,
          }));
        }

        render() {
          return (
            <div>
              <WarningBanner warn={this.state.showWarning} />
              <button onClick={this.handleToggleClick} style={button}>
                {this.state.showWarning ? 'Hide' : 'Show'}
              </button>
            </div>
          );
        }
      }

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

stateとライフサイクル

クラスに変換

5ステップで関数コンポーネントをクラスコンポーネントに変換する。

https://ja.reactjs.org/docs/state-and-lifecycle.html#converting-a-function-to-a-class

      function Clock(props) {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {new Date().toLocaleTimeString()}.</h2>
          </div>
        );
      }
  1. React.Componet を継承する同名の ES6 クラスを作成
  2. render() と呼ばれる空のメソッドを 1 つ追加
  3. 関数の中身を render() メソッドに移動
  4. render() 内の propsthis.props に書き換え
  5. 空になった関数を削除
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString
()}.</h2>
      </div>
    );
  }
}

同一の DOM ノード内で Clock コンポーネントをレンダーしている限り、 Clock クラスのインスタンスは1つだけ使われる。

ローカル state やライフサイクルメソッドなどが利用できるようになる。

state を追加する

3ステップで dateprops から state に移す。

  1. render() メソッド内の this.props.datethis.state.date に書き換え
  2. this.state の初期状態を設定するコンストラクタを追加
  3. <Coloc /> 要素から date プロパティを削除
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

現在の時刻と共に「更新されない」ページが表示された。

ふとした疑問が、「現在の時刻」とは、 DOMContentLoaded イベントだろうか?または、 load イベントだろうか?

初心者な私は確認方法がわからない。

ライフサイクルメソッドを追加

マウント : 最初にコンポーネントが DOM として描画されるとき。出力が DOM にレンダーされた「後」に実行される アンマウント : コンポーネントが生成した DOM が削除されるとき

  componentDidMound() {
    //
  }
  componentWillUnmound() {
    //
  }

タイマーをセットアップするのは、 componentDidMound() ライフサイクルメソッド内です。

componentDidMount() {
  this.timerID = setInterval(
    () => this.tick(),
    1000
  );
}

タイマーの後片付けは componetWillUnmount() ライフサイクルメソッド内で行う。

componentWillUnmound() {
  clearInerval(this.timerID)
}

コンポーネントのローカル state 更新を行うために this.setState() を追加する。

tick() {
  this.setState({
    date: new Date(),
  });

毎秒ごとに時間を刻み続ける。

メソッドが呼び出される順序にそって簡単に振り返ってみましょう https://ja.reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class

丁寧な解説がなされている。コンポーネントの一生について疑問が生じた際は、読みにいくことになりそう。

state を正しく利用する

setState() の3つの重要な事柄

state を直接変更しない

this.state.comment = 'Hello';

代わりに setState() を使う

this.setState({comment: 'Hello'})

時計の例では次のように使用しました。

  tick() {
    this.setState({
      date: new Date()
    });
  }

でも待ってください。 constructor では、次のように書いています。

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

初期化時は例外なのだろうか?

this.state に直接代入してよい唯一の場所はコンストラクタです。

読み落とすところでした。

更新は非同期に行われる可能性がある

this.propsthis.state は非同期に更新されるため、それらの値に依存してはならない。

更新に失敗することのあるコード

this.setState({
  counter: this.state.counter + this.props.increment,
});

オブジェクトではなく、関数を受け取る setState() を使う。

setState()

  • 第1引数:直前の state
  • 第2引数:更新が適用される時点での props
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

更新はマージされる

setState() を呼び出した場合、与えられたオブジェクトを現在の state にマージする。

JavaScript Primer で学んだ オブジェクトのマージ 。ほとんど理解できなかった嫌な記憶が蘇った。

別々の setState() 呼び出しで、変数を独立して更新できる。

componentDidMount() {
  fetchPosts().then(response => {
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    })
  })
}

以上が setState() の3つの重要な事柄だそうです。

データは下方向に伝わる

コンポーネントはその子コンポーネントprops として自身の state を渡してよい。

<FormattedDate date={this.stae.date} />

FormattedDate コンポーネントprops 経由で date を受け取るが、Clock クラスの state から来たのか、 props から来たのかわからない。

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>
}

単一方向 とか 単一方向データバインディング と呼ばれる。

3つの<Clock> コンポーネントをレンダーする App コンポーネントを作成。

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

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

<Clock /> はそれぞれインスタンスを作るので、独立はしているはず。カプセル化もされているはず。

ステートレスなコンポーネントをステートフルなコンポーネントの中で使うことが可能であり、その逆も同様です。

時代は Functionコンポーネント

React は現時点では、 Hooks を使って Functionコンポーネントを定義することが一般的です。

Clock クラスコンポーネントをファンクションコンポーネントに書き換えに挑戦します。

時計の現在の時刻と、それを更新するために useStae が必要になります。

useState

ステートフルな値と、それを更新するための関数を返します。

const [date, setDate] = React.useState(new Date());

useEffect

副作用を有する可能性のある命令型のコードを受け付けます。 DOM の書き換え、データの購読、タイマー、ロギング、あるいはその他の副作用を、関数コンポーネントの本体(React のレンダーフェーズ)で書くことはできません。 代わりに useEffect を使ってください。

useEffect 時とともに DOM の書き換えが必要なタイマーのためにある。間違いない!

tick() 関数を1秒間隔で呼び出します。

React.useEffect(() => {
  const timerID = setInterval(() => tick(), 1000);

Clock コンポーネントDOM から削除される場合を考慮した処理を追加します。

公式サイトで指摘されなければ、削除処理が必要なことに気づかないだろうな!

  return function cleanup() {
    clearInterval(timerID);
  };

tick() 関数を実装します。現在時刻の状態を更新する必要があるので、 setDate() を利用する。

const tick = () => setDate(new Date());

Clock クラスでは、レンダーの中では statedate 属性でした。

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

Clock ファンクションでは、 date は次のように書くことができます。 dateuseState() で定義されたので、 ステートそのものだからでしょうか?

わかっていない。

<h2>It is {date.toLocaleTimeString()}.</h2>

完成したコードを掲載する。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const Clock = () => {
        const [date, setDate] = React.useState(new Date());

        // componentDidMountとcomponentWillUnmountを置き換え
        React.useEffect(() => {
          const timerID = setInterval(() => tick(), 1000);
          return async function cleanup() {
            await clearInterval(timerID);
          };
        });

        // 更新関数に現在の時刻を渡す
        const tick = () => setDate(new Date());

        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {date.toLocaleTimeString()}.</h2>
          </div>
        );
      };

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

要素のレンダー

要素のレンダー

要素のレンダー

要素とは React アプリケーションの最小単位の構成ブロックです。

要素は画面に表示したいものの説明書き。

<h1>Hello, world</h1>

React 要素はプレーンなオブジェクト、安価に作成できる。安価というのは、素早くハードの省資源で作れるという意味。

描画する

React だけで構築されたアプリケーションは、ルート DOM ノードは1つ。

既存アプリケーションと React の併用の場合は、ルート DOM ノードは複数持てる。

React 要素とルート DOM ノードをレンダーに渡す。

    <div id="root"></div>
    <script type="text/babel">
      const element = <h1>Hello, world</h1>;
      ReactDOM.render(element, document.getElementById('root'));

レンダリングされたページを inspect で調べると <div></div> の間に <h1>Hello, world</h1> が挿入されています。

要素の更新

React 要素はイミュータブル。生成後は、子要素、属性を変更できない。特定の時点のUIを表す。

更新するには、新しい要素を作成して ReactDOM.render() に渡す必要がある。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

秒刻みで表示する時分秒のみが更新される。

任意の時点で UI がどのように見えるべきかを考える。バグを排除することができる。

コンポーネントと props

コンポーネントと props

props を受け取り、React 要素を返す。

関数コンポーネントとクラスコンポーネント

コンポーネント定義の最もシンプルな方法は、関数を書くこと。

function Welcome(props) {
  return <h1>Hellom, {props.name}</h1>
}

データの入ったprops オブジェクトを引数に取り、 React 要素を返す。

ES6クラスを使用してコンポーネントを定義することもできる。

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

コンポーネントのレンダー

ユーザ定義のコンポーネントもReact 要素です。

const element = <Welcome name="Sara" />

属性と子要素を単一のオブジェクトとしてコンポーネントに渡す。このオブジェクトを props と呼ぶ。

const Welcome = ({ firstName, lastName }) => {
  return (
    <h1>
      Hello, {firstName} {lastName}
    </h1>
  );
};
const element = <Welcome firstName="Sara" lastName="Conner" />;
ReactDOM.render(element, document.getElementById('root'));

Hello, Sara Conner と表示される。

  1. 要素 <Welcome firstName="Sara" lastName="Conner" /> を引数として ReactDOM.render() を呼び出す
  2. React は Welcome コンポーネントを呼び出し、 props として {firstName: 'Sara', lastName: 'Conner'} を渡す
  3. コンポーネントは出力 <h1>Hello, Sara Conner</h1> を返す
  4. ReactDOM はコンポーネント出力に一致するように、DOM を更新

組み合わせる

コンポーネントは他のコンポーネントを参照できる。

Welcome コンポーネントを複数回参照し、レンダーする App コンポーネントを作成する。

const Welcome = ({ firstName, lastName }) => {
  return (
    <p>
      {firstName} {lastName}
    </p>
  );
};
const App = () => {
  return (
    <div>
      <Welcome firstName="Elijah" lastName="Wood" />
      <Welcome firstName="Ian" lastName="McKellen" />
      <Welcome firstName=" Orlando" lastName="Bloom" />
      <Welcome firstName="Alan" lastName="Howard" />
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));

抽出

コンポーネントを分割する。

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar" src={props.author.avatarUrl} 
alt={props.author.name} />
        <div className="UserInfo-name">{props.author.name}</
div>
      </div>
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</
div>
    </div>
  );

Avator を抽出する。

function Avator(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

UserInfo を抽出する。

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

すっきりした 'Commnet` コンポーネント

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

コード全体

function formatDate(date) {
  return date.toLocaleDateString();
}
/* Avatar Component */
function Avatar(props) {
  return <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} />;
}
/* UserInfo Component */
function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">{props.user.name}</div>
    </div>
  );
}
/* Comment Component */
function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</div>
    </div>
  );
}
/* comment */
const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment date={comment.date} text={comment.text} author={comment.author} />,
  document.getElementById('root')
);

かわいいネコちゃんの画像とともに文字列と年月日が表示される。画像がかわいいとコードの凶悪さが倍増です。

コードを読み取りに挑戦!

function Avatar(props) {
  return (
    <img
      className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

Avatar コンポーネントは、画像を表示する。画像 URLは {props.user.avatarUrl}alt属性{props.user.name}

ここの user は、UserInfo コンポーネント<Avatar user={props.user} /> としているから。

実際は, comment オブジェクト

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};

author: { name: 'Hello Kitty', avatarUrl: 'https://placekitten.com/g/64/64'} が代入される。

ブラウザの inspect で確認すると <img class="Avatar" src="https://placekitten.com/g/64/64" alt="Hello Kitty"> となっている。

UserInfo コンポーネントuser={props.user} とすると comment オブジェクトの author と紐づくのか?

Comment コンポーネント<UserInfo user={props.author} /> としているからです。

そのまま author を使えば良いのにと考えますが、公式で全否定されます。

コンポーネントが使用される文脈ではなく、コンポーネント自身からの観点で props の名前を付けることをお勧めします。 https://ja.reactjs.org/docs/components-and-props.html#extracting-components

私なら、 <UserInfo avatar={props.author} /> とするだろうな。

度し難し!

comment オブジェクトの authoruser に置き換わるところを理解できれば、その他のコード理解は難しくない。めんどくさいだけ!

再利用できるコンポーネントをパレットとして持っておくことは、アプリケーションが大きくなれば努力に見合った利益を生みます。

逆に初心者が数時間で作れるようなアプリケーションならこだわることはないとも読めますね。

Props は読み取り専用

コンポーネントは、自身の props を変更してはいけない。

純粋関数 : 同じ入力に対し同じ結果を返す関数のこと

function sum(a, b) {
  return a + b;
}

以下の関数は純粋関数ではない。

function withdraw(account, amount) {
  account.total -= amount
}

React コンポーネントは、自己の props に対して純関数のように振る舞わねばなりません。 https://ja.reactjs.org/docs/components-and-props.html#props-are-read-only

state とライフサイクル

秒刻みの時計の例では、 ReactDOM.render() を呼び出し、 UI を更新した。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(    element,    document.getElementById('root')  );}

setInterval(tick, 1000);

再利用可かつカプセル化された Clock コンポーネントを定義する方法を学ぶ。

ます時計の見た目をカプセル化する。

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Clock がタイマーを設定して UI を更新するという処理は、カプセル化されるべき。

Clock コンポーネント自身に更新させたい

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

これを実装するには Clock コンポーネントstate を追加する必要がある。

state

state には、そのコンポーネント固有のデータが含まれており、これは時間の経過とともに変化する可能性があります。state はユーザ定義のものであり、プレーンな JavaScript オブジェクトでなければなりません

React Getting Started

Reactの [Main Consepts] をなぞって学ぶ。 Getting Started から始める。

試す

  • 既存のプロジェクトに徐々に追加可能
  • どの程度利用するか選択可能

オンラインエディタで試せる

最初の例

最初の例として、React のホームページのサンプルを試せる。

  <HelloMessage name="Taylor" />,

実際に試した人はいるのかな?

Taylor の文字を書き換えると右側のRESULTの出力結果が変わる。

ここに日本語を入力しようとするとキーボードの操作をReactが検出するのだろうか? 日本語変換より前にキーがフックされる。

この動作を回避するにはどうしたら良いのか?
class を用いずに function の場合はどう書くのか?

興味倍増です!

私は、コンセプトから学びたいので、ガイドからはじめる。

準備として、用意されたHTMLファイルをダウンロードしておく。

Hello World

まずはHello World から始めます。

Reactのいちばん短い例

    <div id="root"></div>
    <script type="text/babel">
      ReactDOM.render(<h1>Hello, world!</h1>, document.getElementById('root'));
    </script>
  </body>

準備として用意したファイルのままですね。

render()

ReactDOM.render(element, container[, callback])

element は、 <h1>Hello, world!</h1>

container は、 id="root"

<div id="root"></div><h1> タグでマークアップした Hello, world! を描画してると理解できる。

このリファレンス 項目の補足は重要じゃないだろうか?

DOM 差分アルゴリズムを使用します。

これが Virtual DOM の革命で「すごい」と騒がれたとか?

もっとも重要なことは

ReactDOM.render() を使用することは非推奨となり、React 17 では削除されます。代わりに hydrate() を使用してください。

日本語の Issue ページに質問を投げたら

SSR自体がやや上級者向けの話であり、少なくとも Hello World で触れるようなものではありません。SSR を使わずにブラウザで動くアプリを書く場合、ReactDOM.render() を使い続けて全く問題ありません。

さらに

将来的に並列モードがデフォルトになると書き方がまた微妙に変わるのですが

と回答を頂いた。

JSXの導入

JSX とは JavaScript の構文拡張

  • UI の見た目を記述する
  • JavaScript の全機能を備える
  • React要素を生成する
const element = <h1>Hello, world!</h1>;

生成された React 要素をさらに DOM に変換する必要がある。

表示のためのロジックは、状態の変化、データの準備などは結合する。

粗結合とか関心の分離などの考え方とは相容れない?

マークアップとロジックを両方含む疎結合の「コンポーネント」という単位を用いて関心を分離]します。

React を使うならコンポーネントの理解が必要なのですね。

式を埋め込む

変数を {} で囲む。

      const name = 'React JSX';
      const element = <h1>Hello, {name}</h1>;
      ReactDOM.hydrate(element, document.getElementById('root'));

JavaScript関数の結果を埋め込む。

      const formatName = ({ firstName, lastName }) => {
        const initialName = firstName.charAt(0).toUpperCase() + firstName.slice(1);
        return initialName + ' ' + lastName;
      };

      const user = {
        firstName: 'harper',
        lastName: 'perez',
      };

      const element = <h1>Hello, {formatName(user)}!</h1>;

      ReactDOM.hydrate(element, document.getElementById('root'));

firstName の1文字目が大文字に変換されて出力されます。

hydrate() を使っていると Warning が発生した。

Warning: Expected server HTML to contain a matching <h1> in <div>. at h1

<div><h1>Hello, {formatName(user)}!</h1></div>

<div> を使っても同じ警告!解決方法はわからない。

JSXは式である

コンパイルの後、JSX の式は普通の JavaScript の関数呼び出しに変換され、JavaScript オブジェクトへと評価されます。 https://ja.reactjs.org/docs/introducing-jsx.html#jsx-is-an-expression-too

      const user = {
        firstName: 'harper',
        lastNmae: 'perez',
      };

      const formatName = ({ firstName, lastNmae }) => {
        return firstName.toUpperCase() + ' ' + lastNmae;
      };
      const getGreeting = (user) =>
        user ? <h1>Hello, {formatName(user)}!</h1> : <h1>Hello, Stranger.</h1>;

      const element = getGreeting();
      // const element = getGreeting(user);

      ReactDOM.hydrate(element, document.getElementById('root'));

ブラウザには Hello, Stranger. と出力されます。

変数に代入したり、引数として受け取ったり、関数から返したりすることができる

JavaScript として実行できることは JSX としてできるということ。

属性を指定する

文字列リテラルを属性として指定するには引用符 "" で囲む。

const  element = <div  tabIndex="0"></div>;

属性に JavaScript 式を埋め込むために中括弧で囲む。

const element = <img src={user.avatarUrl></img>;

プロパティ命名規則はキャメルケースを用いる。 className , firstName

子要素を指定

空タグは、 /> で閉じる。

const element = <img src={user.avatarUrl}> />

タグは子要素を持つことができる。

const element = (
<div>
  <h1>Hello!</h1>
  <h2>Good to see you here.</h2>
</div>
)

インジェクション攻撃を防ぐ

JSX にユーザの入力を埋め込むことは安全です

const title = response.```
potentiallyMaliciousInput;
const element = <h1>{title}</h1>

インジェクション攻撃。名前は聞いたことがある。でも何もわからない。

インジェクション攻撃 とは

インジェクション攻撃とは<サイバー攻撃の一種で、文字入力を受け付けるプログラムに対して攻撃者がスクリプト文を送って想定外の動きをさせたり、システムを抜き取ったりする攻撃のこと。

レンダー前にエスケープします。...レンダーの前に全てが文字列に変換されます。

安全なので安心して「使って」と覚えておく。

オブジェクトの表現である

Babel でコンパイルされ React.createElement() に書き換わる。

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
)

Babel でコンパイル

const element = ReactElement(
  'h1',
  {calssName: 'greeting'},
  'Hello, world!'
)

これは等価である。

そしてReact要素オブジェクトが爆誕!

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};