React: RFC:在 React 18 中规划自定义元素属性/属性

创建于 2017-10-24  ·  129评论  ·  资料来源: facebook/react

这是为了解决 #7249。 该文档概述了 React 可用于处理自定义元素上的属性和属性的各种方法的优缺点。

目录/摘要

  • 背景
  • 提案

    • 选项 1:仅设置属性

    • 优点



      • 易于理解/实施


      • 避免与未来的全局属性发生冲突


      • 利用自定义元素“升级”


      • 自定义元素像任何其他 React 组件一样处理



    • 缺点



      • 可能是一个突破性的变化


      • 需要 ref 来设置属性


      • 不清楚服务器端渲染如何工作



    • 选项 2:可用的属性

    • 优点



      • 不间断的变化



    • 缺点



      • 开发人员需要了解启发式


      • 回退到属性可能会与未来的全局变量冲突



    • 选项 3:用印记区分属性

    • 优点



      • 开发人员可以选择加入的不间断更改


      • 类似于其他库处理属性的方式


      • 系统是明确的



    • 缺点



      • 这是新语法


      • 不清楚服务器端渲染如何工作



    • 选项 4:添加属性对象

    • 优点



      • 系统是明确的


      • 扩展语法还可以解决事件处理问题



    • 缺点



      • 这是新语法


      • 这可能是一个突破性的变化


      • 它可能比之前的任何提案都发生更大的变化



    • 选项 5:用于使用自定义元素的 API

    • 优点



      • 系统是明确的


      • 不间断的变化


      • 惯用的反应



    • 缺点



      • 对于一个复杂的组件来说可能需要做很多工作


      • 可能会膨胀包大小


      • Config 需要跟上组件的步伐



背景

当 React 尝试将数据传递给自定义元素时,它总是使用 HTML 属性来传递数据。

<x-foo bar={baz}> // same as setAttribute('bar', baz)

因为属性必须序列化为字符串,所以当传递的数据是对象或数组时,这种方法会产生问题。 在这种情况下,我们最终会得到类似的结果:

<x-foo bar="[object Object]">

解决方法是使用ref手动设置属性。

<x-foo ref={el => el.bar = baz}>

这种变通方法感觉有点不必要,因为今天发布的大多数自定义元素都是用库编写的,这些库会自动生成支持所有公开属性的 JavaScript 属性。 并且鼓励任何手工编写 vanilla 自定义元素的人也

本文档概述了一些关于如何更新 React 以实现这一目标的建议。

提案

选项 1:仅设置属性

React 可以始终在自定义元素上设置属性,而不是尝试决定是否应该设置属性或属性。 反应不会检查是否元素上存在的财产提前。

例子:

<x-foo bar={baz}>

上面的代码将导致 React 将x-foo元素的.bar属性设置为等于baz

对于驼峰式属性名称,React 可以使用它今天用于tabIndex等属性的相同样式。

<x-foo squidInk={pasta}> // sets .squidInk = pasta

优点

易于理解/实施

这个模型简单、明确,并且与 React 的“以 JavaScript 为中心的 DOM API”相吻合。

使用 Polymer 或 Skate 等库创建的任何元素都将自动生成属性以支持其公开的属性。 这些元素都应该与上述方法“正常工作”。 鼓励开发人员手工编写 vanilla 组件支持具有属性的属性,因为这反映了现代(即不是像<input>这样的古怪)HTML5 元素( <video><audio>等)已经实施。

避免与未来的全局属性发生冲突

当 React 在自定义元素上设置属性时,总会存在这样的风险,即未来版本的 HTML 会发布类似命名的属性并破坏事物。 与规范作者讨论了这个问题,但没有明确的问题解决方案。 完全避免属性(除非开发人员使用ref明确设置属性)可能会回避这个问题,直到浏览器提出更好的解决方案。

利用自定义元素“升级”

自定义元素可以在页面上延迟升级,一些 PRPL 模式依赖于这种技术。 在升级过程中,自定义元素可以访问 React 传递给它的属性——即使这些属性是在定义加载之前设置的——并使用它们来呈现初始状态。

自定义元素像任何其他 React 组件一样处理

当 React 组件相互传递数据时,它们已经使用了属性。 这只会使自定义元素的行为方式相同。

缺点

可能是一个突破性的变化

如果开发人员一直在手工编写只有属性 API 的普通自定义元素,那么他们将需要更新他们的代码,否则他们的应用程序就会崩溃。 解决方法是使用ref来设置属性(如下所述)。

需要 ref 来设置属性

通过改变行为使属性成为首选,这意味着开发人员将需要使用ref来显式设置自定义元素的属性。

<custom-element ref={el => el.setAttribute('my-attr', val)} />

这只是对开发人员需要ref才能设置属性的当前行为的逆转。 由于开发人员很少需要在自定义元素上设置属性,这似乎是一个合理的权衡。

不清楚服务器端渲染如何工作

目前尚不清楚此模型将如何映射到服务器端呈现的自定义元素。 React 可以假设属性映射到类似命名的属性并尝试在服务器上设置这些属性,但这远非防弹而且可能需要对诸如驼峰式属性 -> 破折号属性之类的东西进行启发。

选项 2:可用的属性

在运行时,React 可能会尝试检测自定义元素上是否存在属性。 如果该属性存在 React 将使用它,否则它将回退到设置属性。 这是 Preact 用于处理自定义元素的模型。

伪代码实现:

if (propName in element) {
  element[propName] = value;
} else {
  element.setAttribute(propName.toLowerCase(), value);
}

可能的步骤:

  • 如果一个元素有一个已定义的属性,React 将使用它。

  • 如果一个元素有一个未定义的属性,并且 React 试图向它传递原始数据(字符串/数字/布尔值),它将使用一个属性。

    • 替代方案:警告并且不要设置。
  • 如果一个元素有一个未定义的属性,并且 React 试图传递给它一个对象/数组,它会将它设置为一个属性。 这是因为 some-attr="[object Object]" 没有用。

    • 替代方案:警告并且不要设置。
  • 如果元素正在服务器上呈现,并且 React 试图将字符串/数字/布尔值传递给它,它将使用一个属性。

  • 如果元素正在服务器上呈现,并且 React 尝试将对象/数组传递给它,则它不会执行任何操作。

优点

不间断的变化

可以创建仅使用属性作为其接口的自定义元素。 鼓励这种创作风格,但无论如何它都可能发生。 如果自定义元素作者依赖此行为,则此更改对他们来说将是非破坏性的。

缺点

开发人员需要了解启发式

当 React 根据他们选择加载元素的方式设置属性而不是属性时,开发人员可能会感到困惑。

回退到属性可能会与未来的全局变量冲突

Sebastian提出了一个担忧,即使用in检查自定义元素上的属性是否存在可能会意外地检测到超类 (HTMLElement) 上的属性。

本文档中先前讨论的全局属性还存在其他潜在冲突。

选项 3:用印记区分属性

React 可以继续在自定义元素上设置属性,但提供了一个标志,开发人员可以使用它来显式设置属性。 这类似于Glimmer.js 使用的方法

微光示例:

<custom-img @src="corgi.jpg" @hiResSrc="[email protected]" width="100%">

在上面的例子中,@sigil 表示srchiResSrc应该使用属性将数据传递给自定义元素,并且width应该序列化为属性字符串。

因为 React 组件已经使用属性将数据相互传递,所以它们不需要使用 sigil(尽管如果使用它会起作用,但它只是多余的)。 相反,它主要用作使用 JavaScript 属性将数据传递给自定义元素的显式指令。

h/t to @developit of Preact 建议这种方法:)

优点

开发人员可以选择加入的不间断更改

所有预先存在的 React + 自定义元素应用程序将继续完全按照它们的方式工作。 开发人员可以选择是否要更新他们的代码以使用新的印记样式。

类似于其他库处理属性的方式

与 Glimmer 类似,Angular 和 Vue 都使用修饰符来区分属性和属性。

视图示例:

<!-- Vue will serialize `foo` to an attribute string, and set `squid` using a JavaScript property -->
<custom-element :foo="bar” :squid.prop=”ink”>

角度示例:

<!-- Angular will serialize `foo` to an attribute string, and set `squid` using a JavaScript property -->
<custom-element [attr.foo]="bar” [squid]=”ink”>

系统是明确的

开发人员可以准确地告诉 React 他们想要什么,而不是依赖像properties-if-available方法这样的启发式方法。

缺点

这是新语法

需要教会开发人员如何使用它,并且需要对其进行彻底测试以确保它向后兼容。

不清楚服务器端渲染如何工作

印记是否应该切换到使用类似命名的属性?

选项 4:添加属性对象

React 可以添加额外的语法,让作者显式地将数据作为属性传递。 如果开发人员不使用这个属性对象,那么他们的数据将使用 JavaScript 属性传递。

例子:

const bar = 'baz';
const hello = 'World';
const width = '100%';
const ReactElement = <Test
  foo={bar} // uses JavaScript property
  attrs={{ hello, width }} // serialized to attributes
/>;

这个想法最初Skate.js 的作者 @treshugart 提出,并在val库中实现。

优点

系统是明确的

开发人员可以准确地告诉 React 他们想要什么,而不是依赖像properties-if-available方法这样的启发式方法。

扩展语法还可以解决事件处理问题

注意:这超出了本文档的范围,但可能值得一提:)

问题#7901要求在将声明性事件处理程序添加到自定义元素时,React 绕过其合成事件系统。 因为自定义元素事件名称是任意字符串,这意味着它们可以以任何方式大写。 今天要绕过合成事件系统还意味着需要提出一种启发式方法来将事件名称从 JSX 映射到addEventListener

// should this listen for: 'foobar', 'FooBar', or 'fooBar'?
onFooBar={handleFooBar}

但是,如果语法扩展为允许属性,它也可以扩展为允许事件:

const bar = 'baz';
const hello = 'World';
const SquidChanged = e => console.log('yo');
const ReactElement = <Test
  foo={bar}
  attrs={{ hello }}
  events={{ SquidChanged}} // addEventListener('SquidChanged', …)
/>;

在此模型中,变量名称用作事件名称。 不需要启发式。

缺点

这是新语法

需要教会开发人员如何使用它,并且需要对其进行彻底测试以确保它向后兼容。

这可能是一个突破性的变化

如果任何组件已经依赖名为attrsevents属性,则可能会破坏它们。

它可能比之前的任何提案都发生更大的变化

对于 React 17,进行增量更改(如之前的提案之一)可能会更容易,并将此提案定位为以后更大的重构要考虑的内容。

选项 5:用于使用自定义元素的 API

这个提议是由 React 团队的@sophiebits@gaearon提供的

React 可以创建一个新的 API 来消费自定义元素,该 API 将元素的行为与配置对象进行映射。

伪代码示例:

const XFoo = ReactDOM.createCustomElementType({
  element: ‘x-foo’,
  ‘my-attr’: // something that tells React what to do with it
  someRichDataProp: // something that tells React what to do with it
});

上面的代码返回一个代理组件XFoo ,它知道如何根据您提供的配置将数据传递给自定义元素。 您将在您的应用程序中使用此代理组件,而不是直接使用自定义元素。

用法示例:

<XFoo someRichDataProp={...} />

优点

系统是明确的

开发人员可以告诉 React 他们想要的确切行为。

不间断的变化

开发人员可以选择使用该对象或继续使用当前系统。

惯用的反应

这种变化不需要新的 JSX 语法,感觉更像 React 中的其他 API。 例如,PropTypes(即使它被移动到自己的包中)有一个有点类似的方法。

缺点

对于一个复杂的组件来说可能需要做很多工作

Polymer 的paper-input元素有 37 个属性,所以它会产生一个非常大的配置。 如果开发人员在他们的应用程序中使用了大量自定义元素,那么他们可能需要编写大量配置。

可能会膨胀包大小

与上述点相关,每个自定义元素类现在都会产生其定义成本 + 其配置对象大小。

注意:我不是 100% 确定这是否属实。

Config 需要跟上组件的步伐

每次组件进行添加新属性的次要版本修订时,配置也需要更新。 这并不难,但它确实增加了维护。 也许如果配置是从源代码生成的,这会减轻负担,但这可能意味着需要创建一个新工具来为每个 Web 组件库生成配置。

抄送@sebmarkbage @gaearon @developit @treshugart @justinfagnani

DOM Discussion

最有用的评论

嘿伙计们,在我们等待的同时,我创建了一个用于在 React 中包装您的 Web 组件的 shim https://www.npmjs.com/package/reactify-wc

import React from "react";
import reactifyWc from "reactify-wc";

// Import your web component. This one defines a tag called 'vaadin-button'
import "@vaadin/vaadin-button";

const onClick = () => console.log('hello world');

const VaadinButton = reactifyWc("vaadin-button");

export const MyReactComponent = () => (
  <>
    <h1>Hello world</h1>
    <VaadinButton onClick={onClick}>
      Click me!
    </VaadinButton>
  </>
)

我希望这证明有帮助

(这是我第一次涉足 OSS,也是我办公室外的第一个开源项目之一。非常欢迎建设性的批评😄)

所有129条评论

为阅读太长而道歉,但我想确保我彻底探索了每个选项。 我不想用我自己的观点对事情产生太多偏见,但如果我可以选择,我想我会选择选项 3。

选项 3 向后兼容、声明性和显式。 无需维护回退启发式,其他库已经提供了类似的 sigils/modifiers。

为阅读太长而道歉,但我想确保我彻底探索了每个选项。 我不想用我自己的观点对事情产生太多偏见,但如果我可以选择,我想我会选择选项 3。
选项 3 向后兼容、声明性和显式。 无需维护回退启发式,其他库已经提供了类似的 sigils/modifiers。

