Posted at

初めてのXHP Tutorial / XHPとReact


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"/>;
}

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

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のサンプルと全く同じものです。

ボタンの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 />;
}

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