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 />;
}
基本的な実装方法を覚えて、ぜひ活用してみてください。