用React中的Gallery替换img标签

时间:2018-02-09 14:43:34

标签: javascript html reactjs

渲染库组件存在问题: 我从服务器

获取带有html的字符串
let serverResponse = `
    <h3>Some title</h3>
    <p>Some text</p>
    <p>
       <img src="">
       <img src="">
       <img src="">
       <br>
    </p>
    ...
   `

现在我使用dangerouslySetInnerHTML

呈现此回复
   <div dangerouslySetInnerHTML={{ __html: serverResponse }} />

但是当我得到2个或更多重复的<img>标签时,我想用组件替换它们。 我怎样才能做到这一点?我尝试使用Regex执行此操作并将其替换为<Gallery/>,但它不起作用。我认为我需要在标签数组中拆分字符串,然后用<Gallery/>组件替换图像。 我试过renderToString

  ...
  getGallary = images => {
     // Loop throw images and get sources
     let sources = [];
     if (images) {
        images.map(img => {
           let separatedImages = img.match(/<img (.*?)>/g);
           separatedImages.map(item => sources.push(...item.match(/(https?:\/\/.*\.(?:png|jpg))/)));
        });
     }

     if (sources.length) {
         return <Gallery items={sources}>
     }

     return <div/>
 }; 

  ...
  <div dangerouslySetInnerHTML={{__html: serverResponse.replace(/(<img (.*?)>){2,}/g,
                                         renderToString(this.getGallary(serverResponse.match(/(<img (.*?)>){2,}/g))))}}/>}

这不起作用,因为我只得到没有逻辑的HTML :(

2 个答案:

答案 0 :(得分:1)

所有的拳头,dangerouslySetInnerHTML不是要走的路,你不能在其中插入图库并让它由React处理。你需要做的是多步骤程序。

<强> 1。将HTML解析为文档。在此阶段,您将字符串转换为有效的DOM文档。使用DOMParser非常容易:

function getDOM (html) {
  const parser = new DOMParser()
  const doc = parser.parseFromString(`<div class="container">${html}</div>`, 'text/html')
  return doc.querySelector('.container')
}

我使用这个帮助函数返回带有HTML节点的容器。下一步将需要它。

<强> 2。将DOM文档转换为React JSX树。现在您已经拥有了DOM树,通过从相应的DOM节点创建单独的React元素,可以很容易地将其转换为JSX。此函数需要递归才能处理DOM树的所有级别。这样的事情会做:

function getJSX(root) {
  return [...root.children].map(element => {
    const children = element.children.length ? getJSX(element) : element.textContent
    const props = [...element.attributes].reduce((prev, curr) => ({
      ...prev,
      [curr.name]: curr.value
    }), {})

    return React.createElement(element.tagName, props, children)
  })
}

这足以从DOM创建JSX。它可以像这样使用:

const JSX = getJSX(getDOM(htmlString))

第3。注入图库。现在,如果element包含多于1个图像标记,则可以改进JSX创建以将Gallery注入已创建的JSX。我会将注入函数传递给getJSX作为第二个参数。与上述版本的唯一区别在于如何在图库中计算children

if (element.querySelector('img + img') && injectGallery) {
  const imageSources = [...element.querySelectorAll('img')].map(img => img.src)
  children = injectGallery(imageSources)
} else {
  children = element.children.length ? getJSX(element) : element.textContent
}

<强> 4。创建图库组件。现在是创建图库组件的时候了。该组件将如下所示:

import React from 'react'
import { func, string } from 'prop-types'

function getDOM (html) {
  const parser = new DOMParser()
  const doc = parser.parseFromString(`<div class="container">${html}</div>`, 'text/html')
  return doc.querySelector('.container')
}

function getJSX(root, injectGallery) {
  return [...root.children].map(element => {
    let children

    if (element.querySelector('img + img') && injectGallery) {
      const imageSources = [...element.querySelectorAll('img')].map(img => img.src)
      children = injectGallery(imageSources)
    } else {
      children = element.children.length ? getJSX(element) : element.textContent
    }

    const props = [...element.attributes].reduce((prev, curr) => ({
      ...prev,
      [curr.name]: curr.value
    }), {})

    return React.createElement(element.tagName, props, children)
  })
}

const HTMLContent = ({ content, injectGallery }) => getJSX(getDOM(content), injectGallery)

HTMLContent.propTypes = {
  content: string.isRequired,
  injectGallery: func.isRequired,
}

export default HTMLContent

<强> 5。使用它!以下是您将如何一起使用的方法:

<HTMLContent
  content={serverResponse}
  injectGallery={(images) => (
    <Gallery images={images} />
  )}
/>

以上是此代码的演示。

演示: https://codesandbox.io/s/2w436j98n

答案 1 :(得分:0)

TLDR:您可以使用React HTML Parser或类似的库。

虽然它看起来非常相似,但JSX被解析为一堆React.createElement,因此将插入的React组件转换为HTML字符串将无法正常工作。 renderToString也不会这样做,因为它用于服务器端渲染React页面,并且在您的情况下不起作用。

要用React组件替换HTML标记,您需要一个解析器将HTMl字符串解析为节点,将节点映射到React元素并渲染它们。幸运的是,有一些库可以做到这一点,比如React HTML Parser