用React方式在HTML中包装多个字符串

Dan*_*anV 6 javascript jsx reactjs

我正在构建一个实体荧光笔,因此我可以上传文本文件,查看屏幕上的内容,然后突出显示数组中的单词.这是用户在手动突出显示选择时填充的数组,例如..

const entities = ['John Smith', 'Apple', 'some other word'];

This is my text document that is displayed on the screen. It contains a lot of text, and some of this text needs to be visually highlighted to the user once they manually highlight some text, like the name John Smith, Apple and some other word

现在,我希望通过将其包装在某个标记中来直观地突出显示文本中实体的所有实例,并且执行类似这样的操作非常有效:

getFormattedText() {
    const paragraphs = this.props.text.split(/\n/);
    const { entities } = this.props;

    return paragraphs.map((p) => {
        let entityWrapped = p;

        entities.forEach((text) => {
        const re = new RegExp(`${text}`, 'g');
        entityWrapped =
            entityWrapped.replace(re, `<em>${text}</em>`);
        });

        return `<p>${entityWrapped}</p>`;
    }).toString().replace(/<\/p>,/g, '</p>');
}
Run Code Online (Sandbox Code Playgroud)

...但是(!),这只是给了我一个大字符串所以我必须危险地设置内部HTML,因此我不能在任何这些突出显示的实体上附加onClick事件'React方式',这是我需要做的事情.

这样做的React方法是返回一个如下所示的数组:

['This is my text document that is displayed on the screen. It contains a lot of text, and some of this text needs to be visually highlighted to the user, like the name', {}, {}, {}]{}包含JSX内容的React对象在哪里.

我已经用一些嵌套的循环对此进行了攻击,但是这很麻烦,因为地狱,难以阅读,并且随着我逐渐添加更多实体,性能受到了巨大的打击.

所以,我的问题是......解决这个问题的最佳方法是什么?确保代码简单易读,并且我们不会遇到巨大的性能问题,因为我可能会处理很长的文档.这是我放弃我的React道德和危险的SetInnerHTML以及直接绑定到DOM的事件的时间吗?

更新

@ AndriciCezar下面的答案完美地将格式化的字符串和对象数组准备好用于渲染,但是一旦实体数组很大(> 100)并且文本体也很大(> 100kb),它就不是很有效.我们正在寻找大约10倍的时间来渲染这个数组V是一个字符串.

有没有人知道更好的方法来提供渲染大字符串的速度,但能够在元素上附加React事件的灵活性?或者危险地SetInnerHTML是这种情况下的最佳解决方案?

And*_*zar 5

你尝试过这样的事吗?

复杂性是段落数量*关键字数量.对于22,273个单词(121,104个字符)和3个关键字的段落,我的PC上生成数组需要44ms.

!更新:我认为这是突出关键字最清晰,最有效的方法.我用James Brierley的答案来优化它.

我使用500个关键字对320kb的数据进行了测试,加载速度非常慢.另一个想法是使段落渐进.渲染前10个段落,然后在滚动或一段时间后渲染其余段落.

和你的例子JS小提琴:https://jsfiddle.net/69z2wepo/79047/

const Term = ({ children }) => (
  <em style={{backgroundColor: "red"}} onClick={() => alert(children)}>
    {children}
  </em>
);

const Paragraph = ({ paragraph, keywords }) => {
  let keyCount = 0;
  console.time("Measure paragraph");

  let myregex = keywords.join('\\b|\\b');
  let splits = paragraph.split(new RegExp(`\\b${myregex}\\b`, 'ig'));
  let matches = paragraph.match(new RegExp(`\\b${myregex}\\b`, 'ig'));
  let result = [];

  for (let i = 0; i < splits.length; ++i) {
    result.push(splits[i]);
    if (i < splits.length - 1)
      result.push(<Term key={++keyCount}>{matches[i]}</Term>);
  }

  console.timeEnd("Measure paragraph");

  return (
    <p>{result}</p>
  );
};


const FormattedText = ({ paragraphs, keywords }) => {
    console.time("Measure");

    const result = paragraphs.map((paragraph, index) =>
      <Paragraph key={index} paragraph={paragraph} keywords={keywords} /> );

    console.timeEnd("Measure");
    return (
      <div>
        {result}
      </div>
    );
};

const paragraphs = ["Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ornare tellus scelerisque nunc feugiat, sed posuere enim congue. Vestibulum efficitur, erat sit amet aliquam lacinia, urna lorem vehicula lectus, sit amet ullamcorper ex metus vitae mi. Sed ullamcorper varius congue. Morbi sollicitudin est magna. Pellentesque sodales interdum convallis. Vivamus urna lectus, porta eget elit in, laoreet feugiat augue. Quisque dignissim sed sapien quis sollicitudin. Curabitur vehicula, ex eu tincidunt condimentum, sapien elit consequat enim, at suscipit massa velit quis nibh. Suspendisse et ipsum in sem fermentum gravida. Nulla facilisi. Vestibulum nisl augue, efficitur sit amet dapibus nec, convallis nec velit. Nunc accumsan odio eu elit pretium, quis consectetur lacus varius"];
const keywords = ["Lorem Ipsum"];

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      limitParagraphs: 10
    };
  }

  componentDidMount() {
    setTimeout(
      () =>
        this.setState({
          limitParagraphs: 200
        }),
      1000
    );
  }

  render() {
    return (
      <FormattedText paragraphs={paragraphs.slice(0, this.state.limitParagraphs)} keywords={keywords} />
    );
  }
}

ReactDOM.render(
  <App />, 
  document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root">
</div>
Run Code Online (Sandbox Code Playgroud)


Jam*_*ley 5

这是一个使用正则表达式拆分每个关键字上的字符串的解决方案。如果您不需要它不区分大小写或突出显示多个单词的关键字,则可以简化此操作。

import React from 'react';

const input = 'This is a test. And this is another test.';
const keywords = ['this', 'another test'];

export default class Highlighter extends React.PureComponent {
    highlight(input, regexes) {
        if (!regexes.length) {
            return input;
        }
        let split = input.split(regexes[0]);
        // Only needed if matches are case insensitive and we need to preserve the
        // case of the original match
        let replacements = input.match(regexes[0]);
        let result = [];
        for (let i = 0; i < split.length - 1; i++) {
            result.push(this.highlight(split[i], regexes.slice(1)));
            result.push(<em>{replacements[i]}</em>);
        }
        result.push(this.highlight(split[split.length - 1], regexes.slice(1)));
        return result;
    }
    render() {
        let regexes = keywords.map(word => new RegExp(`\\b${word}\\b`, 'ig'));
        return (
            <div>
                { this.highlight(input, regexes) }
            </div>);
    }
}
Run Code Online (Sandbox Code Playgroud)