React入門その③ stateとprops

2019-12-29

前回から新たに学んだことの自分用メモです。

前回とほぼ同じ機能の簡単なアプリケーションです。前回のものに比べて、「 この選択肢は既に存在します。 」というメッセージが出る機能を追加したくらいです。

新たに学んだ事

  • Reactコンポーネント
    • React.Component から派生したコンポーネントを作る
    • コンポーネントは入れ子にできる。
  • React props(プロパティ)
    • 子となるコンポーネントに値や関数を渡す(読み取り専用)
    • 関数が実行できるよう親コンポーネントの contructor で bind が必要
      • この書き方は見苦しくて苦痛。回避策あり。
  • state
    • props と似ているがコンポーネントの内部で制御される。
    • setStateを呼び出すと (非同期で) 新しい値でレンダー(描画)される。
  • Stateless functional component(関数コンポーネント
    • コンポーネントが状態を持たない場合(例:決まったヘッダーを表示するだけのコンポーネント)は、 React.Componen から派生したクラスのコンポーネントを使わず、const Example =(props) => { return (…); } という形式を用いることができる。
  • defaultProps
    • props(プロパティ)の初期値を設定する。
    • state の初期値として props を指定し、その初期値を defaultProps で指定する方法がある。

コード

class IndecisionApp extends React.Component {
  constructor(props) {
    super(props);
    this.handleDeleteOptions = this.handleDeleteOptions.bind(this);
    this.handlePick = this.handlePick.bind(this);
    this.handleAddOption = this.handleAddOption.bind(this);
    this.state = {
      options: props.options
    };
  }
  handleDeleteOptions() ({
    this.setState(() => { options: [] }));
  }

  handlePick() {
    const randomNum = Math.floor(Math.random() * this.state.options.length);
    const option = this.state.options[randomNum];
    alert(option);
  }

  handleAddOption(option) {
    if (!option) {
      return "適切な値を入力してください";
    } else if (this.state.options.indexOf(option) > -1) {
      return "この選択肢は既に存在します。";
    }

    this.setState(prevState => ({
      options: prevState.options.concat(option)
    }));
  }

  render() {
    const subtitle = "あなたの人生をコンピュータに委ねる。";

    return (
      <div>
        <Header subtitle={subtitle} />
        <Action
          hasOptions={this.state.options.length > 0}
          handlePick={this.handlePick}
        />
        <Options
          options={this.state.options}
          handleDeleteOptions={this.handleDeleteOptions}
        />
        <AddOption handleAddOption={this.handleAddOption} />
      </div>
    );
  }
}

IndecisionApp.defaultProps = {
  options: []
};

const Header = (props) => {
  return (
    <div>
      <h1>{props.title}</h1>
      {props.subtitle && <h2>{props.subtitle}</h2>}
    </div>
  );
}

Header.defaultProps = {
  title: "優柔不断"
};

const Action = (props) => {
  return (
    <div>
      <button
        onClick={props.handlePick}
        disabled={!props.hasOptions}
      >
        私はどうすればいい?
      </button>
    </div>
  );
}

const Options = (props) => {
  return (
      <div>
        <button onClick={props.handleDeleteOptions}>すべて削除</button>
        <ol>
          {props.options.map(option => (
            <Option key={option} optionText={option} />
          ))}
        </ol>
      </div>
    );
}

const Option = (props) => {
  return (
    <div>
      <li>{props.optionText}</li>
    </div>
  );
}

class AddOption extends React.Component {
  constructor(props) {
    super(props);
    this.handleAddOption = this.handleAddOption.bind(this);
    this.state = {
      error: undefined
    };
  }
  handleAddOption(e) {
    e.preventDefault();

    const option = e.target.elements.option.value.trim();
    const error = this.props.handleAddOption(option);

    this.setState(() => ({ error }));
  }
  render() {
    return (
      <div>
        {this.state.error && <p>{this.state.error}</p>}
        <form onSubmit={this.handleAddOption}>
          <input type="text" name="option" />
          <button>選択肢の追加</button>
        </form>
      </div>
    );
  }
}

ReactDOM.render(<IndecisionApp />, document.getElementById("app"));