我介于选项 2 和选项 3 之间,我认为 React 在过去处理行为和 API 变化非常好。 引入警告和文档链接可能有助于帮助开发人员了解幕后发生的事情。

选项 3 看起来很有吸引力,因为它是声明式的,在阅读 JSX 代码时,新来的开发人员会立即知道 React 在渲染元素时会做什么。

对选项 2 的评论

当 React 根据他们选择加载元素的方式设置属性而不是属性时,开发人员可能会感到困惑。

自定义元素的消费者是否需要理解这种区别? 还是只对自定义元素的作者重要? 似乎元素的作者需要处理 HTML 中使用的任何东西的属性(因为这是从 HTML 使用中传递数据的唯一方式)和属性,如果他们想要支持复杂的值或属性从 DOM 获取/设置。 甚至有可能作者最初将某些东西作为属性实现,然后添加具有相同名称的属性以支持更灵活的数据类型,并且仍然使用存储在属性中的值支持该属性。

与未来的 HTMLElement 属性和属性的命名冲突一般来说似乎是 Web 组件标准中的一个弱点,因为无论绑定方法如何,这都会导致错误。

如果一个元素有一个未定义的属性,并且 React 试图传递给它一个对象/数组,它会将它设置为一个属性。 这是因为 some-attr="[object Object]" 没有用。

根据值进行不同的绑定似乎令人困惑。 如果元素的作者没有指定属性 getter/setter 来处理该值,那么设置该属性将导致元素的行为就像从未指定值一样,这可能更难调试。

对选项 3 的评论

选项 3 的另一个潜在缺点是它要求自定义元素的使用者知道该元素是否已将某些内容实现为属性或属性。 如果您混合使用 React 组件和自定义元素,则使用一种语法设置 React props 和使用不同语法的自定义元素属性可能会令人困惑。

自定义元素的消费者是否需要理解这种区别? 还是只对自定义元素的作者重要?

我怀疑这实际上是一个大问题,因为正如您所指出的,元素作者应该为基础值定义一个属性和属性,并接受来自两者的数据。 我还要补充一点,他们应该保持属性和属性同步(因此设置一个设置另一个)。

与未来的 HTMLElement 属性和属性的命名冲突一般来说似乎是 Web 组件标准中的一个弱点,因为无论绑定方法如何,这都会导致错误。

我同意,但我不确定这是否是 React 需要尝试在他们的库中解决的问题。 感觉就像一个需要作为自定义元素规范的一部分来解决的问题。 我可以看看我们是否可以将其作为即将举行的 TPAC 标准会议的一部分进行讨论。

我应该补充一点,对于属性来说,这还不错,因为元素定义的属性会影响添加到 HTMLElement 的未来属性。 因此,如果您将数据作为 js 属性传递给自定义元素,它将继续工作。 主要问题似乎与属性有关,因为它们是全局的。

根据值进行不同的绑定似乎令人困惑。 如果元素的作者没有指定属性 getter/setter 来处理该值,那么设置该属性将导致元素的行为就像从未指定值一样,这可能更难调试。

在自定义元素被延迟加载和“升级”的情况下,它最初将具有未定义的属性。 这通过确保这些元素仍然接收它们的数据并且它们可以在升级后使用它来解决该用例。

确实,如果作者没有为某个值定义 getter/setter,这将不会很有用。 但是拥有my-attr=[object Object]也没什么用。 而且由于您不知道该属性是否真的未定义,或者它们的定义是否只是被延迟加载,所以设置该属性似乎是最安全的。

选项 3 的另一个潜在缺点是它要求自定义元素的使用者知道该元素是否已将某些内容实现为属性或属性。

我认为您今天基本上处于同一条船上,因为没有什么可以强制自定义元素作者定义属性而不是属性。 所以我可以有一个只有属性的 API 元素,它不会从 React 的当前系统接收任何数据,我需要知道使用ref来直接设置 js 属性。

因为自定义元素是一种原语,所以没有强制创建相应的属性和属性。 但是我们非常努力地鼓励这样做作为最佳实践,我今天所知道的所有库都为其属性创建了支持属性。

[编辑]

正如您在之前的观点中所说:

似乎元素的作者需要处理 HTML 中使用的任何东西的属性(因为这是从 HTML 使用中传递数据的唯一方式)和属性,如果他们想要支持复杂的值或属性从 DOM 获取/设置。

因为您永远不知道用户将如何尝试将数据传递给您的元素,所以无论如何您最终都需要具有属性-属性对应关系。 我想如果选项 3 提供,大多数人只会使用@符号绑定所有内容,因为它最简单。 这就是我今天在 Vue 中使用自定义元素的方式,因为它们公开了.prop修饰符。

它要求自定义元素的使用者知道该元素是否已将某些内容实现为属性或属性

这不是 React 应该担心的事情,正如 Rob 在我看来所说的那样,自定义元素作者有责任通知用户元素如何工作。

这实际上是我们今天需要做的方式,例如考虑<video>元素,假设您需要将其静音或更改组件内的当前时间。

muted用作布尔属性

render() {
  return (
    <div className="video--wrapper">
      <video muted={ this.state.muted } />
    </div>
  );
}

目前,您需要创建一个指向视频元素的ref并更改属性。

render() {
  return (
    <div className="video--wrapper">
      <video ref={ el => this.video = el } muted={ this.state.muted } />
    </div>
  );
}

然后创建一个事件处理程序、一个实例方法并手动将属性设置为 DOM 元素。

onCurrenTimeChange(e) {
  this.video.currentTime = e.value;
}

如果你考虑一下,它有点打破 React 本身对其 API 和 JSX 抽象层强加的声明性模型,因为currentTime显然是包装器组件中的一个状态,使用属性绑定我们仍然需要事件处理程序,但是JSX 抽象模型将更具声明性,因此不需要 refs:

render() {
  return (
    <div className="video--wrapper">
      <video muted={ this.state.muted } @currentTime={ this.state.currentTime } />
    </div>
  );
}

我的观点是,无论您是依赖原生元素还是自定义元素,您仍然需要根据文档了解如何绕过它们,不同之处在于,在第二种情况下,它应该来自自定义元素的作者。

@cjorasch我的两分钱 :)

如果我们从头开始设计它,而不需要考虑向后兼容性,我认为选项 1 将是 React 的“以 JavaScript 为中心的 DOM API”中最惯用的。

关于服务器端渲染,能否通过为应用程序代码提供 API 来通知 React 如何将自定义元素属性映射到属性来解决这个问题? 类似于 React 已经为平台定义的属性维护的映射? 这个 API 只需要为每个自定义元素名称调用一次(不是针对它的每个实例),并且只需要为不遵循与其属性的直接 1:1 对应关系的属性调用一次,这应该是相对罕见的。

如果我们担心这会造成太大的破坏性变化,那么我认为选项 3 也很有吸引力。 如果 sigil 表示一个属性,我会建议使用“.”,因为这已经是 JavaScript 的属性访问器。 但是,我认为让应用程序中使用自定义元素的每个实例负责了解何时使用属性与何时使用属性是不幸的。 我更喜欢选项 1 的一点是,即使需要一个属性映射到属性,该映射代码也可以与所有 JSX 用法隔离。

在自定义元素被延迟加载和“升级”的情况下,它最初将具有未定义的属性。 这通过确保这些元素仍然接收它们的数据并且它们可以在升级后使用它来解决该用例。

也许我不明白升级过程。 元素通常具有在类原型中定义为 getter/setter 的属性。 由于 getter/setter 的存在,即使属性值仍未定义,检查propName in element也会返回 true。 在升级期间,是否在某个临时实例上设置属性值,然后在延迟加载完成后将其复制到实际实例?

升级是自定义元素接收其类的过程。 在此之前,它不是该类的实例,因此属性 getter/setter 不可用。

@jeremenichelli

muted 作为布尔属性工作

刚刚检查过,它也有一个相应的属性,尽管它似乎没有在 MDN 上记录:P

目前,您需要创建一个指向视频元素的 ref 并更改属性。

是的,有时您会在现代 HTML 元素上遇到纯属性 API。 currentTime更新频率很高,因此将其反映到 HTML 属性是没有意义的。

我的观点是,无论您是依赖原生元素还是自定义元素,您仍然需要根据文档了解它们

是的,不幸的是没有一刀切的属性/属性规则。 但我认为一般来说,您可以严重依赖属性并提供语法,以便开发人员可以在特殊情况下使用属性。

@robdodson 是的,我也知道静音属性😄我只是用这两个来证明_在野外_已经没有你提到的一刀切规则。

我们将不得不依赖关于原生和自定义元素的文档,所以我不介意这个决定。

在编写最后一个代码片段时,我有点喜欢属性绑定 💟

@effulgentsia

但是,我认为让应用程序中使用自定义元素的每个实例负责了解何时使用属性与何时使用属性是不幸的。

我认为今天已经是这种情况了。 由于主要的自定义元素库(聚合物、滑板,可能还有其他?)会自动为所有公开的属性创建支持属性,因此开发人员可以只为自定义元素上的每个属性使用印记。 他们需要切换到使用属性的情况可能很少见。

@乔拉施

回复:升级。 正如@effulgentsia 所提到的,页面上可能有一个自定义元素,但稍后加载其定义。 <x-foo>最初将是HTMLElement的实例,当我稍后加载它的定义时,它会“升级”并成为XFoo类的实例。 此时,它的所有生命周期回调都被执行。 我们在 Polymer Starter Kit 项目中使用了这种技术。 有点像这样:

<app-router>
  <my-view1></my-view1>
  <my-view2></my-view2>
</app-router>

在上面的例子中,我们不会加载my-view2的定义,直到路由器改变它。

完全有可能在元素升级之前为其设置属性,并且一旦加载了定义,元素就可以在其生命周期回调之一期间获取该数据。

开发人员可以只为自定义元素上的每个属性使用印记

如果开发人员开始这样做,那么如何区分使用属性因为您“可以”使用属性,因为您“必须”使用属性? 这不是服务器端渲染所需的差异化吗?

如果开发人员开始这样做,那么如何区分使用属性因为您“可以”使用属性,因为您“必须”使用属性?

对不起,也许我措辞有误。 我的意思是开发人员可能会使用该标志,因为它会给出最一致的结果。 你可以用它来传递原始数据或丰富的数据,比如对象和数组,它总是有效的。 我认为在运行时使用属性通常比使用属性更受欢迎,因为属性往往更多地用于初始配置。

这不是服务器端渲染所需的差异化吗?

可能的情况是,在服务器上,印记会回退到设置属性。

可能的情况是,在服务器上,印记会回退到设置属性。

如果 sigil 的原因是它是一个不作为属性存在的属性,例如视频的currentTime ,我认为这不会起作用。

区分使用属性是因为您“可以”使用属性,因为您“必须”使用属性

我认为这种区分很重要,因为选择使用属性或属性作为优化(例如,SSR 首选属性与客户端渲染首选属性)与仅作为属性或仅作为属性存在的事物的原因完全不同一个财产。

关于服务器端渲染,能否通过为应用程序代码提供 API 来通知 React 如何将自定义元素属性映射到属性来解决这个问题?

更具体地说,我建议是这样的:

ReactDOM.defineCustomElementProp(elementName, propName, domPropertyName, htmlAttributeName, attributeSerializer)

例子:

// 'muted' can be set as either a property or an attribute.
ReactDOM.defineCustomElementProp('x-foo', 'muted', 'muted', 'muted')

// 'currentTime' can only be set as a property.
ReactDOM.defineCustomElementProp('x-foo', 'currentTime', 'currentTime', null)

// 'my-attribute' can only be set as an attribute.
ReactDOM.defineCustomElementProp('x-foo', 'my-attribute', null, 'my-attribute')

// 'richData' can be set as either a property or an attribute.
// When setting as an attribute, set it as a JSON string rather than "[object Object]".
ReactDOM.defineCustomElementProp('x-foo', 'richData', 'richData', 'richdata', JSON.stringify)

对于只能是属性的东西(其中htmlAttributeName为空),SSR 会跳过渲染它,然后在客户端上将其水化。

对于只能是属性的东西(其中domPropertyName为空),React 将调用setAttribute()目前在 v16 中。

对于两者兼而有之的事情,React 可以选择最优化的策略。 也许这意味着总是在客户端设置为属性,但在服务器端设置为属性。 也许这意味着在最初创建元素时设置为属性,但在以后从 vdom 修补时设置为属性。 也许这意味着仅当值是原始类型时才设置为属性。 理想情况下,React 应该能够随时更改策略作为内部实现细节。

当 React 遇到一个没有为其调用defineCustomElementProp()并且未被 HTML 规范定义为全局属性或属性的 prop 时,React 可以实现一些默认逻辑。 例如,也许:

  • 在版本 17 中,使用 v16 维护 BC 并设置为属性。
  • 在版本 18 中,假设它可以是其中之一并遵循最佳策略。

但无论如何,通过将其保留为单独的 API,JSX 和props对象将保持干净并位于单个命名空间内,就像它们用于 React 组件和非自定义 HTML 元素一样。

抱歉,评论过多,但我想到了我想分享的上述提案的另一个好处:

