2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

初めてのXHP Tutorial / XHPとReact

Posted at

XHPをやってみよう!

今回はReactのTutorialで使われているものを、HackのXHPを利用して、その差を紹介します。
JSのコード(Reactなど)を吐き出す訳ではありませんので、
onClickなどのインタラクティブな要素はなく、
あくまで描画のみとなります。

ShoppingList

Reactのチュートリアルの概要で紹介されている例です。
JSXの例です。

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

これをXHPとして記述します。

<?hh // strict

class :ShoppingList extends :x:element {
  attribute string name @required;
  protected function render(): \XHPRoot {
    return <div class="shopping-list\">
        <h1>Shopping List for {$this->:name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>;
  }
}

PHPに慣れているとcomposer.jsonでPSR-4を利用しているため、
ディレクトリ、ファイル名を合わせる必要がありますが、
Hackの場合は hhvm/hhvm-autoload を利用して、
XHP関係のものはオートローダーが吸収しますので、意識する必要はありません。
また、一ファイルに複数のXHPクラスが存在していても問題ありませんので、
Reactと同じ感覚で実装します。

描画は下記で行います。

<?hh // strict

require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function run(): void {
  echo <ShoppingList name="ytake"/>;
}

出力結果はこの通りです。

スクリーンショット 2018-12-09 23.04.02.png

JSでの操作などを行う訳ではないためclassNameなどはありませんが、
基本的なところはほぼ同等というのがわかると思います。
Attributeの記述方法は次の通りです。

attribute <type> <name> [= default value|@required];

Attributeなど、Hackに対応しているIDEでは基本的にすべて補完等できますので、
簡単に理解できると思います。

XHPはasyncを使って描画などを行うことはできますが、あくまでI/Oが発生するものに対してとなりますので、
利用ケースとしてはHTTPリクエストや、DBの処理などと組み合わせます。

同じ出力結果になりますが、下記の様に記述することもできます。

<?hh // strict

require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function run(): void {
  $div = <div />;
  $div->addClass('shopping-list');
  $name = 'ytake';
  $header = <h1>Shopping List for {$name}</h1>;
  $div->appendChild($header);
  $list = <ul />;
  $v = vec['Instagram', 'WhatsApp', 'Oculus'];
  foreach ($v as $item) {
    $list->appendChild(<li>{$item}</li>);
  }
  $div->appendChild($list);
  echo $div;
}

ほとんどの場合では後者で記述することはないと思います。

tic-tac-toe / 三目並べ

次に三目並べですが、XHPでは描画までとなりますので、
描画部分のみをXHPで記述してみましょう。
Reactの例と同じ様に下記の三つのコンポーネントを用意します。

  • Square
  • Board
  • Game

Square

JSXでの記述は次の通りです。

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

XHPで記述してもそこまで大差はありません。
ここでは描画部分のみを記述しますので、valueは必須にしておきましょう。

<?hh // strict

class :Square extends :x:element {
  attribute int value @required;
  protected function render(): \XHPRoot {
    return <button class="square">
        {$this->:value}
      </button>;
  }
}

Board

次にboardです。

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

これに対してXHPです。
これもそこまで大きな差はありません。

class :Board extends:x:element {
  const string STATUS = 'Next player: XHP';

  private function renderSquare(int $i): :Square {
    return <Square value={$i} />;
  }

  protected function render(): \XHPRoot {
    return <div>
        <div class="status">{self::STATUS}</div>
        <div class="board-row">
          {$this->renderSquare(0)}
          {$this->renderSquare(1)}
          {$this->renderSquare(2)}
        </div>
        <div class="board-row">
          {$this->renderSquare(3)}
          {$this->renderSquare(4)}
          {$this->renderSquare(5)}
        </div>
        <div class="board-row">
          {$this->renderSquare(6)}
          {$this->renderSquare(7)}
          {$this->renderSquare(8)}
        </div>
      </div>;
  }
}

Game

最後にGameです。
まずはjsxの例です。

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

特に難しいものはありません。

class :Game extends :x:element {
  protected function render(): \XHPRoot {
    return <div class="game">
        <div class="game-board">
          <Board />
        </div>
        <div class="game-info">
          <div></div>
          <ol></ol>
        </div>
      </div>;
  }
}

これらを出力すると以下のものになります。
Reactのサンプルと全く同じものです。

スクリーンショット 2018-12-10 0.05.36.png

ボタンのonclickにイベントを埋める場合は、
前回紹介したXHPUnsafeRenderableを実装したクラスを利用します。

final class ClickAlert implements XHPUnsafeRenderable {
  public function toHTMLString(): string {
    return "alert('click');";
  }
}

作成したクラスを :Square クラスで利用します。

class :Square extends :x:element {
  attribute int value @required;
  protected function render(): \XHPRoot {
    $stringish = new ClickAlert()|> $$->toHTMLString();
    return <button class="square" onclick={$stringish}>
        {$this->:value}
      </button>;
  }
}

描画までのコードは以下の通りです。

<?hh // strict

class :Square extends :x:element {
  attribute int value @required;
  protected function render(): \XHPRoot {
    $stringish = new ClickAlert()|> $$->toHTMLString();
    return <button class="square" onclick={$stringish}>
        {$this->:value}
      </button>;
  }
}

class :Board extends:x:element {
  const string STATUS = 'Next player: XHP';

  private function renderSquare(int $i): :Square {
    return <Square value={$i} />;
  }

  protected function render(): \XHPRoot {
    return <div>
        <div class="status">{self::STATUS}</div>
        <div class="board-row">
          {$this->renderSquare(0)}
          {$this->renderSquare(1)}
          {$this->renderSquare(2)}
        </div>
        <div class="board-row">
          {$this->renderSquare(3)}
          {$this->renderSquare(4)}
          {$this->renderSquare(5)}
        </div>
        <div class="board-row">
          {$this->renderSquare(6)}
          {$this->renderSquare(7)}
          {$this->renderSquare(8)}
        </div>
      </div>;
  }
}

class :Game extends :x:element {
  protected function render(): \XHPRoot {
    return <div class="game">
        <div class="game-board">
          <Board />
        </div>
        <div class="game-info">
          <div></div>
          <ol></ol>
        </div>
      </div>;
  }
}

final class ClickAlert implements XHPUnsafeRenderable {
  public function toHTMLString(): string {
    return "alert('click');";
  }
}

最後のEntrypoiintでの出力は下記のもののみです。

<?hh // strict

require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function run(): void {
  echo <Game />;
}

基本的な実装方法を覚えて、ぜひ活用してみてください。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?