facebook / xhp-lib
XHP 类库
Requires
- hhvm: ^4.102
- hhvm/hsl: ^4.36.0
Requires (Dev)
- facebook/fbexpect: ^2.0.0
- hhvm/hacktest: ^2.0
- hhvm/hhast: ^4.0
- hhvm/hhvm-autoload: ^2.0|^3.0
- dev-main / 4.x-dev
- v4.1.1
- v4.1.0
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0.0
- v4.0.0rc2
- v4.0.0rc1
- v3.x-dev
- v3.2.5
- v3.2.4
- v3.2.3
- v3.2.2
- v3.2.1
- v3.2.0
- v3.1.1
- v3.1.0
- v3.0.2
- v3.0.1
- v3.0.0
- 2.10.2
- v2.10.1
- v2.10.0
- v2.9.5
- v2.9.4
- v2.9.3
- v2.9.2
- v2.9.1
- v2.9.0
- v2.8.0
- v2.7.0
- v2.6.0
- v2.5.1
- v2.5.0
- v2.4.0
- v2.3.2
- 2.3.1
- 2.3.0
- 2.2.4
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.x-dev
- 2.0.2
- 2.0.1
- 2.0.0
- 1.x-dev
- 1.6.0
This package is auto-updated.
Last update: 2024-04-25 00:24:57 UTC
README
简介
XHP 扩展了 Hack 的语法,使得 XML 文档片段成为有效的 Hack 表达式。这允许您将 Hack 作为更严格的模板引擎使用,并为可重用组件的实现提供了更加直观的方法。
要查看您可以使用 xhp 构建的组件的高级示例,您可以查看存档项目 xhp-bootstrap。
公告和文章发布在 HHVM 博客 上,之前发布在 XHP-Lib 博客 上
安装
Composer 是推荐的安装方法。要将 XHP 添加到您的项目中,运行以下命令
$ composer require facebook/xhp-lib
简单示例
$href = 'https://#'; $link = <a href={$href}>Facebook</a>;
注意第 2 行的语法 ($link = ...
),这不仅仅是一个字符串。这是 XHP 向 Hack 引入的主要新语法。使用 <a ...>
语法来实例化 a
类的对象。
注意对 href={$href}
的赋值。这并不是字符串插值。它是设置属性 $link->:href
。任何在 {} 中的内容都被解释为完整的 Hack 表达式。这与双引号字符串中的 {} 不同;双引号字符串只能包含变量。
您可以在 Hack 中定义任意可以实例化的元素。在底层,您创建的每个元素都是类的实例。要定义新元素,只需定义一个新类。XHP 随带一组预定义的元素,这些元素为您实现了大多数 HTML。
复杂结构
请注意,XHP 结构可以是任意复杂的。这是一个有效的 XHP 程序
$post = <div class="post"> <h2>{$post}</h2> <p><span>Hey there.</span></p> <a href={$like_link}>Like</a> </div>;
XHP 相比于字符串构建的一个优点是,它在编译时强制执行正确的标记结构。也就是说,表达式 $foo = <h1>Header</h2>;
不是一个有效的表达式,因为您不能使用 /h2
标签关闭 h1
标签。当构建大量的标记时,完全正确可能很困难。使用 XHP,编译器现在会检查您的工作,并且只有在标记正确的情况下才会运行。
动态结构
有时,可能需要创建一系列元素,并将它们动态地添加到元素作为子元素。所有 XHP 对象都支持 appendChild
方法,其行为与同名的 JavaScript 方法类似。例如
$list = <ul />; $items = ...; foreach ($items as $item) { $list->appendChild(<li>{$item}</li>); }
在代码中,<ul />
创建了一个没有子元素的 ul。然后我们为 $items
列表中的每个项动态地将其子元素添加到它。
或者,您可以将子元素的数组传递给 <ul>...</ul>
。这在处理较大的 xhp 树时特别有用,其中获取 ul
的变量引用可能更困难。
$list_items = vec[]; $items = ...; foreach($items as $item) { $list_items[] = <li>{$item}</li>; } $list = <div><ul>{$list_items}</ul></div>;
转义
XHP的一个有趣特性是自动转义的概念。如果你想在不用XHP的情况下渲染用户输入,你必须手动进行转义。这种做法容易出错,并且在实践中已被证明是不可持续的解决方案。它增加了代码复杂性,并且由于编程不慎仍然会导致安全漏洞。然而,由于XHP具有关于页面结构的上下文信息,它可以自动转义数据。以下两个示例是相同的,并且两者都是“安全的”。
$hello = '<div>Hello '.htmlspecialchars($name).'</div>';
$hello = <div>Hello {$name}</div>;
正如你所看到的,使用XHP将安全性作为默认选项而不是例外。
定义元素
XHP中的所有元素都是Hack类。即使是基本的HTML元素,如div和span,也是类。你使用xhp
类修饰符来定义一个元素,指定你正在创建一个XHP元素
use namespace Facebook\XHP\Core as x; xhp class thing extends x\element { ... }
请注意,我们正在扩展x\element
。名称x\element
是Facebook\XHP\Core\element
的常用简称。在文件中使用xhp-lib时,常用use
语句use namespace Facebook\XHP\Core as x;
。在未来的示例中,将省略use子句,因为x\...
已经成为Core
的规范名称,这是出于历史原因。
定义thing
后,我们可以使用表达式<thing />
来实例化它。x\element
是在定义元素时应从其继承的核心XHP类。它将提供你所需的所有方法,如appendChild
等。作为一个x\element
,你必须只定义renderAsync()
。renderAsync()
应始终返回更多的XHP元素。重要的是要记住这个规则:即使是你自己定义的元素也会返回XHP元素。唯一允许返回字符串的XHP元素是继承自x\primitive
的元素。唯一应该继承自x\primitive
的元素是作为HTML构建块的基元素。使用核心HTML库的XHP是字符串标记的有效替代品。
定义属性
大多数元素将接受一些属性,这些属性会影响其行为。你使用attribute
关键字在元素中定义属性。
xhp class thing extends x\element { attribute string title = "No Title", string sub-title, float fill-percentage @required; }
这里我们定义了三个属性:title
、sub-title
和fill-percentage
。title
的类型为string
,默认值为"No Title"
。sub-title
的类型为?string
。该属性是可选的,没有默认值。因此,当你不设置它时,Hack将推断出sub-title
将是null
。fill-percentage
的类型为float
,它是必需的。由于它是必需的,如果未设置值,xhp-lib将在渲染或访问$thing->:fill-percentage
时抛出异常。因此,Hack可以确信该值不会为null。
请注意,当你扩展另一个元素时,你将始终继承其属性。但是,任何与父元素同名的属性都将覆盖你的父属性。
你也可以通过在定义中仅指定一个标签名称来复制另一个元素的属性声明。声明attribute :div
表示此元素可以接受div元素可以接受的任何属性。
定义元素结构
所有元素都必须遵循某种结构。例如,在HTML5中,在<body>
标签中直接出现<input>
是不合法的(它必须在一个表单内)。XHP允许你定义一个内容模型,文档必须遵守此模型。这是通过使用XHPChild\Validation
特质来完成的。XHPChild\Validation
是Facebook\XHP\ChildValidation\Validation
的简称。在声明xhp类的文件中,常用use子句use namespace Facebook\XHP\ChildValidation as XHPChild
。在示例中,将省略use子句。
xhp class thing_container extends x\element { abstract protected static function getChildrenDeclaration(): XHPChild\Constraint { return XHPChild\any_number_of( XHPChild\any_of( XHPChild\of_type<thing>(), XHPChild\pcdata(), ) ); } }
您可以通过查看ChildValidation函数列表来获取可以构建的完整约束列表。上述示例组合了以下规则。顶层规则any_number_of()
声明可能有零个或多个子节点,这些子节点都符合内部约束。内部约束说明,当子节点符合以下约束之一时是有效的:它是一个thing
类型的对象或纯文本(pcdata)。
元素类别
很多时候,您可能想接受特定组中所有的子元素,但枚举该组变得不可持续。在这种情况下,您可以使用接口作为标记来表示组成员资格。一个很好的例子是所有内置html元素都实现了匹配html-spec类别的Category接口。
以下是一个在<audio>
元素子节点定义中使用Flow
类别的示例。
protected static function getChildrenDeclaration(): XHPChild\Constraint { return XHPChild\sequence( XHPChild\any_number_of(XHPChild\of_type<source>()), XHPChild\any_number_of(XHPChild\of_type<track>()), XHPChild\any_number_of( XHPChild\any_of(XHPChild\pcdata(), XHPChild\of_type<Category\Flow>()), ), ); }
异步数据获取
XHP支持Hack的'async'功能,允许您构建能够高效获取所需数据的组件
xhp class async_thing extends :x:element { protected async function renderAsync(): Awaitable<x\node> { $db = await AsyncMysqlClient::connect(...); $result = await $db->queryf( 'SELECT id2 FROM %T WHERE id1 %=d', 'friends', $this->getContext('viewer')->getUserID(), ); $rows = $result->dictRowsTyped(); $friend_ids = Vec\map($rows, $row ==> $row['id2']); $friend_data = await Vec\map_async( $friend_ids, $id ==> User::get($id), ); $out = <ul />; foreach ($friend_ids as $id) { $out->appendChild(<li>.... </li>); } return $out; } }
当XHP树被渲染时,会为所有子节点调用renderAsync()
,数据获取是并行进行的。这使得组件的数据依赖项可以高效地成为实现细节,而不是作为API的一部分由用户传递(例如在属性中)。
空白字符
在XHP中,仅包含空白字符的文本节点会被移除。表达式<div> </div>
和<div />
是相同的。包含非空白字符的文本节点在左右两侧会被修剪至最多1个空格。这值得注意,因为您可能想做一些类似以下操作:
$node = <div><label>Title:</label> <span>{$title}</span></div>;
这将导致不理想的结果,因为:
和$title
之间的空格将丢失。为了解决这个问题,尝试将空格移入<label />
标签中。如果您无法这样做,则只需使用{' '}
,它不会被删除。
最佳实践
在使用XHP时,您应该遵守某些约定。
- 不要在全局XHP命名空间中污染无命名空间的元素。您定义的大多数元素应该使用某个命名空间。使用无命名空间的元素绝对不应该具有“魔法”功能。例如,
xhp class fb:thing extends x\element { protected async function renderAsync(): Awaitable<x\node> { return <div class="thing">thing</div>; } }
这个元素会被视为魔法元素,因为当你打印一个<fb:thing />
时,它实际上返回一个div。
外部资源
以下是关于XHP的外部资源列表
- Code Before the Horse - 由Facebook的一名UI工程师编写的XHP基本介绍、示例和从Facebook学到的经验教训。
许可协议
此软件遵循MIT许可协议。