这些ReactDOM.defineCustomElementProp()调用可以在由自定义元素作者维护的 JS 文件中提供(在与维护/分发自定义元素的位置相同的存储库中)。 对于具有严格的 1:1 属性/属性对应关系的自定义元素,不需要它,根据本期的背景声明,无论如何,这都是推荐和多数情况。 因此,只有不遵循此建议的自定义元素作者才需要提供 React 集成文件。 如果作者不提供它(例如,因为自定义元素作者不关心 React),那么在 React 应用程序中使用该自定义元素的人们社区可以自组织一个中央存储库来容纳该集成文件。

我认为这种集中化的可能性比要求自定义元素的每个用户始终必须使用符号明确的解决方案更可取。

选项 3 将是我的首选,但这是一个巨大的突破性变化......相反呢? 属性有前缀不是道具?

React 中的 Sigils,我不知道我对此有何感受。 JSX 规范应该被认为是通用的,而不是过度依赖或依赖浏览器的特性,尤其是不能因为向后兼容而导致的不规范。 obj[prop] = valueobj.setAttributes(props, value)行为不同是不幸的,但从整体上看浏览器 api,并不奇怪。 @ : []会使实现细节泄漏到表面并与以 javascript 为中心的方法相矛盾。 因此,除非我们有一个规范来执行以下操作,否则我认为这是一个坏主意: const data = <strong i="8">@myFunction</strong> // -> "[object Object]"

如果我不得不依赖一个 Web 组件,如果语义隐藏在 React 和 JSX 之外,并确保它们不会引入破坏性更改,我会很高兴。 从所有选项来看,保留ref => ...似乎对我有利。 ref专门用于访问该对象。 至少开发人员确切地知道发生了什么,没有符号泄漏,也没有可能破坏现有项目的新属性。

@LeeCheneler

选项 3 将是我的首选,但这是一个巨大的突破性变化......相反呢? 属性有前缀不是道具?

为什么会发生重大变化? 默认属性的当前行为将保持不变。 符号将是可选的,开发人员将使用它来替换代码中目前使用ref将数据作为 JS 属性传递给自定义元素的位置。

@drcmda

既不是可以破坏现有项目的新属性。

你能澄清一下你的意思吗?

对于任何关注讨论的人,仅供参考,我已经使用 React 团队成员建议的第 5 个选项更新了 RFC。

@罗布多森

我指的是这个:

选项 4:添加属性对象
缺点
这可能是一个突破性的变化

选项 5 对我们来说似乎是最安全的。 由于生态系统仍处于“弄清楚”阶段,因此我们无需立即就“隐式”API 做出决定即可添加该功能。 我们总是可以在几年后重新审视它。

Polymer 的 paper-input 元素有 37 个属性,所以它会产生一个非常大的配置。 如果开发人员在他们的应用程序中使用了大量自定义元素,那么他们可能需要编写大量配置。

我的印象是 React 中的自定义元素用户最终会希望将一些自定义元素包装到 React 组件中,以用于特定于应用程序的行为/自定义。 如果一切都已经React 组件,那么对于这种情况,这是一个更好的迁移策略,例如

import XButton from './XButton';

这恰好是由

export default ReactDOM.createCustomElementType(...)

这让他们可以在任何时间点使用(甚至不使用)自定义元素的自定义组件替换 React 组件。

因此,如果人们要在互操作点创建 React 组件,我们不妨提供一个强大的助手来这样做。 人们也可能会为他们使用的自定义元素共享这些配置。

最终,如果我们看到生态系统稳定下来,我们可以采用无配置的方法。

我认为下一步是编写详细的建议,说明配置应该如何满足所有常见用例。 对于自定义元素 + React 用户来说,它应该足够引人注目,因为如果它不回答常见用例(如事件处理),我们将最终陷入困境,该功能无法提供足够的好处来抵消冗长.

根据我之前的评论构建,如何:

const XFoo = ReactDOM.createCustomElementType('x-foo', {
  propName1: {
    propertyName: string | null,
    attributeName: string | null,
    attributeSerializer: function | null,
    eventName: string | null,
  }
  propName2: {
  }
  ...
});

然后,对于 XFoo 实例上的每个 React 道具,逻辑将是:

  1. 如果该道具的eventName不为空,则将其注册为调用道具值(假设为函数)的事件处理程序。
  2. 否则,如果呈现客户端并且propertyName不为空,则将元素属性设置为 prop 值。
  3. 否则,如果attributeName不为空,则将元素属性设置为字符串化的 prop 值。 如果attributeSerializer不为 null,则使用它来对 prop 值进行字符串化。 否则,只需执行'' + propValue

Polymer 的 paper-input 元素有 37 个属性,所以它会产生一个非常大的配置。

我想建议配置只对异常值道具是必要的。 对于 XFoo 实例上未包含在配置中的任何道具,默认为:

  • 如果值是一个函数:
eventName: the prop name,
  • 别的:
propertyName: the prop name,
attributeName: camelCaseToDashCase(the prop name),

或者,也许将事件保存在单独的命名空间中是有意义的,在这种情况下,从最后一条评论中删除与eventName的所有内容,而是让事件注册为:

<XFoo prop1={propValue1} prop2={propValue2} events={event1: functionFoo, event2: functionBar}>
</XFoo>

@gaearon @effulgentsia你们对选项 1 和选项 5 的组合有何看法?

选项 1 将使自定义元素的临时用户更容易传递丰富的数据。 我正在想象我正在构建一个应用程序的场景,我只想使用几个自定义元素。 我已经知道它们是如何工作的,而且我并没有那么投入,以至于我想为它们编写一个配置。

选项 5 适合那些想要在他们的应用程序中使用诸如纸质输入之类的东西并且真的想将其整个 API 公开给他们团队中的每个人的人。

对于选项 1 的 SSR,如果在服务器上呈现,启发式可以始终使用属性。 camelCase 属性被转换为 dash-case 属性。 这似乎是 Web 组件库中非常常见的模式。

我非常喜欢 option1 + option5 组合的想法。 这意味着对于大多数自定义元素:

<x-foo prop1={propValue1}>

将按预期工作:prop1 设置为属性客户端和(短划线框)属性服务器端。

对于上述不适合他们的任何事情,人们可以切换到 option5。

尽管与 React 16 的工作方式相比,这将是一个突破性的变化。 对于任何遇到这种破坏的人(例如,他们使用具有不受属性支持的属性的自定义元素),他们可以切换到 option5,但这仍然是一种破坏。 我把它留给 React 团队来决定这是否可以接受。

啊,这就是我在火车上快速阅读这篇文章的收获@robdodson 🤦‍♂️ ...现在不是选项 3 的真正粉丝🤔我把它读成一个全押的道具前缀,因此我犹豫了。

选项 5 似乎合理且直接。

我喜欢@effulgentsia的发展方向。 有没有不能的原因:

const XFoo = ReactDOM.createCustomElementType('x-foo', {
  propName1: T.Attribute,
  propName2: T.Event,
  propName3: T.Prop
})

或者在单个道具上支持多种类型有价值吗?

尽管@effulgentsia,我会对这个流程

如果值是一个函数:
eventName:道具名称,
别的:
propertyName:道具名称,
属性名称:camelCaseToDashCase(道具名称),

我不认为我想要一个函数道具默认为一个事件,并且同时分配 propertyName 和 attributeName 是明智的吗? 你什么时候希望两者都得到支持来模仿上面的问题? 🙂

@李切内勒

引用问题摘要的选项 1 优点:

使用 Polymer 或 Skate 等库创建的任何元素都将自动生成属性以支持其公开的属性。 这些元素都应该与上述方法“正常工作”。 鼓励开发人员手工编写 vanilla 组件支持具有属性的属性,因为这反映了现代(即不是像<input>这样的古怪)HTML5 元素( <video><audio>等)已经实施。

所以这就是分配 propertyName 和 attributeName 是明智的原因:因为它反映了遵循最佳实践的元素的实际情况。 通过让 React 意识到这一点,它允许 React 根据情况决定使用哪个:例如使用属性进行客户端渲染和使用属性进行服务器端渲染。 对于不遵循最佳实践并且具有一些没有相应属性的属性和/或一些没有相应属性的属性的自定义元素,React 需要意识到这一点,以便在 SSR 和属性期间不会呈现无属性的属性-less-attributes 可以在客户端渲染期间使用 setAttribute() 设置。

根据您的提议,这可能可以通过位组合标志来完成,例如:

propName1: T.Property | T.Attribute,

但是,这不会提供一种表达属性名称与属性名称不同的方法(例如,camelCase 到破折号大小写)。 它也不会提供一种方法来表达如何在 SSR 期间将富对象序列化为属性(“[object Object]”的当前行为没有用)。

我不认为我想要一个函数道具默认为一个事件

是的,我想我也同意这一点,因此后续评论。 感谢您证实我的犹豫!

这是我之前建议的一个不太冗长的版本的想法:

const XFoo = ReactDOM.createCustomElementType('x-foo', {
  UNREFLECTED_ATTRIBUTES: [
    'my-attr-1',
    'my-attr-2',
  ],
  UNREFLECTED_PROPERTIES: [
    'myProp1',
    'myProp2',
  ],
  REFLECTED_PROPERTIES: {
    // This is default casing conversion, so could be omitted.
    someVeryLongName1: 'some-very-long-name-1',

    // In case anyone is still using all lowercase without dashes.
    someVeryLongName2: 'someverylongname2',

    // When needing to define a function for serializing a property to an attribute.
    someRichData: ['some-rich-data', JSON.stringify],
  },
});

根据上面的代码注释,我强烈建议不要要求定义每个反射属性,而是将未定义为自动定义为属性名称为破折号版本的反射属性的任何内容默认。

有道理@ effulgentsia👍

我喜欢你的第二个例子,但如果添加更多类型,ala events +任何可能有意义的东西,它是否不会对组合爆炸开放?

- UNREFLECTED_ATTRIBUTES
- UNREFLECTED_PROPERTIES
- UNREFLECTED_EVENTS
- REFLECTED_PROPERTIES_ATTRIBUTES
- REFLECTED_PROPERTIES_EVENTS
- REFLECTED_ATTRIBUTES_EVENTS
- REFLECTED_PROPERTIES_ATTRIBUTES_EVENTS
...

尽管我想您无论如何都不想将事件与道具或属性混合在一起🤔 属性和道具可能是您唯一想要模仿的东西。

我认为 React 和 Web 组件社区都有机会在最佳实践上保持一致。 React 在这里发表意见将在自定义元素作者被引导到正确的方向上大有帮助,因为它的广泛采用和其意见的重要性。

尽管我已经编写了选项 4 的实现,但我总是不得不将属性和事件与属性分开。 理想情况下,我更喜欢选项 1。实际上,我想我更喜欢带有逃生舱口的选项 2。

选项 1 是理想的,但是有许多类型的属性没有相应的属性(aria / 数据),因此它需要围绕这些进行额外的启发式方法,如果存在元素可能具有属性的任何边缘情况,则该属性应该具有属性,但无论出于何种原因都不要实现。 我觉得这是一个应该仔细考虑的选择,但从长远来看可能是可行的。

选项 2 更可取,因为它是一个非破坏性的更改。 它适用于在创建实例之前注册自定义元素定义(或在设置属性之前加载)的所有情况。 此选项的现有技术是 Preact (cc @developit),到目前为止它运行良好。 沿着这条路走下去,可以得到一个相当健壮的、不间断的实现,这个实现已经被一个成功的 React 变体证明了,并且它在大多数情况下都有效。 如果有的话,它为 React 提供了一个短期解决方案,同时为长期评估更好的解决方案。

对于它不起作用的情况(情况s ?) - 延迟加载自定义元素定义和我们没有涵盖的任何其他元素 - 可以实现像增量 DOM 所做的那样的逃生舱口。 这类似于@effulgentsia的提议,但可以更好地扩展到x自定义元素的数量。 如果消费者想在每个自定义元素的基础上执行此操作,他们仍然可以,因为它只是一个函数。 这允许 React 通过将所有边缘情况的责任移交给消费者来发表意见、逃避孵化并满足所有用例。 这也是我之前与@developit讨论

关于未来HTML属性的担忧,这是我们无法解决的问题,所以我认为我们不应该在这里纠结于它。

这些ReactDOM.defineCustomElementProp()调用可以在自定义元素作者维护的 JS 文件中提供

这会将自定义元素实现与特定于库的实现(在本例中为 React)联系起来。 在我看来,这对自定义元素作者来说负担太重了。 此外,对于不使用 React 的作者来说,说服他们发布定义会进入没人想要的政治讨论。

仅使用用户实际使用的属性/属性来定义自定义元素的用户对此类 API 的调用,需要维护的代码更少,表现力更强。

即使库作者不使用 React,我认为对于那些 Web 组件的客户端来说,一次定义属性配置然后使用它是更好的 UX。

第一次使用需要属性的 web 组件时,就像添加一个 sigil 一样困难(更冗长,但也更明确),但随后的使用更容易,因为您不需要考虑它该组件的每个调用点。 在任何一种情况下,当采用新的 Web 组件时,您都需要了解属性/属性差异,但使用单一共享配置,您不需要同一应用程序中该组件的每个用户都考虑它。

我们可能允许在该配置中重新映射属性名称,以允许在那里创建更多 React 惯用名称。 升级到更复杂的包装器的路径(如果需要的话)也更顺畅,因为您可以将 XFoo 替换为自定义 React 组件,该组件可以执行所需的任何花哨的翻译逻辑。

基本上:如果人们在每次使用组件时都可以不考虑属性配置,我宁愿采用这种方法。 这就是我建议选项 5 的原因。我认为它比 3 和 4 更符合人体工程学,同时同样灵活。

