如何使用 React 渲染 <template /> 标签?

How can I render a <template /> tag with react?

有时您可能需要从您的 React 应用程序呈现 Web 组件。

Web 组件通常使用特殊的 <template> ... </template> 标记。 但是如果我尝试用这样的反应来呈现这样的标记:

render() {
  return (
    <template>
      <div>some content</div>
    </template>
  )
}

然后我的网络组件无法正常工作。

原因是 JSX 的作用与 <template /> 标签的作用不同。模板标签的想法是 不呈现它的 children 并且几乎像处理未解析的文本一样处理它(浏览器实际上解析它只是为了确保它有效 html, 但仅此而已)

但是当你在 JSX 中写这个时:

return (
  <template>
    <div>some content</div>
  </template>
)

你基本上是在指示 react 创建一个 'template' 元素,然后 创建一个 'div' 元素 然后附加这个 div template 作为 child.

所以在幕后发生了这种情况:

const template = document.createElement('template')
const div = document.createElement('div')
const text = document.createTextNode('some text')
div.appendChild(text)
template.appendChild(div)

但是你想要的是将<template />的内容设置为一个字符串。您可以为此使用 innerHTML


解决方案

一个解决方案是:

render() {
  return (
    <template
      dangerouslySetInnerHTML={{
        __html: '<div>some content</div>'
      }}
    />
  )
}

现在你要求 React 创建所有这些 children 标签作为节点元素,但让浏览器决定如何处理它们。

更好的解决方案

您可能不想一直使用 dangerouslySetInnerHTML。那么让我们创建一个辅助组件:

function Template({ children, ...attrs }) {
  return (
    <template
      {...attrs}
      dangerouslySetInnerHTML={{ __html: children }}
    />
  );
}

现在,任何时候您需要使用模板时,您都可以像这样使用它:

render() {
  return (
    <Template>
      {'<div>some content</div>'}
    </Template>
  )
}

不要忘记将内部内容放在引号中,因为它应该是一个字符串。

我知道这个问题已经有了答案,但我想还有一个更简单的解决方案可以创建 hoc(高阶组件)。

只需像这样创建新的“组件”:

// hoc/Template.js
const template = props => props.children
export default template

然后你可以这样在你的项目中使用它:

import './hoc/Template.js'

...

render() {
  return (
    <Template>
      {'<div>some content</div>'}
    </Template>
  )
}

较新版本的react已经构建了这样的组件,所以你不用创建组件也能达到同样的目的。

import { Fragment } from 'react'

...

render() {
  return (
    <Fragment>
      {'<div>some content</div>'}
    </Fragment>
  )
}

这实际上是 React 中的一个错误! https://github.com/facebook/react/issues/19932

当本机浏览器解析 <template> 标签时,它会将所有 children 附加到模板的 content 片段,而不是将它们添加为 children。所以一个模板实际上不应该有任何 children.

但是,如果您以编程方式构建 DOM(react-dom 的方式),则模板将具有 empty content片段,因为所有 children 都添加为 children。这就是 Web 组件无法与这些模板一起正常运行的原因。

这里也有一个问题示例:https://codesandbox.io/s/new-sun-8l62o?file=/src/App.tsx:1056-1118

最简单的解决方法是使用 dangerouslySetInnerHTML,如下所示:

<template dangerouslySetInnerHTML={{ __html: `
  <p> Some text </p>
` }} />

这受限于您只能提供原始 HTML(无自定义 React 元素)这一事实。但是,由于您首先使用的是 <template> 标签,因此您似乎不太可能在模板中使用 React。