与 React 的差异
Preact 并非旨在成为 React 的重新实现。它们之间存在差异。其中许多差异微不足道,或者可以通过使用 preact/compat 完全消除,后者是 Preact 上的一层薄层,旨在实现与 React 100% 的兼容性。
Preact 不尝试包含 React 的每个功能的原因是为了保持小巧和专注 - 否则,直接向 React 项目提交优化方案更有意义,而 React 项目本身就是一个非常复杂且架构良好的代码库。
主要差异
Preact 与 React 之间的主要差异在于,出于大小和性能方面的考虑,Preact 并未实现合成事件系统。Preact 使用浏览器的标准 addEventListener
来注册事件处理程序,这意味着事件命名和行为在 Preact 中与在纯 JavaScript/DOM 中的工作方式相同。请参阅 MDN 的事件参考 以获取 DOM 事件处理程序的完整列表。
标准浏览器事件的工作方式与 React 中事件的工作方式非常相似,但有一些细微的差别。在 Preact 中
- 事件不会通过
<Portal>
组件冒泡 - 对于表单输入,应使用标准
onInput
,而不是 React 的onChange
(仅在未使用preact/compat
时) - 应使用标准
onDblClick
,而不是 React 的onDoubleClick
(仅在未使用preact/compat
时) - 通常应将
onSearch
用于<input type="search">
,因为在 IE11 中,清除“x”按钮不会触发onInput
另一个值得注意的区别是 Preact 更严格地遵循 DOM 规范。自定义元素与任何其他元素一样受支持,并且自定义事件以区分大小写的名称受支持(就像在 DOM 中一样)。
版本兼容性
对于 preact 和 preact/compat,版本兼容性根据 React 的当前和上一个主要版本进行衡量。当 React 团队宣布新功能时,如果符合 项目目标,则可能会将其添加到 Preact 的核心。这是一个相当民主的过程,通过公开讨论和决策,使用问题和拉取请求不断发展。
因此,在讨论兼容性或进行比较时,网站和文档反映了 React
15.x
到17.x
。
调试消息和错误
我们灵活的架构允许附加组件以任何方式增强 Preact 体验。其中一个附加组件是 preact/debug
,它添加了 有用的警告和错误,并附加了 Preact Developer Tools 浏览器扩展(如果已安装)。这些指南在开发 Preact 应用程序时为您提供指导,并使检查正在发生的事情变得更加容易。您可以通过添加相关导入语句来启用它们
import "preact/debug"; // <-- Add this line at the top of your main entry file
这与 React 不同,React 需要一个打包器,该打包器在构建时通过检查 NODE_ENV != "production"
来剥离调试消息。
Preact 的独特功能
Preact 实际上添加了一些方便的功能,灵感来自 (P)React 社区的成果
对 ES 模块的原生支持
Preact 从一开始就考虑了 ES 模块,并且是首批支持它们的框架之一。您可以在浏览器中直接通过 import
关键字加载 Preact,而无需先通过捆绑器。
Component.render()
中的参数
为了方便起见,我们将 this.props
和 this.state
传递给类组件上的 render()
方法。看看这个使用一个 prop 和一个 state 属性的组件。
// Works in both Preact and React
class Foo extends Component {
state = { age: 1 };
render() {
return <div>Name: {this.props.name}, Age: {this.state.age}</div>;
}
}
在 Preact 中,也可以这样编写
// Only works in Preact
class Foo extends Component {
state = { age: 1 };
render({ name }, { age }) {
return <div>Name: {name}, Age: {age}</div>;
}
}
两个代码段渲染完全相同的内容,提供渲染参数是为了方便起见。
原始 HTML 属性/属性名称
Preact 旨在紧密匹配所有主流浏览器支持的 DOM 规范。在将 props
应用到元素时,Preact 会检测每个 prop 是应该设置为属性还是 HTML 属性。这使得在自定义元素上设置复杂属性成为可能,但这也意味着您可以在 JSX 中使用像 class
这样的属性名称
// This:
<div class="foo" />
// ...is the same as:
<div className="foo" />
大多数 Preact 开发人员更喜欢使用 class
,因为它更简洁,但两者都受支持。
JSX 中的 SVG
说到 SVG 的属性和特性名称,它非常有趣。SVG 对象上的一些属性(及其特性)采用驼峰式命名(例如 clipPath 元素上的 clipPathUnits),一些特性采用连字符分隔命名(例如 许多 SVG 元素上的 clip-path),而其他特性(通常是从 DOM 继承的,例如 oninput
)全部采用小写。
Preact 按原样应用 SVG 特性。这意味着您可以将未修改的 SVG 代码段直接复制并粘贴到代码中,并立即使其正常工作。这允许与设计师用来生成图标或 SVG 插图的工具进行更好的互操作。
// React
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<circle fill="none" strokeWidth="2" strokeLinejoin="round" cx="24" cy="24" r="20" />
</svg>
// Preact (note stroke-width and stroke-linejoin)
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<circle fill="none" stroke-width="2" stroke-linejoin="round" cx="24" cy="24" r="20" />
</svg>
如果您来自 React,您可能习惯于以驼峰式命名指定所有特性。您可以继续使用始终采用驼峰式命名的 SVG 特性名称,方法是将 preact/compat 添加到您的项目中,它会镜像 React API 并使这些特性标准化。
使用 onInput
而不是 onChange
在很大程度上出于历史原因,React 的 onChange
事件的语义实际上与浏览器提供的 onInput
事件相同,后者得到广泛支持。input
事件最适合大多数情况下您希望在表单控件被修改时做出反应的情况。在 Preact 核心版本中,onChange
是标准的 DOM change 事件,当元素的值被用户提交时触发。
// React
<input onChange={e => console.log(e.currentTarget.value)} />
// Preact
<input onInput={e => console.log(e.currentTarget.value)} />
如果您使用 preact/compat,大多数 onChange
事件在内部都会转换为 onInput
以模拟 React 的行为。这是我们用来确保与 React 生态系统最大程度兼容的技巧之一。
JSX 构造函数
JSX 是 JavaScript 的语法扩展,它会转换为嵌套函数调用。使用这些嵌套调用来构建树结构的想法早于 JSX,并且以前通过 hyperscript 项目在 JavaScript 中流行起来。这种方法的价值远远超出了 React 生态系统的范围,因此 Preact 推广了最初的通用社区标准。有关 JSX 及其与 Hyperscript 关系的更深入讨论,阅读这篇文章了解 JSX 的工作原理。
源代码: (JSX)
<a href="/">
<span>Home</span>
</a>
输出
// Preact:
h(
'a',
{ href:'/' },
h('span', null, 'Home')
);
// React:
React.createElement(
'a',
{ href:'/' },
React.createElement('span', null, 'Home')
);
最终,如果您查看 Preact 应用程序的生成输出代码,很明显,一个较短的未命名空间的“JSX pragma”既更易于阅读,又更适合于缩小等优化。在大多数 Preact 应用程序中,您会遇到 h()
,但使用哪个名称并不重要,因为还提供了 createElement
别名导出。
无需 contextTypes
旧版 Context
API 要求组件使用 React 的 contextTypes
或 childContextTypes
声明特定属性,才能接收这些值。Preact 没有此要求:默认情况下,所有组件都会接收 getChildContext()
生成的所有 context
属性。
仅 preact/compat
具备的功能
preact/compat
是我们的兼容层,它将 React 代码转换为 Preact。对于现有的 React 用户来说,这可能是尝试 Preact 的一种简单方法,而无需更改任何代码,通过 在您的捆绑器配置中设置几个别名。
Children API
Children
API 是一组专门用于处理 props.children
值的方法。对于 Preact,这通常是不必要的,我们建议改用内置的数组方法。在 Preact 中,props.children
可能是虚拟 DOM 节点、空值(如 null
)或虚拟 DOM 节点的数组。前两种情况是最简单、最常见的情况,因为可以按原样使用或返回 children
// React:
function App(props) {
return <Modal content={Children.only(props.children)} />
}
// Preact: use props.children directly:
function App(props) {
return <Modal content={props.children} />
}
对于需要遍历传递给组件的子项的特殊情况,Preact 提供了一个 toChildArray()
方法,它接受任何 props.children
值并返回一个扁平化且规范化的虚拟 DOM 节点数组。
// React
function App(props) {
const cols = Children.count(props.children);
return <div data-columns={cols}>{props.children}</div>
}
// Preact
function App(props) {
const cols = toChildArray(props.children).length;
return <div data-columns={cols}>{props.children}</div>
}
一个与 React 兼容的 Children
API 可从 preact/compat
获得,以实现与现有组件库的无缝集成。
专业组件
preact/compat 附带了并非每个应用都需要的专业组件。这些组件包括
- PureComponent:仅在
props
或state
发生更改时更新 - memo:与
PureComponent
类似,但允许使用自定义比较函数 - forwardRef:向指定的子组件提供
ref
。 - 门户:继续将当前树渲染到不同的 DOM 容器中
- Suspense:实验性允许在树未准备就绪时显示备用内容
- lazy:实验性延迟加载异步代码,并相应地将树标记为准备就绪/未准备就绪。