选项 1 和 2 不适用于服务器渲染(HTML 生成),选项 2 还为 Web 组件作者带来升级风险,现在添加与现有属性同名的属性是一个重大更改。 如果我们认为 SSR 没有用,那么从 React 的角度来看,选项 1 非常有吸引力。 我不熟悉它在实践中是否足够全面 - 组件作者是否通过属性公开所有内容?

@treshugart 在没有深入了解自行车棚的情况下,您认为您可以展示您期望“逃生舱门”的工作方式吗? 伪代码没问题。 就这样我们都在同一页面上。

抱歉@sophiebits刚刚看到您的回复。 我想在多读几遍后回应其中的几点,但想快速回应这一点:

我不熟悉它在实践中是否足够全面 - 组件作者是否通过属性公开所有内容?

任何使用 Polymer 或 Skate 构建的组件都将通过属性公开所有内容。 可能有一些罕见的异常值(可能只是为了样式或其他东西设置一个属性)但除此之外我认为事情总是由属性支持。 我非常有信心,大多数生产中的 Web 组件都是使用这两个库之一构建的。

如果有人手工编写了一个 vanilla web 组件,没有什么可以强迫他们通过属性公开所有内容,但我们鼓励他们在我们的最佳实践文档示例中这样做

对于它的价值,也没有什么强迫他们使用属性。 自定义元素定义只是一个类,因此开发人员可以为所欲为。 但在实践中,大多数人更喜欢使用像 Polymer 或 Skate 这样的抽象来创建他们的组件,所以他们免费获得了一个属性 API。

也许最好的方法是始终在客户端处理属性,然后为想要在其应用程序中支持 SSR 的人支持原始属性名称到属性名称的 Option 5 样式配置映射。

@sophiebits :我认为这是一个好主意,但对于使用属性名称编写 React 应用程序的人来说,这是一个重大变化。 例如:

<x-foo long-name={val} />

但是,如果 propname 没有破折号,则“do properties”规则和“do attributes”(如果有)的规则呢?

@robdodson :您是否知道有任何自定义元素框架或实践,其中人们将拥有与相应属性不同的属性大小写,而不包含破折号? 即,带有longname属性的longName属性? 这实际上似乎是更常见的带有内置 HTML 元素和全局属性的模式(例如, contenteditable => contentEditable ),但我不清楚现在是否已经足够劝阻由于 Polymer 和 Skate,用于自定义元素属性。 如果它仍然是一个问题,那么现有的 JSX 具有:

<x-foo longname={val} />

如果属性为longName则作为属性集失败。

@effulgentsia我只能说 Skate,但我们只推荐在编写 HTML 时使用属性 API,这 - 出于所有意图和目的 - 也可以归类为服务器渲染。 如果你通过 JS 做任何事情,你应该设置道具。 我们的props API 自动从属性进行单向同步/反序列化,并通过对属性名称进行破折号(camelCase 变为camel-case 等)来派生属性名称。 这种行为作为一个整体是可配置的,但我们鼓励最佳实践是上述。

正如许多人所说,道具使 SSR 变得困难,这是一个值得关注的问题。 由于听起来大多数人更喜欢选项 1,也许我们尝试推进@sophiebits的提议,即在提供后备/映射的同时在客户端上设置道具? 我认为这意味着将在服务器上设置属性?

举个例子,这里是你如何使用 Skate(以及任何实现renderedCallback()props和/或state setters。在自定义元素级别这样做是微不足道的。如果 React 沿着在服务器上设置属性的道路走下去,补水基本上是一样的。Skate 组件作者实际上不需要像我们一样做任何事情'已经为他们提供了反序列化逻辑。

@robdodson我会做与增量 DOM 正在做的类似的事情。 这看起来像:

const isBrowser = true; // this would actually do the detection
const oldAttributeHook = ReactDOM.setAttribute;

// This is much like the IDOM impl but with an arguably more clear name.
ReactDOM.setAttribute = (element, name, value) => {
  // This is essentially option 2, but with the added browser check
  // to keep attr sets on the server.
  if (isBrowser && name in element) {
    element[name] = value;
  } else {
    oldAttributeHook(element, name, value);
  }
};

@sophiebits

选项 2 还为 Web 组件作者带来了升级风险,现在添加与现有属性同名的属性是一项重大更改。

考虑到属性在平台中的工作方式,我认为这可能是不可避免的。 如果您对自定义元素进行 SSR,因此必须写入属性,则您还面临平台将来会发布类似名称的属性的风险。 正如@treshugart之前提到的,我不认为这是 React(或任何库)有权解决的问题。 这是我想与 TPAC 的 Web 组件规范作者讨论的事情,看看我们是否可以在自定义元素空间中修复它。

我不确定这是否会改变您对选项 2 的看法😁,但我想提一下,因为选项 2 有一些不错的奖励,而 Preact 似乎在实践中证明了这一点。

也许最好的方法是始终在客户端处理属性,然后为想要在其应用程序中支持 SSR 的人支持原始属性名称到属性名称的 Option 5 样式配置映射。

+1,我支持继续朝着这个方向前进。


@effulgentsia

我认为这是一个好主意,除了对于使用属性名称编写 React 应用程序的人来说,这是一个重大变化。

选项 2 可以解决这个问题:)
否则,可能需要结合选项 1 的启发式方法将破折号属性映射到驼峰式属性。

但是,如果 propname 没有破折号,则“do properties”规则和“do attributes”(如果有)的规则呢?

如果名称中有破折号,Preact 似乎会设置该属性。 我假设那是因为他们使用选项 2 并且long-name没有通过in检查,所以它回退到一个属性( source )。

我个人喜欢这种行为,但它又回到了设置属性和未来可能发生冲突的领域,所以我认为@sophiebits应该权衡

您是否知道任何自定义元素框架或实践,其中人们将拥有与相应属性不同的属性大小写,而不包含破折号?

从来没听说过。 破折号是图书馆知道骆驼案例在哪里的一种简单方法。 如果您正在手工编写一个 vanilla web 组件,您可以执行longname/longName并且只有选项 2 可以拯救您,因为它找不到longname属性,但它会回退到以前使用的longname属性。

对于它的价值,它看起来像 Preact,作为最后的手段,在设置属性之前调用toLowerCase() 。 因此,如果您在 SSR 中使用<x-foo longName={bar}/>它也会正确地回退到longname=""属性。


@treshugart

也许我们尝试推进@sophiebits的提议,即在提供后备/映射的同时在客户端上设置道具? 我认为这意味着将在服务器上设置属性?

是的,是的。

我会做与增量 DOM 正在做的类似的事情

每个元素(或它们的基类)都需要将其混合在一起的想法吗?

btw,谢谢大家继续参与讨论,我知道已经很久了❤️

不确定我是否理解https://github.com/facebook/react/issues/11347#issuecomment -339858314 中的最后一个示例,但我们极不可能提供这样的全局可覆盖钩子。

@gaearon我认为 Trey 只是将净变化显示为猴子补丁,大概它会被写入 ReactDOM 实现本身。

根据最近的评论,你们对这个提议有什么看法?

  1. 对于 BC,不要更改小写自定义元素在 React 中的工作方式。 换句话说,像<x-foo ... />这样的 JSX 在 React 17 中将继续以与 16 中相同的方式工作。或者,如果需要修复 _minor_ 错误,请打开一个单独的问题来讨论这些问题。

  2. 添加无配置 API 以创建具有更好自定义元素语义的 React 组件。 例如, const XFoo = ReactDOM.customElement('x-foo');

  3. 对于上面创建的组件,使用以下语义:

  4. 对于 XFoo 上任何保留 React 道具名称的道具( childrenref ,还有其他任何道具?),对其应用通常的 React 语义(不要将其设置为属性或属性在自定义元素 DOM 节点上)。
  5. 对于 HTML 规范定义的任何 HTMLElement 全局属性属性(包括data-*aria-* ),对这些 props 做同样的事情,如果它们在div上,React 会对它们做同样的事情<XFoo data-x={} className={} contentEditable={} />上的 props 应用于 DOM 节点应该与它对<div data-x={} className={} contentEditable={} /> (对于客户端和 SSR)。
  6. 对于包含破折号的 XFoo 上的任何道具(除了上面允许的全局属性),发出警告(通知开发人员 XFoo 是一个以属性为中心的,而不是一个以属性为中心的 API)并且对它不做任何处理(既不将其设置为属性或属性)。
  7. 对于 XFoo 上以下划线或大写 ASCII 字母开头的任何道具,不要对其进行任何操作(可能会发出警告?)。 两者都不是为自定义元素命名属性的推荐方式,因此保留这些命名空间以备将来语义使用(例如,参见下面的 #4)。
  8. 对于 XFoo 上的所有其他道具,在渲染客户端时,将其设置为 DOM 节点上的属性。 对于SSR,如果值为primitive,则将其渲染为属性; 如果非原始,则跳过渲染并将其设置为水化期间的属性客户端。 对于作为属性的 SSR 渲染,名称为 camelCaseToDashCase。

  9. 对于通过上面 #2 中的 API 创建的组件,保留一个 prop 名称以用于事件侦听器。 例如, 'events''eventListeners' 。 或者,如果不想与那些潜在的自定义元素属性名称冲突,则'_eventListeners''EventListeners' 。 ReactDom 创建的XFoo将自动在 DOM 节点上注册这些事件侦听器。

  10. 对于边缘情况(例如,使用上述实现不理想或不充分的自定义元素),应用程序开发人员可以实现他们自己的 React 组件来做任何他们需要的特殊事情。 即,他们不需要使用ReactDOM.customElement()或者他们可以扩展它。

  11. 对于那些想要上述所有行为,但希望他们的 JSX 使用小写自定义元素名称的人( <x-foo />而不是<XFoo /> ,出于类似的原因,熟悉编写 HTML 的人更喜欢<div>超过<Div> ),他们可以对 React.createElement() 进行猴子补丁。 这将是一个非常简单的猴子补丁:只需取第一个参数,如果它与您想要的自定义元素名称匹配(无论是特定列表还是任何包含所有小写字母和至少一个破折号的字符串),然后调用ReactDOM.customElement()并将结果和剩余参数转发到原始React.createElement()

@developit @gaearon也可以是。 如果“映射”是必要的,我认为钩子更可持续。 然而,正如 Jason 指出的那样,如果它在 ReactDOM 的核心中实现,这也是为了显示净变化。

对于 BC,不要更改小写自定义元素在 React 中的工作方式。 换句话说,JSX 喜欢将继续在 React 17 中以与 16 中相同的方式工作。或者,如果需要对其进行小错误修复,请打开一个单独的问题来讨论这些问题。

就我个人而言,我更希望能够按原样使用我的<x-foo>元素,并且无需首先包装它即可轻松传递它的属性。

如果可能的话,我宁愿去与@sohpiebits“建议选择1(客户端性能)和5(配置为SSR)。 我仍然希望人们会重新考虑选项 2(也许选项 2 + 5?)以获得向后兼容性奖励。

@gaearon如果 Trey 的提议是 ReactDOM 核心的一部分,你的意见会改变吗? 如果这有帮助,也许我们可以更充实这个例子?

我对选项 5 的主要担忧是创建大量样板以允许互操作性,破坏 DX,对于所有 React 核心团队和贡献者来说,花费大量时间进行不会产生预期影响的更改将是一种耻辱。

我真的很喜欢 React 团队如何处理 repo 中的最后更改,比如 _PropTypes_,最好考虑一个涉及不止一个开关的计划,以教育开发人员将来可能针对自定义而不是自定义进行的更改元素。

我认为让我们所有人都满意的解决方案是将其中一些选项组合在一起,例如_steps_、API 添加、警告和弃用或行为改变。

也许选项 5,带有警告,稍后的选项 2 弃用所需的包装器。

也许选项 5,带有警告,稍后的选项 2 弃用所需的包装器。

我实际上在想,做相反的事情会是一系列更好的步骤。 选项 1 或 2,因为它不是一个戏剧性的变化。 并衡量其进展情况以及 Web 组件的 SSR 故事开始的样子。 然后跟进选项 5,因为它添加了一个新的 API 和代理/包装器组件的概念。

选项 1 的主要问题是它是一个相当大的 BC 中断。 选项 2 的主要问题是它具有竞争条件,具体取决于元素何时完成升级。 对于像 React 一样广泛使用的项目,我不确定其中任何一个是否真的可以接受。

鉴于选项 5 的可用性,我想知道我们是否可以对非选项 5 的用法进行更安全但仍然有用的改进。 例如,选项 4 的反转如何:简单地引入domPropertieseventListeners道具,这样你就可以做这样的事情:

<x-foo 
  my-attr1={...} 
  domProperties={{myRichDataProperty: ...}} 
  eventListeners={{'a-custom-element-event':  e => console.log('yo')}} 
/>

这是完全向后兼容的,因为由于它们的大写字母, domPropertieseventListeners不是有效的属性名称。 除了 React 16 目前调用 setAttribute() 甚至对于带有大写字母的 prop 名称,依靠浏览器在内部将名称小写; 然而,React 16 的未来小版本是否会在遇到不是有效属性名称的自定义元素 props 时发出警告,以便人们可以在升级到 React 17 之前修复他们的大小写错误?

除了保留 BC 之外,这种方法很容易理解:它以属性为中心(意味着 React props 被视为元素属性),考虑到元素名称本身全部小写并带有破折号,因此以 HTML 为中心,这很合适。 但是, domProperties是为比较少见的需要传递属性的情况提供的,比如传递丰富的数据。

对于想要将他们的心智模型转换为以属性为中心的人(ala 选项 1),这就是选项 5 样式 API 可以出现的地方:

const XFoo = ReactDOM.customElement('x-foo');
<XFoo prop1={} prop2={} data-foo={} aria-label={} />

使用这种语法,每个道具都被视为一个属性(选项 1)。 哪个合适,因为元素“名称”(XFoo)也遵循以 JS 为中心的约定。 我认为我们仍然希望至少支持将data-*aria-* props 视为属性,我们可以将其限制为仅这些,或者概括为将任何带有破折号的道具视为属性属性。

同时,为了支持 SSR 的 option 5 配置,我们可以在ReactDOM.customElement添加一个配置 API,例如:

const XFoo = ReactDOM.customElement('x-foo', ssrConfiguration);

也许ssrConfiguration可能是一个类似于@treshugart评论中ReactDOM.setAttribute的回调函数?

你怎么认为?

@effulgentsia我喜欢你的想法。 稍微修改一下名称: domProps / domEvents 。 这与选项 4 非常接近。

WRT SSR,我认为 SSR 可以由自定义元素本身处理,只要 React 可以尊重组件在不跟踪它们时对其自身进行变异的属性。 我之前发布了一个要点,但为了方便起见,这里再次发布: https :

@effulgentsia我也喜欢你要去的地方。 @sophiebits@gaearon
大家对这个方向有想法吗?

2017 年 10 月 31 日,星期二,晚上 7:33,Trey Shugart通知@ github.com 写道:

@effulgentsia https://github.com/effulgentsia我喜欢你的地方
想法正在发生。 把名字稍微改一下,可能有用
对齐它们的命名:domProps / domEvents,或者什么的。

WRT 选项 2,它至少向后兼容并解决了大多数用例,
但我正在寻找替代方案。

我认为 SSR 可以由自定义元素本身处理,只要
如果组件发生变异,React 可以尊重它自身的属性
不跟踪他们。 我之前发布了一个要点,但又来了,因为
方便:
https://gist.github.com/treshugart/6eff0da3c0bea886bb56589f743b78a6。
本质上,组件在服务器上渲染后应用属性
并在客户身上补充水分。 Web 组件的 SSR 是可能的,但是
解决方案仍在标准层面进行讨论,因此可能
最好在这里等待提案的这一部分。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/react/issues/11347#issuecomment-340960798
或静音线程
https://github.com/notifications/unsubscribe-auth/ABBFDeiQhBWNGXNplbVV1zluYxT-ntFvks5sx9hngaJpZM4QD3Zn
.

稍微修改一下名称:domProps / domEvents。

我喜欢那些。 我也在集思广益,是否有办法通过将“dom”的概念替换为“ref”的概念来使它们更符合 React 的习惯。 所以换句话说: refProps / refEvents ,因为它们是关于将道具和事件侦听器附加到“ref”。

然后我想,如果不是在this.props中引入新的特殊名称,我们只是重载现有的ref JSX 属性会怎样。 目前,“ref”是一个在 React 组件挂载和卸载时调用的函数。 如果我们允许它也是一个对象会怎样:

<x-foo my-attr-1={}
  ref={{
    props: ...
    events: ...
    mounted: ...
    unmounted: ...
  }}
/>

只是一个想法。

我真的很喜欢这种方法:)

