这里有很多部分,但我在涵盖问题的同时整理了一些从最低级别到最高级别的链接。
第一个也是最重要的定义是 React Components 和 React Elements。 This page 有深入的解释 - 我建议完全跳过“管理实例”部分,因为它通过描述传统的 UI 模型而使用与 React 中使用方式不同的术语来混淆水域。总之,我的理解是:
React 组件是一个通用概念,可以通过多种方式实现。不管它是如何实现的,它本质上是一个从 props(和可选的 state)到页面内容的函数 - 一个渲染器。
React Element 是对页面内容的描述,表示组件的特定呈现。
React components and props docs 描述了定义 React 组件的两种方式,第一种是从 props 到 react 元素的函数,这是我们感兴趣的。React.createFactory 文档然后确认我们可以通过createFactory 这样的函数。据我所知,这是为了适应定义 React 组件的多种方式(通过子类化 React.Component 或 React.PureComponent,通过使用 React.createClass,或通过从 Props 到 ReactElement 的函数)来适应一种方式将道具渲染到 ReactElement。我们可以通过查看 gist 在 React 0.12 中引入 React.createFactory 来了解这一点——本质上,他们想在用于定义 React 组件的类和从 props 到 React Elements 的最终函数之间引入一些抽象,当渲染,而不是让类直接渲染道具。
接下来我们有一个小问题 - React.createFactory 在文档中被标记为旧版。幸运的是,这不是一个主要问题,据我所知,React.createFactory(type) 只是生成了一个与React.createElement(type, props) 相同的函数f(props) - 我们只是修复了React.createElement 中的type 参数。我已经在 react-sortable-hoc 包装器中对此进行了测试,我们可以使用 createElement 代替 createFactory:
val componentFunction = js.Dynamic.global.SortableContainer(wrappedComponent.factory)
(props) => (wrappedProps) => {
val p = props.toJS
p.updateDynamic("v")(wrappedProps.asInstanceOf[js.Any])
React.asInstanceOf[js.Dynamic].createElement(componentFunction, p).asInstanceOf[ReactComponentU_]
}
我们现在几乎要解决问题 2)。如果我们查看the source for SortableElement,我们可以看到sortableElement 函数接受WrappedComponent 参数——它用于通过“子类React.Component”方法创建另一个React 组件。在这个类的render函数中,我们可以看到WrappedComponent被用作React Component,所以我们知道它确实是一个组件,即使没有静态类型:) 这意味着 WrappedComponent 需要被接受React.createElement,因为这就是JSX component use desugars to。
因此我们知道我们需要向sortableElement 函数传递一些可用作javascript React.createElement 函数中的React 组件的东西。查看scalajs-react types doc,我们可以看到 ReactComponentC 看起来是一个不错的选择——它构造组件,大概是从 props 中构建的。查看at the source for this,我们可以看到我们有两个看起来很有希望的值-reactClass 和factory。在这一点上,我意识到代码可能使用了错误的代码——我尝试用.reactClass 替换.factory,这仍然有效,但更有意义,因为我们有评论告诉我们它给了@987654351 @,这是有效 React 组件的选项之一。我怀疑factory 也可以通过两次将提供的组件包装在 createFactory 中来工作,因为 createFactory 的输出也可用作其输入...我认为鉴于此更正,我们已经回答了问题 2 :) 这也差不多回答问题 1a) - ReactComponentC 是 scala 特征,它为我们提供了 scala 定义的 React 组件所需的 .reactClass val。我们只关心它使用的道具类型(因为我们必须提供它们),因此P。由于 scala 是类型化的,我们知道这是我们以正常方式构建 scala React 组件所得到的(至少对于我尝试过的组件而言)。
在问题 1b) 中,我从代码中找到了类型 ReactComponentU_,例如 scalajs-react Addons 和 scalajs-react-components notes on interop 中的 ReactCssTransitionGroup 外观,它显示了非 HOC 组件的包装。查看类型本身,我们可以看到它扩展了ReactElement,这是有道理的——这是渲染 React 组件的预期结果。在 SortableElement 和 SortableContainer 门面的 wrap 函数中,我们(最终)生成了另一个从 props 到 ReactElement 的函数,它只是通过 HOC 方法跳过了几个环节。我不确定为什么使用ReactComponentU_ 而不仅仅是ReactElement,我认为这与通过类型跟踪组件的状态有关,但是如果我返回ReactElement,代码仍然可以编译,这很奇怪。
问题 3) 要容易得多 - scalajs-react 可以与可以是 Ints、Longs 等的 Props 一起使用,但在 Javascript 中这些不是对象。为了让每个 scalajs 组件的 props 都成为一个对象,scalajs-react 将它们包装在一个像 {"v": props} 这样的对象中,然后在使用它们时再次展开。当我们使用 HOC 包装一个 Scala React 组件时,我们需要以某种方式获取该包装组件的 props。 HOC 函数生成的组件(“包装”组件,SortableElement 或 SortableContainer)通过期望它自己的 props 已经包含被包装组件的 props 作为字段来做到这一点,然后它只是让这些流向被包装的组件,例如在 SortableElement 的渲染中:
<WrappedComponent
ref={ref}
{...omit(this.props, 'collection', 'disabled', 'index')}
/>
this.props 被传递给被包装的组件。由于被包装的 scala 组件需要一个包含 scala props 对象的“v”字段,因此我们需要将其添加到包装组件的 props 中。幸运的是,该字段将通过原样通过,稍后由 scala 组件解释。你不会看到“v”字段,因为 scalajs-react 会为你解开它。
这确实会在包装其他一些 HOC 时引发问题 - 例如,ReactGridLayout's WidthProvider 测量包装组件的宽度并将其作为 {"width": width} 在道具中传递,但不幸的是我们无法从 scala 中看到这一点。很可能有一些解决方法。
这涵盖了 HOC 包装部分的详细信息和参考,但实际上该过程非常简单(前提是您不想访问“注入”到包装组件中的道具):
- 为外观创建一个 scala 对象来组织代码。
- 找出包装器组件需要哪些道具。例如在
SortableElement 中,这是index、collection 和disabled。在外观对象中使用这些字段创建一个 Props 案例类。
- 在外观对象中编写一个“包装”函数。这会执行以下操作:
- 接受
wrappedComponent: ReactComponentC[P,_,_,_] 并将其传递给 javascript HOC 函数(例如 SortableElement)以生成新的 React 组件。
- 使用包装组件的 props 和带有被包装组件的 props 的魔术“v”字段构建一个 javascript props 对象。
- 使用 javascript React.createElement 函数生成一个 ReactElement,该 ReactElement 呈现包装的组件,并将其转换为 ReactComponentU_。
请注意,在第 5 阶段,我们需要将 Scala Props 案例类(包装器组件的 props)转换为 HOC 可以理解的普通 javascript 对象。封装组件的 props 直接进入“v”字段,无需转换,直接转换为 js.Any。
我为 SortableElement 和 SortableContainer 编写的代码将其拆分了一点,以便 wrap 返回一个 curried 函数,该函数接受包装器组件的 props,并从包装的 props 生成另一个函数到最终的 React 元素。这意味着您可以提供一次包装器道具,然后在您的 Scala 渲染代码中像普通组件一样使用生成的函数。
我已经用上述改进更新了SortableElement facade,现在这几乎是一个 HOC 外观的最小示例。我想其他 HOC 看起来会非常相似。您可能可以出于 DRY 的目的抽象一些代码,但实际上实际上并没有很多。
感谢您提出问题并帮助解决这个问题 - 回顾整个过程,特别是您在 .factory 上的问题,让我更加确信现在正在以正确的方式工作(所描述的更改)。