使用 scalajs-react 包装 React HOC 组件的说明

Explanation for wrapping React HOC components with scalajs-react

我正在尝试理解@trepidacious's scalajs-react wrapper for this HOC 反应组件。

1a) 为什么包裹组件的类型是here ReactComponentC[P,_,_,_] ?

1b) 为什么组件的return类型是ReactComponentU_

  def wrap[P](wrappedComponent: ReactComponentC[P,_,_,_]): Props => P => ReactComponentU_ = {

2) 为什么工厂函数传递给 SortableElement here

val componentFactoryFunction = js.Dynamic.global.SortableElement(wrappedComponent.factory) ?

SortableElement 不带 Component 吗?

3)为什么包裹的道具像this一样传递?

  "v" -> wrappedProps.asInstanceOf[js.Any]

这条线背后的原因是什么?

神奇的 v 是从哪里来的?是来自 scalajs-react 还是来自 react-sortable-hoc

4) 这个包装背后的原因是什么?如果我想为另一个 HOC 组件编写一个包装器,那么它的一般接收方式应该是什么?

我没有得到所有的答案,但我的理解是 scalajs-react 的作者使用了很多类型来防止在构建组件期间以及组件构建后的生命周期内出现错误。他使用带有后缀和字母的命名约定来分隔有意义但一开始可能令人生畏的类型。

1a) ReactComponentC 是组件的构造函数(C 与构造函数一样)。

1b) ReactComponentU_ 表示未安装的原生 (JavaScript) React 组件。

3) 我认为,查看 scalajs-react 来源,是的,"v" 是一个神奇的键名。源代码中也有(曾经?)一些说明这并不理想;)

有个plan to simplify scalajs-react's types in a new version.

这里有很多部分,但我在涵盖问题的同时从最低级别到最高级别整理了一些链接。

第一个也是最重要的定义是 React Components 和 React Elements。 This page 有一个深入的解释——我建议完全跳过 "Managing the Instances" 部分,因为它通过描述传统的 UI 模型而混淆了水域,同时使用与它们在 React 中使用的方式不同的术语.总之我的理解是:

React 组件是一个通用概念,可以通过多种方式实现。不管它是如何实现的,它本质上是一个从道具(和可选的状态)到页面内容的函数——一个渲染器。

React 元素是对页面内容的描述,代表组件的特定渲染。

React components and props docs describe the two ways of defining a React component, the first one is a function from props to a react element, this is the one we're interested in. The React.createFactory docs then confirm that we can pass such a function to createFactory. As far as I can tell this exists in order to adapt from the multiple ways of defining a React Component (by subclassing React.Component or React.PureComponent, by using React.createClass, or by a function from Props to ReactElement) to a way of rendering props to a ReactElement. We can see something about this by looking at this gist 在 React 0.12 中引入 React.createFactory - 本质上他们想在用于定义 React 组件的 class 和从 props 到 React Elements 的最终函数之间引入一些抽象在渲染时使用,而不是直接让 class 渲染道具。

接下来我们有一个小问题 - React.createFactory 在文档中被标记为旧版。幸运的是,这不是一个主要问题,据我所知,React.createFactory(type) 只是生成了一个与 React.createElement(type, props) 相同的函数 f(props) - 我们只是修复了 type React.createElement 中的参数。我已经在 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 we can see that the sortableElement function accepts a WrappedComponent argument - this is used to create another React Component, via the "subclass React.Component" approach. In the render function of this class, we can see that WrappedComponent is used as a React Component, so we know that it is indeed a component, even without a static type :) This means that WrappedComponent needs to be something accepted by React.createElement, since this is what a JSX component use desugars to.

因此我们知道我们需要向 sortableElement 函数传递一些可用作 javascript React.createElement 函数中的 React 组件的东西。查看 scalajs-react types doc we can see that ReactComponentC looks like a good bet - it constructs components, presumably from props. Looking at the source for this 我们可以看到我们有两个看起来很有希望的值 - reactClassfactory。在这一点上,我意识到代码可能使用了错误的代码——我尝试用 .reactClass 替换 .factory 并且这仍然有效,但更有意义,因为我们有评论告诉我们它给出 Output of [[React.createClass()]],这是有效 React 组件的选项之一。我怀疑 factory 也可以通过将提供的组件包装在 createFactory 中两次来工作,因为 createFactory 的输出也可用作其输入...我认为给出这个更正我们已经回答了问题 2 :) 这也几乎回答了问题 1a) - ReactComponentC 是 scala 特性,它使我们获得了我们需要的 .reactClass val,这是一个 scala 定义的 React 组件。我们只关心它使用的道具类型(因为我们必须提供它们),因此 P。由于 scala 是类型化的,我们知道这是我们以正常方式构建 scala React 组件所得到的(至少对于我尝试过的组件)。