对此进行友好 ping。 @gaearon @sophiebits你们对@effulgentsia的最新提案有什么想法吗? 只是好奇它是在棒球场还是非首发。

我们刚刚开启了一个 RFC 流程。 我们可以要求你们中的任何一个将其作为 RFC 提交吗?
https://github.com/reactjs/rfcs

我宁愿不添加domPropertieseventListeners "props"(或 ref={{}} 对象中的等效项),因为它使使用自定义元素非常不自然,并且与所有其他 React 组件不同。 如果组件用户需要敏锐地了解属性和属性等之间的区别,那么我宁愿将其作为 ReactDOM.createCustomElementType 样式的解决方案来做。 那么如果你只使用一次组件,它的工作量是相当的(指定配置然后使用一次),但是如果你多次使用组件,那么你不需要每次都考虑配置对象。 除非我遗漏了什么,否则要求每次都指定配置似乎无法实现干净的自定义元素集成。

@sophiebits好的,我可以尝试为类似的内容编写 RFC。 您如何看待您在 10 月 26 日提出的首先在客户端获取属性并允许人们为 SSR 编写 ReactDOM.createCustomElementType 和/或如果他们想要真正细粒度地控制客户端如何映射属性/属性的想法? ?

至少使用createCustomElementType样式,库可以轻松地将其 API 映射到该样式中。 即,我们的 skatejs/renderer-react 可以轻松地使用props并为 React 配置自定义元素。 不过,这种让香草人高高干爽,没有抽象或进行一些工作。 我喜欢 Rob 关于安全默认值的建议,同时允许细粒度控制。 那会起作用吗?

@robdodson我想我已经

选项 3 是迄今为止最好的选项,它可以与 SSR 一起使用,我将解释如何操作。

到目前为止,所有选项都是自己的,除了 3,

  • 要么为 React 源之外的每个人都带来高维护负担
  • 或者他们强制整个宇宙中的所有自定义元素作者都遵守 React 特定的规则。

以下是我们都同意的普遍规则:

  • 使用setAttribute设置属性是 Universe 中最标准的方式,用于以与声明性 HTML 属性一对一匹配的方式将数据传递给元素。 这必须在 100% 的时间内作为宇宙法则起作用,因此它是唯一 100% 保证将数据传递给元素的方式。
  • 有些人不高兴它只是为字符串设计的。
  • 某些元素(我再说一遍, _only some elements_ )通过映射到某些属性的对象属性接受值。 这_不是
  • 有些人喜欢对象属性,因为它们可以接受非字符串

因此,至少,如果 React 想要 100% 使用_宇宙中的每一个自定义元素,而不是成为 people_ 的负担,那么:

  • React 需要意识到它不是上帝,除了 React 之外,人们还使用了许多其他库,
  • 因此 React 应该_默认_只通过setAttribute传递数据,因为这是 100% 的标准。
  • React 应该接受自定义元素作者可以在他们的类定义中扩展/覆盖setAttribute方法的事实,使setAttribute接受字符串以外的东西。 一个主要的例子可以是像 A-frame 这样的库。
  • React 应该接受,如果自定义元素作者希望自定义元素与每个可能的库一起使用, _不仅仅是 React_那么作者将依赖setAttribute使他的元素默认与所有内容兼容,如果默认情况下所有的库都依赖于属性,那么整个宇宙将相互协作。 _关于这个没有如果,和,或者但是!_ (除非 w3c/whatwg 做了一些大的改变)

Sooooooooo 话虽如此,我们至少应该

然后,我们可以考虑元素有时具有对象属性 API 的含义:

  • 开发人员可以选择使用setAttribute因为知道它会一直工作。
  • 开发人员有时可以利用对象属性。
  • 开发人员必须始终了解任何给定元素(自定义与否)是否存在对象属性接口。
  • object-property 接口是一种替代接口,不一定将属性与属性进行一对一映射。

因此,有了属性与属性的这些知识,React 中的一个解决方案_希望增加 100% 标准并尊重宇宙法则_应该:

  1. 默认情况下,允许属性 100% 工作。 这意味着<x-foo blah="blah" />应该_默认_映射到setAttribute并沿_unchanged_传递值。 这是一个不间断的变化。 事实上,这是一个修复更改,否则会导致传入无意义的"[object Object]"字符串。
  2. 如果用户意识到存在哪些对象-属性接口并希望明确使用这些接口,那么想出另一种方法让 React 用户_使用 props。

似乎选项 3 使用一个符号(他的额外语法实际上并不难学),是一个最接近理想的解决方案。 基于这个 SO question ,那么唯一可用的符号是= ,尽管解决像& (带有可转义的形式,可能像\& )这样的东西更具可读性。 例如,如果我特别想要一个道具:

<x-foo &blah="blah" />

WHATWG HTML 语法规范涵盖的大多数其他字符应该可以使用,我希望它们可以,但这是另一个主题。

选项 3 是目前​​最好的选项。

如果客户端没有使用水合作用,因此道具在 SSR 中没有意义,那么,哦,好吧。 它以前从来没有工作过,现在也不需要工作。 PHP 风格或 Java 风格的 SSR 发送静态 HTML,没有水合作用,它们 100% 依赖于属性。 也就是说,如果我们使用 React SSR,我们可能会使用客户端 hydration,如果我们不想要 hydration,那么我们应该简单地意识到在这种情况下我们不应该使用 props。 _这很简单。 react 所要做的就是在文档中明确说明这一点。_

但!!! 那不是全部! 我们仍然可以包含选项 5 的功能,让人们有更多的控制权。 使用 Option 5 之类的 API,

  • 有些人可以配置一些属性如何映射到道具
  • 以及一些道具如何映射到属性。 这可以帮助人们使 SSR 工作,即使对于非保湿类型的 SSR!
  • 我们可以让人们定义“如果写了这个属性,实际传递给这个prop,但是如果是SSR,就不要传递给这个prop”,或者“只在SSR的时候传递这个prop给这个属性,否则怎么办我指定使用印记”等

最后,以下似乎是一个可行的解决方案:

  • 选项 3 在幕后默认使用setAttribute
  • 修复了 #10070 以便 React 不会妨碍人们,
  • 和选项 5 中的 API,用于为真正需要它的人进一步调整,包括调整 SSR、客户端或两者的方法。

新年快乐!

我想回应上面的一些观点,但担心这个帖子已经_令人难以置信地_长了。 所以,很抱歉让它更长:P

以下是我们都同意的普遍规则:

使用 setAttribute 设置属性是宇宙中最标准的方式,用于以与声明性 HTML 属性一对一匹配的方式将数据传递给元素。 这必须在 100% 的时间内作为宇宙法则起作用,因此它是唯一 100% 保证将数据传递给元素的方式。

这并不完全正确。 平台中没有任何内容强制自定义元素公开属性接口。 你可以同样轻松地创建一个只接受 JS 属性的。 因此,这不是“传递数据的保证方式”。 缺乏强制机制意味着您不能 100% 确定地依赖任一样式(HTML 属性或 JS 属性)。

有些人不高兴它只是为字符串设计的。
一些元素(我再说一遍,只有一些元素)通过映射到某些属性的对象属性接受值。 这不是可以 100% 依赖的东西。

我们鼓励人们甚至不要为接受对象/数组等丰富数据的属性创建属性。 这是因为某些数据无法序列化回属性字符串。 例如,如果您的对象属性之一是对 DOM 节点的引用,则实际上无法对其进行字符串化。 此外,当您对一个对象进行字符串化和重新解析时,它会失去其身份。 意思是,如果它引用了另一个 POJO,您实际上不能使用该引用,因为您已经创建了一个全新的对象。

有些人喜欢对象属性,因为它们可以接受非字符串

因此,默认情况下 React 应该只通过 setAttribute 传递数据,因为这是 100% 的标准。

JavaScript 属性同样是标准的。 大多数 HTML 元素都公开一个属性和相应的属性接口。 例如, <img src="">HTMLImageElement.src

React 应该接受自定义元素作者可以在他们的类定义中扩展/覆盖 setAttribute 方法的事实,使 setAttribute 接受字符串以外的东西。

作者可以这样做,但实际上这似乎比仅使用 JS 属性更“非标准”。 它还可能使您面临与解析和克隆元素相关的奇怪问题

React 应该接受,如果自定义元素作者希望自定义元素与每个可能的库一起使用,而不仅仅是与 React 一起使用,那么该作者将依赖 setAttribute 使他的元素默认与所有内容兼容,如果默认情况下所有库都依赖于属性,那么整个宇宙将相互合作。 没有关于这个的如果,和,或但是! (除非 w3c/whatwg 做了一些大的改动)

我不确定你是如何得出这个结论的,因为还有其他库更喜欢在自定义元素(例如 Angular)上设置 JS 属性。 为了最大的兼容性,作者应该用 JS 属性来支持他们的属性。 这将覆盖可能用途的最大表面积。 默认情况下,使用 Polymer 创建的所有元素都会执行此操作。

默认情况下,允许属性 100% 工作。 这意味着默认情况下应该映射到 setAttribute 并保持不变地传递值。 这是一个不间断的变化。 事实上,这是一个修复更改,否则会导致传入无意义的“[object Object]”字符串。

我认为 React 实际上 _is_ 传递值不变。 调用setAttribute('foo', {some: object})结果为[object Object] 。 除非你提议他们在对象上调用JSON.stringify() ? 但是那个对象不是“不变的”。 我想也许你依赖作者覆盖了setAttribute() ? 鼓励他们创建相应的 JS 属性而不是猴子修补 DOM 可能更合理。

我认为 React 实际上 _is_ 传递值不变。 调用setAttribute('foo', {some: object})结果为[object Object]

React 在传入setAttribute之前将值强制转换为字符串:

https://github.com/facebook/react/blob/4d6540893809cbecb5d7490a77ec7ad32e2aeeb3/packages/react-dom/src/client/DOMPropertyOperations.js#L136

https://github.com/facebook/react/blob/4d6540893809cbecb5d7490a77ec7ad32e2aeeb3/packages/react-dom/src/client/DOMPropertyOperations.js#L166

我基本上同意你说的一切。

我们同意人们做事是双向的,并没有一个标准强迫每个人都以一种或另一种方式做事,所以我仍然认为

  • 默认使用setAttribute选项 3 和用于指定使用实例属性的标志,
  • 修复了 #10070,以便 React 不会将 args 强制转换为字符串,
  • 和选项 5,用于微调的 API

