【问题标题】:How can I replace Markdown-rendered HTML tags with React components?如何用 React 组件替换 Markdown 渲染的 HTML 标签?
【发布时间】:2018-06-16 20:29:43
【问题描述】:

目前,我正在使用 marked 将 Markdown 转换为 HTML 代码,然后将其中的某些部分替换为 React 元素。这会产生一个 HTML 字符串和 React 元素的数组,确实可以呈现:

const prepareGuide = markdown => replaceToArray(
  marked(markdown),
  /<a href="SOME_SPECIAL_HREF".*?>(.*?)<\/a>/,
  (match, label, slug) => <a href={`/${slug}`}>{label}</a>
)

const Guide = ({ guide }) =>
  prepareGuide(guide.fields.text).map(
    n => typeof n === 'object'
      ? n
      : <span dangerouslySetInnerHTML={{ __html: n }} />
  )

这个问题,我们称之为解决方法,是每段 HTML 都需要一个包装器元素,例如 span(并使用 dangerouslySetInnerHTML)。

我基本上需要的是能够用 React 组件替换呈现的 HTML 元素,以添加 React 功能,如路由器链接和其他自定义元素。

还有其他方法吗?

编辑:我使用的replaceToArray 函数类似于String.prototype.replace,但返回一个数组(因此可以返回任何类型)

编辑:我的另一种方法是将 HTML 直接渲染到 DOM(使用 dangerouslySetInnerHTML),并使用容器元素的 ref 来查询我想要替换的所有元素。但是,下一个问题:要在我拥有的 HTML ref 中呈现 React 组件,我需要另一个 React 根,这是可能的,但不切实际,因为我会丢失所有上下文(如路由器),所以我什至不能以这种方式正确使用路由器链接。

【问题讨论】:

标签: reactjs markdown


【解决方案1】:

我能够通过以下方式解决这个问题:

我一直使用markeddangerouslySetInnerHTML 直接设置HTML。现在,如第二种方法中所述,我使用ref 来查询我想要替换的元素。现在为了能够将 React 元素渲染到 HTML,我只使用了 ReactDOM.render 函数。

最大的问题是组件无法访问应用程序的上下文,因为我现在有多个 React 根。为了解决这个问题,我发现我们可以将上下文从一个组件复制到另一个组件:Is it possible to pass context into a component instantiated with ReactDOM.render?

所以为了能够访问渲染 HTML 的组件中的上下文,我们需要为我们需要复制的上下文设置组件的 contextTypes

class Article extends Component {
  static contextTypes = {
    router: PropTypes.any
  }

  static propTypes = {
    markdown: PropTypes.string
  }

  prepare(ref) {
    const Provider = createContextProvider(this.context)
    const links = Array.from(ref.querySelectorAll('a'))

    links.forEach((link) => {
      const span = document.createElement('span')
      const { pathname } = url.parse(link.href)
      const text = link.innerText

      link.parentNode.replaceChild(span, link)
      ReactDOM.render(
        <Provider>
          <Link to={pathname}>{text}</Link>
        </Provider>,
        span
      )
    })
  }

  render() {
    return (
      <article
        ref={this.prepare}
        dangerouslySetInnerHTML={{ __html: marked(this.props.markdown) }}
      />
    )
  }
}

上面的代码需要我从上面链接的问题中复制的片段。我称为 prepare 的方法用 React 根替换特定的 HTML 节点。

function createContextProvider(context) {
  class ContextProvider extends React.Component {
    getChildContext() {
      return context
    }

    render = () => this.props.children

    static propTypes = { children: PropTypes.node }
  }

  ContextProvider.childContextTypes = {}
  Object.keys(context).forEach(key => {
    ContextProvider.childContextTypes[key] = PropTypes.any.isRequired
  })

  return ContextProvider
}

所以我们基本上有一个创建Provider 组件的函数。该函数需要能够动态适应所需的上下文类型,这就是循环将它们设置为必需的原因。

【讨论】:

    【解决方案2】:

    如果你只是想让 links 与 React Router 一起工作,你可以像往常一样使用 dangerouslySetInnerHTML 渲染 markdown,然后 intercept internal link clicks 让它们通过 react-路由器。

    外部加载 .md 的完整示例,然后使用 react-router 捕获要处理的链接:

    import React from "react"
    import { withRouter } from 'react-router'
    
    import catchLinks from 'catch-links'
    import marked from "marked"
    
    import styles from './styles.scss'
    
    class ContentPage extends React.Component {
    
        constructor(props) {
            super(props);
            this.state = {
                loading: false,
                markdown: '',
                title: ''
            }
        }
    
        componentDidMount() {
    
            if (typeof window !== 'undefined') {
                catchLinks(window, (href) => {
                    this.props.history.push(href);
                });
            }
    
            const page = location.pathname.replace('/', '');
    
            fetch(`/assets/content/${page}.md`)
            .then(response => response.text())
            .then(text => {
                this.setState({ markdown: marked(text, {}) })
            });
    
            const title = page_titles[page] || capitalize(page);
    
            if (title) {
                document.title = title;
                this.setState({title})
            }
    
        }
    
        render() {
    
            const {
                markdown,
                title
            } = this.state;
    
            return (
                <div class={styles.contentPage}>
    
                    <div class={styles.pageTop}>
                        {title}
                    </div>
    
                    <div class={styles.page}>
                        <div class={styles.content} dangerouslySetInnerHTML={{__html: markdown}}></div>
                    </div>
    
                </div>
            );
        }
    }
    
    export default withRouter(ContentPage);
    

    【讨论】:

      猜你喜欢
      • 2016-08-07
      • 1970-01-01
      • 2018-12-01
      • 2018-05-07
      • 2021-03-20
      • 2017-08-08
      • 2019-05-17
      • 1970-01-01
      • 2017-04-17
      相关资源
      最近更新 更多