关于问题 1b),我从 scalajs-react Addons and the scalajs-react-components notes on interop 中的 ReactCssTransitionGroup facade 这样的代码中找到了类型 ReactComponentU_,它显示了非 HOC 组件的包装。查看类型本身,我们可以看到它扩展了 ReactElement,这是有道理的——这是渲染 React 组件的预期结果。在 SortableElementSortableContainer 门面的 wrap 函数中,我们正在(最终)生成另一个从 props 到 ReactElement 的函数,只是一个通过 HOC 方法跳过几个环到达那里的函数.我不确定为什么使用 ReactComponentU_ 而不是 ReactElement,我认为这是通过类型跟踪组件的状态,但是如果我 return 代码仍然可以编译ReactElement,奇数

问题 3) 更简单 - scalajs-react 使用可以是整数、长整数等的道具,但在 Javascript 中这些不是对象。为了使每个 scalajs 组件的 props 成为一个对象,scalajs-react 将它们包装在一个对象中,如 {"v": props},然后在使用时再次展开。当我们使用 HOC 包装 Scala React 组件时,我们需要以某种方式获取包装组件的 props。由 HOC 函数("wrapping" 组件、SortableElement 或 SortableContainer)生成的组件通过期望它自己的 props 已经包含包装组件的 props 作为字段来做到这一点,然后它只让这些流经包装组件,例如在 SortableElement 的渲染中:

<WrappedComponent
                ref={ref}
                {...omit(this.props, 'collection', 'disabled', 'index')}
            />

this.props 传递给包装的组件。由于包装的 scala 组件需要一个 "v" 字段,其中包含 scala props 对象,我们需要将其添加到包装器组件的 props 中。幸运的是,这个字段将不加改变地通过,稍后由 scala 组件解释。你不会看到 "v" 字段,因为 scalajs-react 会为你打开它。

这在包装其他一些 HOC 时确实会引发问题 - 例如 ReactGridLayout's WidthProvider 测量包装组件的宽度并将其作为 {"width": width} 在 props 中传递,但不幸的是我们看不到这个来自斯卡拉。可能有一些解决方法。

这涵盖了 HOC 包装部分的细节和参考,但实际上这个过程非常简单(前提是你不想访问 props "injected" 到包装的组件中):

  1. 为门面创建一个 scala 对象来组织代码。
  2. 找出包装器组件需要哪些道具。例如,在 SortableElement 中,这是 indexcollectiondisabled。在外观对象中使用这些字段创建一个 Props 案例 class。
  3. 在门面对象中写一个'wrap'函数。这会执行以下操作:
  4. 接受一个 wrappedComponent: ReactComponentC[P,_,_,_] 并将其传递给 javascript HOC 函数(例如 SortableElement)以生成一个新的 React 组件。
  5. 使用包装器组件的 props 和魔法 "v" 字段与包装组件的 props 构建一个 javascript props 对象。
  6. 使用 javascript React.createElement 函数生成一个呈现包装组件的 ReactElement,并将其转换为 ReactComponentU_。

请注意,在第 5 阶段,我们需要将 Scala Props 案例 class(包装器组件的道具)转换为 HOC 可以理解的普通 javascript 对象。包装组件的 props 直接进入 "v" 字段而不进行转换,只是转换为 js.Any.

我为 SortableElement 和 SortableContainer 编写的代码将其拆分了一点,以便包装 returns 一个柯里化函数,该函数接受包装器组件的 props 并从包装的 props 到最终的 React 元素生成另一个函数.这意味着您可以提供一次包装器道具,然后像 Scala 渲染代码中的普通组件一样使用生成的函数。

我已经使用上述改进更新了 SortableElement facade,现在这几乎是 HOC 外观的一个最小示例。我想其他 HOC 看起来会非常相似。您可能出于 DRY 的目的抽象了一些代码,但实际上并没有那么多。

感谢您提出问题并帮助解决这个问题 - 回顾整个过程,尤其是您在 .factory 上提出的问题让我更有信心,现在它正在以正确的方式工作(通过描述的更改) .