如果选项 5 做得好,那么 SSR 解决方案的水化可以将数据映射到属性或道具,这可以通过使用选项 5 API 来指定。

React 在传递到 setAttribute 之前将值强制转换为字符串

我知道了。 由于大多数人没有定义自定义toString()它默认为[object Object]

由于大多数人没有定义自定义toString()它默认为[object Object]

就像如果我这样做

const div = document.createElement('div')
div.setAttribute('foo', {a:1, b:2, c:3})

结果是

<div foo="[object Object]"></div>

显然,作为 Web 开发人员,我们应该知道将非字符串传递给元素属性时会发生什么。 例如,我知道我可以将非字符串传递给 A-Frame 元素,而且我应该可以自由地做到这一点,而不会受到库的阻碍。

React 需要意识到它不是上帝,除了 React 之外,人们还使用了许多其他库

这是不必要的尖刻。 您会在此线程中注意到,我们确实关心这一点,但是对于在哪里采用自定义元素设计,有许多不同的选择和愿景。 应该做什么当然不是很明显。

@sebmarkbage抱歉,我根本没有

我的意思是,React 非常流行,所以 React 有可能让人们以某种​​方式做事,而这在其他地方可能行不通(fe 它可以告诉人们依赖所有自定义元素的实例属性,这不会) t 与所有自定义元素一起使用)。

React 当前将传递给元素属性的所有值转换为字符串。 例如,如果 React 没有这样做,就不需要aframe-react (它可以解决字符串问题)甚至存在。

如果 React 能让我们有能力选择如何将数据传递给元素,就像在普通的 JavaScript 中一样,那会让我成为最满意的用户。 😊

再次,对我在那里选择的词感到抱歉。 下次我会仔细考虑的。

我为此向 RFC PR 添加了评论。 我认为值得讨论,因为它涵盖了所提议的内容以及用于可靠地推断自定义元素及其属性的更简单模型。 它将它变成了一种混合方法,但为大多数用例提供了一种零配置的集成方式。

我很想看到这个功能在 React 中实现。 非常感谢您为使 React 变得出色所做的努力。

此 RFC 有任何更新吗?

自定义元素库变得非常好,我很想在我的 React 应用程序中使用它们。 有这方面的消息吗? 考虑到 Vue 和 Angular 在本地轻松处理组件,包装自定义元素并将它们的内容字符串化以便稍后再次解析它们是一个非常不可行的解决方案

关于这个问题的任何更新?

我也很想使用自定义元素库,而不必求助于黑客。 我很想解决这个问题。

@mgolub2 React 团队并不关心网络社区想要什么。 Web Components 现在是一个广泛支持的标准,并且在 2 年后团队没有任何形式的信号来支持这个标准。

大家好,我发起了一个拉取请求,通过更改两行来解决这个问题: https :

这允许自定义元素作者执行以下操作:

class MyElement extends HTMLElement {
  setAttribute(name, value) {
    // default to existing behavior with strings
    if (typeof value === 'string')
      return super.setAttribute(name, value)

    // but now a custom element author can decide what to do with non-string values.
    if (value instanceof SomeCoolObject) { /*...*/ }
  }
}

扩展的setAttribute方法的外观有很多变化,这只是一个小例子。

React 团队,您可能会争辩说自定义元素作者不应该这样做,因为他们在某些情况下会绕过 DOM 的本机属性处理(当值不是字符串时)。 如果您确实有这种意见,那仍然并不意味着您应该阻止自定义元素作者可以使用他们的 __自定义__ 元素做什么。

React 不应该对如何使用现有的 DOM API 发表意见。 React 是一个操作 DOM 的工具,不应该对我们可以对 DOM 做什么而固执己见,而应该只对数据以什么方式流向 DOM 抱有偏见。 我所说的“数据流向 DOM 的方式”是指数据到达那里的路径,没有对数据进行更改(将作者的对象转换为字符串就是在更改作者的数据)。

为什么要对作者的数据进行变异? 为什么不能假设希望操作 DOM 的人知道要传递给 DOM 的数据?

@trusktr我认为这在https://github.com/facebook/react/issues/10070中讨论过

我真的会警告人们不要告诉自定义元素作者覆盖像setAttribute这样的内置方法。 我不认为我们打算修补它。 抄送@gaearon

在这样的子类中覆盖setAttribute是无害的(这不是猴子补丁)。 但正如我所提到的,如果 React 库的工作不一定是这样,那么为什么 React 必须对此做出规定。 我们想使用 React 来操作 DOM(没有障碍)。

如果我必须手动使用el.setAttribute()来提升性能,那只会让开发体验变得更糟。

我不认为 React 团队通过将传递给setAttribute任何内容转换为字符串来使许多人免于一些巨大的危险。

我同意另一种解决方案可能更好。 例如,更新 JSX 规范以使用一些新语法,但这似乎需要很长时间。

如果我们取消自动字符串转换,社区会失去什么? React 团队失去了什么?

React 团队可以稍后改善这种情况,提供更好的解决方案......

为什么不至少给我们一个选项来绕过字符串化? 这是您愿意考虑的事情吗?

我真的会警告人们不要告诉自定义元素作者覆盖像setAttribute这样的内置方法。

你能提供一个很好的理由吗?

这似乎是一个红鲱鱼。 “说服每个编写自定义元素的人将具有特定行为的方法附加到他们的元素”并不是解决这个问题的方法,而是关于如何在 DOM 元素上设置属性。

@trusktr

你能提供一个很好的理由吗?

Web 组件是一个标准。 因此,标准的衍生产品也应该符合标准。

然而,覆盖setAttribute不适合这种情况:它只为 React 创建了一个 hack,而有很多其他框架可以开箱即用地使用 web 组件。 因此,即使他们根本不使用 React,他们也需要使用 React hack。 我认为这不是正确的解决方案。

其次,众所周知,修补标准方法是一种错误的方法。 覆盖setAttribute更改了原始行为,该行为可能会使最终用户在尝试使用它时感到困惑。 每个人都希望标准作为标准工作,自定义元素也不例外,因为它继承了HTMLElement行为。 虽然它可能适用于 React,但它为所有其他用户创造了一个陷阱。 例如,在没有框架的情况下使用 Web 组件时,可能会调用很多setAttribute 。 我怀疑自定义元素开发人员是否会同意采用这种方法。

就个人而言,我认为某种 React 包装器看起来更有前途。

嘿伙计们,在我们等待的同时,我创建了一个用于在 React 中包装您的 Web 组件的 shim https://www.npmjs.com/package/reactify-wc

import React from "react";
import reactifyWc from "reactify-wc";

// Import your web component. This one defines a tag called 'vaadin-button'
import "@vaadin/vaadin-button";

const onClick = () => console.log('hello world');

const VaadinButton = reactifyWc("vaadin-button");

export const MyReactComponent = () => (
  <>
    <h1>Hello world</h1>
    <VaadinButton onClick={onClick}>
      Click me!
    </VaadinButton>
  </>
)

我希望这证明有帮助

(这是我第一次涉足 OSS,也是我办公室外的第一个开源项目之一。非常欢迎建设性的批评😄)

嘿伙计们,在我们等待的同时,我创建了一个用于在 React 中包装您的 Web 组件的 shim https://www.npmjs.com/package/reactify-wc

import React from "react";
import reactifyWc from "reactify-wc";

// Import your web component. This one defines a tag called 'vaadin-button'
import "@vaadin/vaadin-button";

const onClick = () => console.log('hello world');

const VaadinButton = reactifyWc("vaadin-button");

export const MyReactComponent = () => (
  <>
    <h1>Hello world</h1>
    <VaadinButton onClick={onClick}>
      Click me!
    </VaadinButton>
  </>
)

我希望这证明有帮助

(这是我第一次涉足 OSS,也是我办公室外的第一个开源项目之一。非常欢迎建设性的批评😄)

伟大的包装:)

还值得一提的是,使用 Stencil 构建 Web 组件使用 React 修复了这个问题: https: //stenciljs.com/docs/faq#can -data-be-passed-to-web-components-

@matsgm我很高兴用 Stencil 构建对你

只是想了解这个线程的最新情况。 最终的解决方案是什么?

在某种程度上,我是显式定义 attr、prop、event 的忠实粉丝,而不是可能非常令人困惑的魔法启发式方法。

例如snabbdom使用显式的顶级命名空间,这使得很容易知道什么去哪里。 没有执行速度更快的字符串操作,例如从onClick => click修剪后缀仍然是性能命中。

<input attrs={{placeholder: `heyo`}} style={{color: `inherit`}} class={{hello: true, world: false}} on={{click: this.handleClick}} props={{value: `blah`}} />
attr = (attr, val) => elem.setAttribute(attr, val);
prop = (prop, val) => elem[prop] = val;
on  = (event, handler) => elem.addEventListener(event, handler)
style = (prop, val) => elem.style[prop] = val;
class = (name, isSet) => isSet ? elem.classList.add(name) : elem.classList.remove(val)
dataset = (key, val) => elem.dataset[key] = val;

我希望 JSX 支持使用点语法的命名空间。 这意味着 props 将是默认命名空间,我们可以简单地编写

<div tabIndex={-1} attr.title={"abcd"} on.click={handler} style.opacity={1} class.world={true} />`

仅供参考@sebmarkbage ^

const whatever = 'Whatever';
const obj = { a: 1, b: 2 };
const reactComponent = (props) => (
<div>
  ...
  <custom-element attr="{whatever}" someProp={obj} />
  { /* double quotes for attributes */ }
  { /* no quotes for properties */ }
</div>
);

这可以通过修改 React JSX Pragma 解析器来实现。

附加选项是将propeties (或props用于反应粉丝)作为属性传递的指定词

由于今天发布了 17.0,我们是否可以更新此问题以反映何时解决此问题的状态?

是的。 我们最初计划将 React 17 发布为删除更多弃用(并具有新功能)的版本。 然而,旧代码库更新所有代码不太现实,这就是为什么我们决定退出 React 17,它只关注一个重大的重大更改(事件附加到根元素而不是文档)。 这是为了让旧的应用程序可以永远留在 React 17 上,并且只将其中的一部分更新到 18+。

我知道我们一直在说这个特殊功能将进入 17 令人沮丧,但你会注意到我们最初计划在 17 中的几乎所有东西也被移到了 18。 17是一个特殊的垫脚石释放。 这让我们可以在 18 中进行更积极的突破性更改。如果有明确的前进道路,可能会包括这个。

我不确定 WC 社区对这些选项中哪一个更可取的最新共识。 尽管如此,将所有这些都写下来是非常有帮助的( @robdodson做这项工作的重要道具)。 我很好奇人们对这些选项的看法是否在撰写此线程后发生了变化,以及是否有任何新信息可以帮助我们选择方向。

我不认为 WC 社区已经改变了他们的首选选项,仍然是选项 3。 @developit可以更多地谈论 Preact 如何与自定义元素兼容,这可能对 React 也很有趣。 有关框架如何兼容将(复杂)数据传递到自定义元素的一般概述, https://custom-elements-everywhere.com/包含所有详细信息。

请注意,在https://github.com/vuejs/vue/issues/7582 中,Vue 选择使用 sigil,他们选择了“.”。 作为前缀(不是 Glimmer 的“@”)。

https://github.com/vuejs/vue/issues/7582#issuecomment -362943450 中, @trusktr建议最正确的 SSR 实现是不要将 sigil 的属性呈现为 SSR 的 HTML 中的属性,并且而是在水合作用期间通过 JS 将它们设置为属性。

我认为我们不太可能仅仅为了这个特殊功能而引入新的 JSX 语法。

还有一个问题是,如果可能有选项 (5) 的更通用版本,是否值得做选项 (3)。 即选项 (5) 可能是一种低级机制,用于声明具有自定义挂载/更新/卸载/水化行为的自定义低级 React 节点。 甚至不特定于自定义元素本身(尽管它们将是用例之一)。

与其为这个特定的特性引入一个全新的 JSX 语法,不如引入一个更通用的 JSX 并使用它来定义自定义属性呢?

我很久以前就提议将 ECMAScript 的计算属性语法添加到 JSX 中(facebook/jsx#108),并认为这将是对一般语法的有用补充。

如果计算属性语法可用,则可以使用计算属性语法和符号或前缀字符串定义属性。

例如:

import {property} from 'react';
// ...
<custom-img [property('src')]="corgi.jpg" [property('hiResSrc')]="[email protected]" width="100%">

@dantman这个提案如何处理服务器渲染和水化?

我认为 WC 社区中没有人想要选项 5。

这与当前为自定义元素编写自定义 React 包装器打补丁的做法没有什么不同。 这种方法的巨大缺点是它给组件作者带来了特殊情况 React 或组件消费者特殊情况自定义元素的负担,这两者都不是必需的。

您对服务器渲染和水化有何看法? 如果没有明确的配置,哪种启发式是可取的? 在 WC 社区中,这通常是如何完成的? 我重新阅读了这个 RFC,它似乎没有深入研究这个主题的细节。 但这非常重要,尤其是当 React 越来越重视服务器渲染时(因为它经常因过于以客户端为中心的默认设置而受到批评)。

@gaearon我不知道足够的水化和自定义属性来知道需要什么,这主要是一个语法建议。

一般的想法是property(propertyName)可以根据您希望如何实现它,输出一个带前缀的字符串(例如'__REACT_INTERNAL_PROP__$' + propertyName )或创建一个 Symbol 并将“symbol => propertyName”关联保存在一张地图。

如果您需要将该地图传达给客户端,后者可能会出现问题。

然而,据我粗略理解,属性不是你可以在服务器上处理的东西,并且水化涉及客户端代码,所以我不确定它的计划是什么。 至于我的建议,它可能适用于您解决该问题的任何计划。 如果您计划对服务器上的属性做出反应,那么它可以在看到property创建的属性之一时执行此操作。

作为 Web 组件库作者,选项 5 不是一个方便的选择。 我想创建自定义元素,而不必为每个框架和组件显式定义架构。 许多自定义元素作者不会这样做,从而将负担推给 React 开发人员。

没有人会用选项 5 获胜。😕

@claviska你对服务器渲染和水化应该如何使用更隐式的方法有任何意见吗?

@gaearon SSR 将细分为两种情况:自定义元素支持 SSR 的情况和不支持 SSR 的情况。

对于不支持 SSR 的元素,在服务器上设置属性无关紧要。 除了像idclassName这样的内置反射属性,它们可以被删除并且只写在客户端上。

对于支持 SSR 属性的元素很重要,但前提是它们可以触发它们在 DOM 上引起的任何结果。 这需要一个元素实例或某种 SSR 代理/替身,以及一些用于传达 SSR DOM 状态的协议。 这个过程还没有通用的服务器端接口,所以现在这里真的没有什么可做的。 Web 组件作者和库维护者需要弄清楚一些事情,然后 React 才能在那里构建任何桥梁。

在我团队的 SSR 工作中,我们通过修补createElement和使用dangerouslySetInnerHTML与 React 集成。 我认为这就是我们将要达到的集成/实验水平。 在不久的某个时候,我希望我们能够在 SSR 系统内融合一些用于互操作的用户端协议。 在那之前,在没有 _deep_ SSR 的情况下拥有自定义元素属性和事件支持是完全安全和谨慎的。 元素的标签仍然会像今天一样作为 React 组件的子元素进行 SSR。

除了像 id 和 className 这样的内置反射属性,它们可以被删除并且只写在客户端上。

你能帮我理解这是如何工作的吗? 例如说有<github-icon iconname="smiley" /> 。 你的意思是 SSR 应该只在 HTML 响应中包含<github-icon /> ,然后 React 会在 hydration 期间设置domNode.iconname = ...吗? 在这种情况下,我不确定我是否理解如果在 React hydration 发生之前加载自定义元素实现会发生什么。 如果 HTML 中不存在iconnamegithub-icon实现如何知道要呈现哪个图标?

这个过程还没有通用的服务器端接口,所以现在这里真的没有什么可做的。 Web 组件作者和库维护者需要弄清楚一些事情,然后 React 才能在那里构建任何桥梁。

我很好奇是否有什么特别需要让社群在此达成共识的事情。 React 会阻止它吗? 我非常理解这个问题自 2017 年开放以来的挫败感。另一方面,已经三年了,我想你是说这个共识还没有形成。 它发生的先决条件是什么? 这只是更多实验的问题吗?

@gaearon如果在 React hydration 发生之前加载自定义元素实现,它将分配iconname属性的任何 dafault 值。 你所说的if _iconname_ does not exist in the HTML是什么意思? 如果 HTMLElement 类型没有定义属性,它将忽略它。 自定义元素定义加载后,它将扩展 HTMLElement 类型并定义iconname并能够对传入的新值做出反应。

我试图从用户的角度理解这一点。 我们正在服务器上呈现一个页面。 它在文本中间有一个图标。 该图标是作为自定义元素实现的。 最终用户应该体验的事情的顺序是什么?

到目前为止,我的理解是:

  1. 他们会看到初始渲染结果。 它将包含所有正常标记,但他们根本看不到<github-icon>自定义元素。 大概它会是一个洞,就像一个空的 div。 如果我错了(?),请纠正我。 它的实现还没有加载。

  2. <github-icon>自定义元素实现加载并注册。 如果我理解正确,这就是称为“升级”的过程。 然而,即使它的 JS 已经准备好了,它仍然不能显示任何东西,因为我们没有在 HTML 中包含iconname 。 所以我们没有要渲染哪个图标的信息。 这是因为我们之前说过“可以从 HTML 中删除非内置属性”。 所以用户仍然看到一个“洞”。

  3. React 和应用程序代码加载。 发生水合作用。 在水合期间,React 根据启发式设置.iconname =属性。 用户现在可以看到该图标。

对于首先加载自定义元素 JS 实现的情况,这是正确的细分吗? 这是WC社区可取的行为吗?

@gaearon是的,这就是我希望在这种情况下发生的事情。

另一方面,已经三年了,我想你是说这个共识还没有形成

我并不是说没有达成共识。 我是说您现在不需要担心 _deep_ SSR,也不应该让对它非常模糊的担忧阻碍最基本的客户端互操作性,而这些互操作性现在可以完成并且非常有用。

我以前从未见过这种区别(深与浅),所以让我尝试重述它以检查我是否理解您。

通过“深度”SSR,我认为您的意思是 SSR 类似于它在 React 组件中的工作方式。 也就是说,渲染 CE 将产生一个可以在 CE 逻辑加载之前显示的输出。 这就是您所说的我们现在应该担心支持的事情。 这对我来说很有意义。

通过“不深”的 SSR,我认为您是在说我们只是将 CE 标签本身放入 HTML 中。 不用担心它会解决什么问题。 这对我来说也很有意义。 我的问题是关于 _this_ 流程——而不是关于“深度”SSR。

特别是, https: //github.com/facebook/react/issues/11347#issuecomment -713230572 描述了一种情况,即使我们_已经_下载了 CE 逻辑,我们仍然无法向用户显示任何内容,直到水合为止。 直到整个应用程序的 JS 客户端代码加载和水化发生之前,它的属性都是未知的。 我在问这是否确实是所需的行为。

从我的角度来看,我不认为这是一个模糊的问题。 我不想误解功能请求。 因此,获得指定的确切语义将有助于我评估此提案。 例如,一个实际问题是 React 在今天的水化过程中实际上并没有区分生产中的属性/属性,因为没有一个内置组件需要它。 但如果真的需要,我们可以为这个特殊情况添加一些东西。

@gaearon

我认为https://github.com/facebook/react/issues/11347#issuecomment -713230572 可能不是需要此功能的最佳示例。

至少根据我的经验,设置属性而不是属性对于更简单的类型(如字符串、数字或布尔值)来说并不是必需的。

您描述的情况可以很容易地实现,只需直接使用iconname作为属性,并且无需等待水合,它仍然与最终渲染结果大致相同。

自定义元素实现加载并注册。 如果我理解正确,这就是称为“升级”的过程。 然而,即使它的 JS 已经准备好了,它仍然不能显示任何东西,因为我们没有在 HTML 中包含图标名。 所以我们没有要渲染哪个图标的信息。 这是因为我们之前说过“可以从 HTML 中删除非内置属性”。 所以用户仍然看到一个“洞”。

至于您在这里提到的内容,根据组件的实现方式,您可以避免看到“洞”的问题。

这可以通过在这种情况下仅通过简单的设置默认值或通过插槽接受部分甚至大部分内容来实现,以便在组件升级之前显示内容。


我认为这个特性对于使用具有对象、数组、函数等属性的更复杂的组件最有用。

lit-virtualizer虚拟滚动组件为例。

为了使其正常工作,它需要一个items数组和一个renderItem函数,您甚至可以选择设置一个scrollTarget元素,所有这些都只能在 React 中使用 refs 设置马上。

对于这样的情况,您可能无论如何都会使用某种分页或延迟加载来加载内容,因此在 SSR 案例上的水化步骤之前没有任何内容可能不会有那么大的问题。

@gaearon请注意,自定义元素是 DOM 标准,因此从使用自定义元素的每个人都以相同的方式使用它们的意义上说,本身并不存在“WC 社区”。 每个开发人员都以自己的方式集成自定义元素,因为它们本质上是人们构建的低级原语。

也就是说,随着时间的推移,出现了许多模式,所有流行的框架(React 除外)都实现了一些解决方案来为这个 DOM 标准提供兼容性。 正如您在https://custom-elements-everywhere.com/ 上看到的,所有框架/库(React 除外)都选择了不同的选项。 在本期中,所选择的选项被列为选项 1、2 和 3。目前没有实现选项 4 或 5 的框架/库,我认为实现这些是不可取的。

因此,我建议 React 效仿其他框架/库,并选择一个已经证明有效的选项,例如选项 1-3。 我不认为 React 需要通过选择选项 4 或 5 来重新发明轮子,我们没有数据表明它是 React 或那些基于 DOM 标准的长期可维护解决方案。

因此,通过消除过程(带有下面链接的评论),由于选项 3 不会对 JSX 进行,并且选项 4 和 5 已在此线程中被 WC 人员标记为不受欢迎,我们被困在 1 或 2 .

既然 1 似乎有站不住脚的缺点,那么看起来是选项 2吗? 只是去看看 Preact 是怎么做的?


目前没有实现选项 4 或 5 的框架/库,我认为实现这些是不可取的。

@TimvdLippe在 https://github.com/facebook/react/issues/11347#issuecomment-713474037)

我认为我们不太可能仅仅为了这个特殊功能而引入新的 JSX 语法。

@gaearon在 https://github.com/facebook/react/issues/11347#issuecomment-713210204)

这对我来说很有意义,是的。 谢谢总结! 😄

至少根据我的经验,设置属性而不是属性对于更简单的类型(如字符串、数字或布尔值)来说并不是必需的。 您描述的情况可以很容易地实现,只需直接使用 iconname 作为属性,它仍然会在很大程度上与最终渲染结果相同,而无需等待水合。

我不是很关注。 我的假设场景是对https://github.com/facebook/react/issues/11347#issuecomment -713223628 的回应,这听起来像是在建议我们根本不应该在服务器生成的 HTML 中发出属性,除了idclass 。 我不知道“只使用 iconname 作为属性”是什么意思——你是说你想看到服务器生成的 HTML _include_ 其他属性? 这似乎与刚才所说的相矛盾。 我认为如果每个回复都提供完整的图片会很有帮助,否则我们将继续循环下去。 我要问你的问题是:

  1. 在我描述的场景中,服务器生成的 HTML 中到底序列化了什么
    一种。 万一<github-icon iconname="smiley" />
    湾在你提出的情况下,例如<github-icon icon={{ name: "smiley" }} />
  2. 在这两种情况中的任何一种情况下,在水合期间在客户端上调用什么 DOM API

谢谢!

它是否被认为是创建 shim 的一种选择,用自定义实现替换React.createElement() ,其中属性映射在内部?

用法示例:

/** <strong i="8">@jsx</strong> h */
import { h } from 'react-wc-jsx-shim';

function Demo({ items }) {
   return <my-custom-list items={items} />
}

实施草案:

export function h(element, props, children) {
   if(typeof element === 'string' && element.includes('-')) {
      return React.createElement(Wrapper, { props, customElementName: element }, children)
   }
   return React.createElement(element, props, children);

}

function Wrapper({customElementName, props}) {
   const ref = React.useRef();
   React.useEffect(() => {
      for(const prop in props) {
         ref.current[prop] = props[prop];
      }
   });
   return React.createElement(customElementName, { ref, ...props });
}

我知道这不是内置功能,但我认为应该以非常低的开销解锁用户。

@just-boris 基本上就是应用程序或库现在所做的,带有createElement补丁或包装器。 您还需要在 React 中排除特殊大小写的属性名称。 我认为没有导出列表,所以他们只是硬编码了一个拒绝列表,如['children', 'localName', 'ref', 'elementRef', 'style', 'className']

因为它是基于引用的,所以不会在服务器上设置属性。 这就是为什么我称这种担忧为模糊。 在 React 中根本没有设置属性的方法,所以现在它都坏了。 可用的解决方法已经只适用于客户端。 如果内置了一种在客户端而不是服务器上设置属性的方法,它不会突然修复或破坏 SSR。

我实际上会比 SSR 更关心事件。 React 没有办法添加事件处理程序,这是与基本 DOM API 互操作性的一个巨大漏洞。

是否考虑创建一个 shim 的选项,用自定义实现替换 React.createElement(),它在内部进行属性映射?

正如 Rob 在原始问题中所提到的,我过去曾通过https://github.com/skatejs/val对此进行过试验,因此拥有一个包含React.createElement的自定义 JSX pragma 是可行的 - 可能短期 - 解决方案。

我也有 Skate 的WIP 分支实现了所谓的深和浅 SSR,专门用于 React。 到目前为止,从这项工作中学到的东西是,如果你想进行深度 SSR,你不能为所有库使用一个单一的 JSX 包装器,因为每个库都依赖于它们自己的 API 和算法来实现。 (就上下文而言,Skate 的库中有一个核心自定义元素部分,并构建了一些小层来集成市场上每个流行的库,以使消费和使用全面一致。)


我不再在开源中进行太多操作,也不再使用 Twitter,因此请随意采纳我的意见。

选项 1 感觉就像是选项 2 的魔鬼代言人。

选项 2 感觉像是要走的路。 它与 Preact 所做的很接近,因此有先例和社区标准的可能性(我认为无论如何这样做都很棒;JSX 已经证明这可以工作),而且这也是 React 易于升级的务实举措(相对于选项 1)。

选项 3 在纸面上看起来不错,但我认为从逻辑上讲,由于认知开销、记录所述开销以及 SSR 的未知因素,它会比选项 2 困难得多。

如前所述,我最初提出了选项 4,但经过深思熟虑后,我不确定我是否喜欢它了。 您可以将其更改为更多选择加入,您可以在其中指定显式propsevents (而不是attrs ),但即便如此,选项 2 也需要较少的实现细节知识任何人都无需改变即可采用它。

选项 5 感觉我们会完全忽略核心问题。


顺便说一句,我很高兴看到这方面的牵引力,并希望你们一切顺利!

如果在userland有可能的解决方案,是否可以暂时整合并由社区推荐?

如果某个功能已经作为流行且可行的 3rd 方模块存在,那么说服 React 核心团队添加该功能会容易得多。

如果在userland有可能的解决方案,是否可以暂时整合并由社区推荐?

我不相信这是可以从外面[整齐地]修补的东西。 大多数用户空间解决方案都是诸如thisthis 之类的包装器。 在不修改 React 的情况下,我想不出这种方法的合理替代方案,而 wrap 绝对不是一个理想的解决方案。 😕

请注意,在 vuejs/vue#7582 中,Vue 选择使用 sigil,并且他们选择了“.”。 作为前缀(不是 Glimmer 的“@”)。

我没有关于这方面权威声明的链接(也许接近 Vue 的人可以明确确认),但从 Vue 3.0 开始,Vue 似乎已转向选项 2,这意味着它的行为与 Preact 相似。 (有关上下文,请参阅此问题。)

因为它是基于引用的,所以不会在服务器上设置属性。 这就是为什么我称这种担忧为模糊。 在 React 中根本没有设置属性的方法,所以现在它都坏了。 可用的解决方法已经只适用于客户端。 如果内置了一种在客户端而不是服务器上设置属性的方法,它不会突然修复或破坏 SSR。

您能否回答https://github.com/facebook/react/issues/11347#issuecomment -713514984 中的具体问题? 我觉得我们在谈论彼此。 我只想以特定的方式了解整个预期行为。 什么时候设置,什么时候序列化。

作为偶然在这里偶然发现的 Preact+WC 开发人员,我想指出“选项 2”不仅是一个_破坏性的更改_,而且是一个_不完整的解决方案_(Preact 仍然只能与有效 Web 组件的子集进行声明式交互)。

即使作为启发式的当前用户,从长远来看,这听起来也不太理想。


Preact 的启发式(“选项 2”)无法设置在 React(键、子项等)或 Preact(以on开头的任何内容)中具有特殊含义的属性。

即在 JSX 下渲染后:

<web-component key={'key'} ongoing={true} children={[1, 2]} />

属性keyongoingchildren都将是undefined (或任何默认值是)上的创建的实例web-component

此外,与 OP 相反,此选项也是一个重大更改。 大多数 Web 组件(例如用lit-element )提供通过属性传递序列化丰富数据的能力,以便与纯 HTML 一起使用。 这样的组件可以像这样从 React 渲染

<web-component richdata={JSON.stringify(something)} />

如果选择“选项 2”,它将中断。 不确定在 React 中这样使用 Web 组件有多普遍,但肯定有一些代码库这样做,它的效率低于 refs,但也更简洁。

最后,Web Component 的作者可以自由地将细微不同的语义附加到同名的属性和属性上。 一个例子是模仿原生input元素行为的 Web 组件 - 将属性value视为初始值,并且只观察属性value 。 此类组件无法通过 jsx 与启发式方法完全交互,如果引入,也可能会遇到微妙的破坏。

我认为我们不太可能仅仅为了这个特殊功能而引入新的 JSX 语法。

使用命名空间怎么样? https://github.com/facebook/jsx/issues/66建议为keyref添加一个react命名空间。 同样,像react-dom这样的东西会是一个可以用来识别 DOM 属性的命名空间吗? 使用 OP 的选项 3 示例,这将是:

<custom-img react-dom:src="corgi.jpg" react-dom:hiResSrc="[email protected]" width="100%">

这不是最符合人体工程学的。 只是dom会更好,但不确定 React 是否想要创建不以react开头的命名空间。 此外,如果认为设置为属性应该是不太常见的情况,那么糟糕的人体工程学并不是世界末日。 使用属性会更好,因为这提供了与 SSR 相同的属性。 设置为属性是为了当属性有问题时(例如传递一个对象或没有属性支持的属性),而正是对于那些有问题的属性,我们无论如何都无法将它们 SSR 到属性,并且需要将它们水合通过JS。

_不完整的解决方案_

(Preact 仍然只能与有效 Web 组件的子集进行声明式交互)

@brainlessbadger您还可以编辑上面的评论以包含该子集的实际内容吗? 例如。 哪些 Web 组件/自定义元素受到影响(带有示例)? 他们究竟是如何受到影响的?

@karlhorky我添加了一个解释。 抱歉,不必要地含糊其辞。

关于 Web 组件事件。 关于如何通过碰撞事件名称来避免未来阻塞是否有任何共识?

这对于属性和属性也是一个问题,因为可以像所有这些一样将新属性添加到 HTMLElement: https :

我认为严格来说,您应该在任何自定义属性或自定义事件的名称中使用-

其他任何事情都是权衡该组件阻止未来规范使用好名字的风险的妥协。

事件让我更加紧张,因为这不仅仅是该组件具有不兼容行为的风险。 如果新事件冒泡,它将触发该树路径中的所有现有 Web 组件。

如果自定义属性和事件需要在其名称中使用- ,可以用作启发式来确定要使用的内容。 然后我们会对内置的进行特殊处理。 这就是我们首先知道它是否是自定义元素的方式。

然后可以将属性用作便利以提供更好的简写。 至少那些可以隐藏内置属性并且不会与未来的属性发生冲突。

我认为严格来说你应该在任何自定义属性或自定义事件的名称中使用 - 。

自定义元素规范不包含此类限制,因此在 React 中强制执行它们将显着影响互操作性。

不应期望自定义元素作者订阅框架的任意要求。 想象一下每个框架都有不同的意见和约定。 这违背了使用 Web 组件的目的。 😕

理想情况下,框架应该适应适应标准。

的确。 setAttribute()dispatchEvent()只是非 Web 组件特定的 API,它们从一开始就允许任意名称。 任何元素都可以具有任何属性并触发任何事件——这只是 DOM 的基本事实。

不过,添加data-命名空间是有原因的。 您在技术上可以做什么与最佳实践之间存在差异。 您也可以在技术上修补原型,但随后您会遇到包含/包含情况。

我不建议仅仅 React 添加这个,而是社区转向这个约定来帮助保护未来的命名空间。 我只是觉得这个问题没有被认真对待是很不幸的。 但也许网络注定只会为新 API 添加尴尬的名称。

我喜欢从 SSR 中排除非内置函数的想法,并且更喜欢在水合期间使用属性,因为使用声明性 shadow DOM 可以在内部扩展真实内容。

具体来说,虽然我认为这会导致人们使用 SSR + AMP 的当前方法出现问题,因为它使用属性,并且您可以假设 AMP 运行时已经加载。

假设选项 3 的门没有关闭,它比选项 2 有显着的优势,上面没有列出——我发现选项 2 的问题是,如果 web 组件库是异步加载的,preact 可能不会知道,对于未知元素,“属性”将是一个属性。 由于它没有找到未知元素中存在的属性,所以它默认为一个属性。 解决方法是在加载依赖的 Web 组件库之前不渲染该组件,这不是很优雅(或理想的性能方面)。 由于我的小组使用 asp.net,而不是 node.js,我从未真正探索过 SSR,所以下面的观察是推测性的。

我认为,就 React 团队而言,这是一个很好的姿态,建议超越其他框架支持的范围(据我所知),就 SSR 而言,允许在初始渲染期间传递对象属性,这可以服务于用户更好。

我不知道如果我说明明显与否,但我觉得一点与web组件共识,对于某些属性,是很方便的,有时设置属性的初始值通过JSON字符串,用单引号包裹。

不过,AMP 使用另一种约定——它使用script type=application.json来执行此操作,这是一种合理(但冗长)的替代方案。

但坚持使用属性方法:

然后 Web 组件库可以解析该属性,或通过属性传入值。

因此,为了避免水合延迟,设置:

<github-icon icon='{"name":"smiley"}'></github-icon>

在 SSR 期间。 然后 github-icon 可以立即做它的事情,在内部做一个 JSON.parse 属性,而不必等待 React 传入(更完整?)对象值。

所以现在我们有一个棘手的情况——如果在服务器上渲染,我们希望“图标”被视为一个属性,但能够将值作为一个对象传入,理想情况下,SSR 机制会将其字符串化到属性中。 在客户端,我们希望能够直接传入对象,而不是经过字符串化和解析的开销。

当然,某些属性是不适合字符串化/解析的对象,因此这不适用,并且这些属性需要等待水合完成。

如果所有属性和属性都遵循 React 团队的首选命名约定(也许)——所有属性都有破折号,并且所有属性都是使用驼峰命名的复合名称,也许破折号的存在有助于区分属性和属性:

<github-icon my-icon={myStringifiedProperty} myIcon={myObjectProperty}></github-icon>

问题在于上面的语法中没有任何内容指示在服务器上与客户端上做什么,理想情况下我们可以为两者使用一个绑定,而 React 将足够聪明,可以确定使用哪个。

React 可以假设,如果属性/属性键是驼峰式大小写,则始终将其作为对象在客户端传递,并且如果在 SSR 期间设置了对象的 JSON 字符串化序列化。 同样,键中的破折号可以(安全地,我认为)假定它是一个属性,只需传入值的 .toString() 即可。 但我认为这假设太多了。 并且不支持单个单词属性和属性,如果这些属性应用于扩展 HTMLElement 的 web 组件,则被认为是有效的 HTML,这将太受限制。 我赞成 W3C 发布一个未来可能使用的“保留”属性名称列表,类似于 JS 的保留关键字,并且框架会在开发人员不正确使用保留属性名称时找到警告他们的方法。

但我认为选项 3 是最好的方法。 但是,如果可以按照@gaearon 的建议对其进行增强,以获得更好的用户体验,那就太好了。

我的建议是:

<github-icon icon={myDynamicIcon}/>

表示属性(toString())。

<github-icon icon:={myDynamicIcon}/>

将意味着 - 在 SSR 期间,忽略,但将客户端绑定到对象属性(在水合之后)。

现在 React 团队有兴趣解决的场景(一些?)呢? 我的第一个想法是其他一些符号,例如两个冒号:

<github-icon icon::={myDynamicIcon}/> //Not my final suggestion!

这意味着,在 SSR 期间,JSON.stringify 属性,在呈现的 HTML 中设置为一个属性,并在它绑定到的属性在水合后发生变化时作为对象传递给客户端。

这留下了如何处理复合名称的棘手情况。 即如果我们设置:

<github-icon iconProps::={myDynamicIconProp}/>  //Not my final suggestion!

过去没有争议,属性名iconProps对应的属性应该是icon-props。

也许这变得更具争议性,因为其中一些 Web 组件可以在不进行任何更改的情况下内置到平台中,其中属性不能有破折号(但属性可以是驼峰式的)。 据我所知,目前还没有允许通过 JSON 反序列化传入复杂对象的本机元素,但如果将来有需要,我不会感到惊讶。 那么 React 如何知道何时插入破折号?

我能想出的唯一(丑陋的?)建议是:

<github-icon icon-props:iconProps={myDynamicIconProp}/>

这意味着,在服务器上,序列化后使用属性 icon-props,在客户端使用属性 iconProps,直接传入对象。

允许混合属性/属性对的更详细符号的另一个(远射?)潜在好处是:重复设置本机元素的属性可能会快一点,而不是相应的属性,特别是对于不是字符串的属性。 如果是这样,React 当前是否使用服务器上的属性和客户端上的属性? 如果不是,是因为同样的命名翻译困难问题,这个表示法可以解决什么问题?

@bahrus我认为可以简化

<my-element attr='using-quotes' property={thisIsAnObject} />

我认为最好用一个具体的例子来说明这个问题。

HTML Form元素有许多属性。 具有“复合名称”的那些似乎并不都遵循一致的命名模式——通常它坚持全部小写,没有分隔符,但有时会有一个破折号分隔符——例如“接受字符集”,有时不是—— - “无验证”。

相应的 JS 属性名称也不适合一个很好的通用模式——acceptCharset、noValidate。 noValidate 属性/ novalidate 属性是一个布尔值,因此在客户端上,执行 myForm.setAttribute('novalidate', '') 而不是 myForm.noValidate = true 会很浪费(我想)。

但是,在服务器上,设置 myForm.noValidate = true 没有意义,因为我们要发送 DOM 的字符串表示,因此我们需要使用该属性:

<form novalidate={shouldNotValidate}>

实际上,我不清楚 React/JSX 是否有条件设置布尔属性的通用方法,可能依赖于固定的静态查找表,这似乎(?)过于僵化。 如果我这么大胆,这似乎是 React/JSX 可以改进的另一个领域,作为本 RFC 的一部分,与 DOM 的(凌乱)工作方式完全兼容?

我们如何表示所有这些场景,以便开发人员在不牺牲性能的情况下完全控制服务器(通过属性)和客户端(通过属性)? 在像 novalidate 这样的布尔值的情况下,我建议:

<form novalidate:noValidate?={shouldNotValidate}/>

如果 React 完全支持 DOM,尽管它很混乱,以尽可能高性能的方式,我认为这将大大支持自定义元素,这些元素在命名模式上也可能不太一致。

在我看来,通过服务器上的 JSON.stringify 添加对属性的可选序列化对象属性的支持,对于 React 和 Web 组件来说将是一个额外的巨大双赢。

或者我错过了什么? ( @eavichay ,您建议如何最好地表示这些场景,而不是遵循您的示例)。

此页面是否有帮助?
0 / 5 - 0 等级