【问题标题】:Highlight text using ReactJS使用 ReactJS 突出显示文本
【发布时间】:2015-04-15 14:22:22
【问题描述】:

我正在尝试突出显示与查询匹配的文本,但我不知道如何让标签显示为 HTML 而不是文本。

var Component = React.createClass({
    _highlightQuery: function(name, query) {
        var regex = new RegExp("(" + query + ")", "gi");
        return name.replace(regex, "<strong>$1</strong>");
    },
    render: function() {
        var name = "Javascript";
        var query = "java"
        return (
            <div>
                <input type="checkbox" /> {this._highlightQuery(name, query)}
            </div>
        );
    }
});

当前输出:Java脚本

所需输出:Java脚本

【问题讨论】:

    标签: javascript reactjs react-jsx


    【解决方案1】:

    这是我简单的 twoliner 辅助方法:

    getHighlightedText(text, highlight) {
        // Split text on highlight term, include term itself into parts, ignore case
        const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
        return <span>{parts.map(part => part.toLowerCase() === highlight.toLowerCase() ? <b>{part}</b> : part)}</span>;
    }
    

    它返回一个跨度,其中请求的部分用&lt;b&gt; &lt;/b&gt; 标签突出显示。如果需要,可以简单地修改为使用另一个标签。

    更新:为避免唯一键丢失警告,这里是基于跨度和设置匹配部分的 fontWeight 样式的解决方案:

    getHighlightedText(text, highlight) {
        // Split on highlight term and include term into parts, ignore case
        const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
        return <span> { parts.map((part, i) => 
            <span key={i} style={part.toLowerCase() === highlight.toLowerCase() ? { fontWeight: 'bold' } : {} }>
                { part }
            </span>)
        } </span>;
    }
    

    【讨论】:

    • 本教程也可能对某人有所帮助:vladopandzic.com/react/creating-react-highlighter-component
    • 我想指出一些事情。我很困惑为什么他的“部分”仍然包括分隔符。想想看,string.split() 总是排除分隔符。事实证明,有一个超级特殊情况,其中 .split( regex ),其中正则表达式有一个捕获组,也会将捕获的元素插入到结果数组中。这是关于这个超级随机边缘案例的官方文档的链接:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…。向下滚动到“使用正则表达式拆分以在结果中包含部分分隔符”
    • 根据一些实验,我认为匹配的部分总是在奇数索引处。因此,您可以使用index % 2 === 1 而不是part.toLowerCase() === higlight.toLowerCase() 来测试匹配。当您有多个突出显示的单词时,这会派上用场,并且每次与数组进行比较更麻烦。示例实验:"fooAfoobar".split(/(foo|bar)/gi) -> [ '', 'foo', 'A', 'foo', '', 'bar', '' ]
    • 挑剔:你应该使用const parts = text.split(new RegExp(`(${higlight})`, 'gi')); 而不是let parts = text.split(new RegExp(`(${higlight})`, 'gi'));
    • 谢谢!很好!我使用你的函数并编写使用他的函数组件 React。 codesandbox.io/s/musing-rgb-4jkpi?file=/src/…
    【解决方案2】:

    下面是一个使用标准&lt;mark&gt; 标签突出显示文本的反应组件示例:

    const Highlighted = ({text = '', highlight = ''}) => {
       if (!highlight.trim()) {
         return <span>{text}</span>
       }
       const regex = new RegExp(`(${_.escapeRegExp(highlight)})`, 'gi')
       const parts = text.split(regex)
       return (
         <span>
            {parts.filter(part => part).map((part, i) => (
                regex.test(part) ? <mark key={i}>{part}</mark> : <span key={i}>{part}</span>
            ))}
        </span>
       )
    }
    

    这里是如何使用它

    <Highlighted text="the quick brown fox jumps over the lazy dog" highlight="fox"/>
    

    【讨论】:

    • 完美解决方案!
    • 注意这里使用了lodash _.escapeRegExp
    • 我同意,很好的解决方案!
    【解决方案3】:

    已经有react component on NPM可以做你想做的事了:

    var Highlight = require('react-highlighter');
    [...]
    <Highlight search={regex}>{name}</Highlight>
    

    【讨论】:

      【解决方案4】:

      这是我的解决方案。

      我试图专注于简单性和性能,因此我避免了涉及在 React 之外手动操作 DOM 的解决方案,或者像 dangerouslySetInnerHTML 这样的不安全方法。

      此外,此解决方案还负责将后续匹配项合并为单个 &lt;span/&gt;,从而避免出现冗余跨度。

      const Highlighter = ({children, highlight}) => {
        if (!highlight) return children;
        const regexp = new RegExp(highlight, 'g');
        const matches = children.match(regexp);
        console.log(matches, parts);
        var parts = children.split(new RegExp(`${highlight.replace()}`, 'g'));
      
        for (var i = 0; i < parts.length; i++) {
          if (i !== parts.length - 1) {
            let match = matches[i];
            // While the next part is an empty string, merge the corresponding match with the current
            // match into a single <span/> to avoid consequent spans with nothing between them.
            while(parts[i + 1] === '') {
              match += matches[++i];
            }
      
            parts[i] = (
              <React.Fragment key={i}>
                {parts[i]}<span className="highlighted">{match}</span>
              </React.Fragment>
            );
          }
        }
        return <div className="highlighter">{parts}</div>;
      };
      

      用法:

      <Highlighter highlight='text'>Some text to be highlighted</Highlighter>
      

      查看此codepen 以获取实时示例。

      【讨论】:

        【解决方案5】:

        默认情况下,ReactJS 会转义 HTML 以防止 XSS。如果您确实希望设置 HTML,则需要使用特殊属性 dangerouslySetInnerHTML。 试试下面的代码:

        render: function() {
                var name = "Javascript";
                var query = "java"
                return (
                    <div>
                        <input type="checkbox" /> <span dangerouslySetInnerHTML={{__html: this._highlightQuery(name, query)}}></span>
                    </div>
                );
            }
        

        【讨论】:

        • 有没有更好的方法可以在不使用 dangerouslySetInnerHTML 属性的情况下做到这一点?谢谢
        • 是的。这将需要创建一个新的 React 组件来处理查询的“突出显示”。然后,您可以在其中使用该组件。这种方法更符合 ReactJS 的精神,其中事物被分解成小组件,每个组件做一件事情并且做得非常好。这是single responsibility principle,它也适用于其他编程领域。
        【解决方案6】:
          const escapeRegExp = (str = '') => (
            str.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1')
          );
        
          const Highlight = ({ search = '', children = '' }) => {
            const patt = new RegExp(`(${escapeRegExp(search)})`, 'i');
            const parts = String(children).split(patt);
        
            if (search) {
              return parts.map((part, index) => (
                patt.test(part) ? <mark key={index}>{part}</mark> : part
              ));
            } else {
              return children;
            }
          };
        
          <Highlight search="la">La La Land</Highlight>
        

        【讨论】:

          【解决方案7】:

          将匹配标记为函数 https://codesandbox.io/s/pensive-diffie-nwwxe?file=/src/App.js

          import React from "react";
          
          class App extends React.Component {
            constructor(props) {
              super(props);
              this.state = {
                res: "Lorem ipsum dolor"
              };
              this.markMatches = this.markMatches.bind(this);
            }
            markMatches(ev) {
              let res = "Lorem ipsum dolor";
              const req = ev.target.value;
              if (req) {
                const normReq = req
                  .toLowerCase()
                  .replace(/\s+/g, " ")
                  .trim()
                  .split(" ")
                  .sort((a, b) => b.length - a.length);
                res = res.replace(
                  new RegExp(`(${normReq.join("|")})`, "gi"),
                  match => "<mark>" + match + "</mark>"
                );
              }
              this.setState({
                res: res
              });
            }
          
            render() {
              return (
                <div className="App">
                  <input type="text" onChange={this.markMatches} />
                  <br />
                  <p dangerouslySetInnerHTML={{ __html: this.state.res }} />
                </div>
              );
            }
          }
          
          export default App;
          

          【讨论】:

          • 这是我最喜欢的解决方案,它不仅优雅地解决了 OP 的请求,还允许同时替换多个匹配项,不错!
          【解决方案8】:

          我需要在包含 HTML 标签的 cmets 中进行搜索。

          例如:我的一个 cmets 如下例所示

          你好世界

          <div>Hello<strong>World</strong></div>
          

          所以,我想在所有这些类型的 cmets 中搜索并突出显示搜索结果。

          众所周知,我们可以使用 HTML 标签 &lt;mark&gt; 突出显示文本

          所以。我创建了一个辅助函数,如果它包含搜索到的文本,它会执行在文本中添加&lt;mark&gt; 标记的任务。

          getHighlightedText = (text, highlight) => {
              if (!highlight.trim()) {
                return text;
              }
              const regex = new RegExp(`(${highlight})`, "gi");
              const parts = text.split(regex);
              const updatedParts = parts
                .filter((part) => part)
                .map((part, i) =>
                  regex.test(part) ? <mark key={i}>{part}</mark> : part
                );
              let newText = "";
              [...updatedParts].map(
                (parts) =>
                  (newText =
                    newText +
                    (typeof parts === "object"
                      ? `<${parts["type"]}>${highlight}</${parts["type"]}>`
                      : parts))
              );
              return newText;
            };
          

          所以,我们必须在函数内传递我们的文本和搜索文本作为参数。

          输入

          getHighlightedText("<div>Hello<strong>World</strong></div>", "hello")
          

          输出

          <div><mark>Hello</mark><strong>World</strong></div>
          

          如果在解决方案方面需要更多帮助,请告诉我。

          【讨论】:

            【解决方案9】:

            这应该可行:

            var Component = React.createClass({
                _highlightQuery: function(name, query) {
                    var regex = new RegExp("(" + query + ")", "gi");
                    return "<span>"+name.replace(regex, "<strong>$1</strong>")+"</span>";
                },
                render: function() {
                    var name = "Javascript";
                    var query = "java"
                    return (
                        <div>
                            <input type="checkbox" />{JSXTransformer.exec(this._highlightQuery(name, query))}
                        </div>
                    );
                }
            });
            

            基本上,您正在动态生成一个反应组件。如果需要,您可以将 &lt;span&gt; 标签放在 render() 函数中,而不是 _highlightQuery() 之一。

            【讨论】:

              【解决方案10】:

              我建议您使用不同的方法。创建一个组件,例如 &lt;TextContainer /&gt;,其中包含 &lt;Text /&gt; 元素。

              var React = require('react');
              var Text = require('Text.jsx');
              
              var TextContainer = React.createClass({
                  getInitialState: function() {
                      return {
                          query: ''
                      };
                  },
                  render: function() {
                      var names = this.props.names.map(function (name) {
                          return <Text name={name} query={this.state.query} />
                      });
                      return (
                          <div>
                              {names}
                         </div>
                      );
                  }
              });
              
              module.exports = TextContainer;
              

              如您所见,文本容器保存当前查询的状态。现在,&lt;Text /&gt; 组件可能是这样的:

              var React = require('react');
              
              var Text = React.createClass({
                  propTypes: {
                      name: React.PropTypes.string.isRequired,
                      query: React.PropTypes.string.isRequired
                  },
              
                  render: function() {
                      var query = this.props.query;
                      var regex = new RegExp("(" + query + ")", "gi");
                      var name = this.props.name;
                      var parts = name.split(regex);
                      var result = name;
              
                      if (parts) {
                          if (parts.length === 2) {
                              result =
                                  <span>{parts[0]}<strong>{query}</strong>{parts[1]}</span>;
                          } else {
                              if (name.search(regex) === 0) {
                                  result = <span><strong>{query}</strong>{parts[0]}</span>
                              } else {
                                  result = <span>{query}<strong>{parts[0]}</strong></span>
                              }
                          }
                      }
              
                      return <span>{result}</span>;
                  }
              
              });
              
              module.exports = Text;
              

              因此,根组件的状态为当前查询。当它的状态发生改变时,它会触发孩子的render() 方法。每个孩子都会收到新查询作为新道具,并输出文本,突出显示与查询匹配的部分。

              【讨论】:

                【解决方案11】:

                基于@Henok T 的解决方案,这里有一个没有 lodash

                它在 Typescript 中实现并使用 Styled-components,但可以很容易地适应 vanilla JS,只需删除类型并内联添加样式。

                import React, { useMemo } from "react";
                import styled from "styled-components";
                
                const MarkedText = styled.mark`
                  background-color: #ffd580;
                `;
                
                interface IHighlighted { 
                  text?: string;
                  search?: string;
                }
                
                export default function Highlighted({ text = "", search = "" }: IHighlighted): JSX.Element {
                  /**
                   * The brackets around the re variable keeps it in the array when splitting and does not affect testing
                   * @example 'react'.split(/(ac)/gi) => ['re', 'ac', 't']
                   */
                  const re = useMemo(() => {
                    const SPECIAL_CHAR_RE = /([.?*+^$[\]\\(){}|-])/g;
                    const escapedSearch = search.replace(SPECIAL_CHAR_RE, "\\$1");
                    return new RegExp(`(${escapedSearch})`, "i");
                  }, [search]);
                
                  return (
                    <span>
                      {search === ""
                        ? text
                        : text
                            .split(re)
                            .filter((part) => part !== "")
                            .map((part, i) => (re.test(part) ? <MarkedText key={part + i}>{part}</MarkedText> : part))}
                    </span>
                  );
                }
                

                【讨论】:

                  【解决方案12】:

                  使用react-mark.js,您可以:

                  <Marker mark="hello">
                    Hello World
                  </Marker>
                  

                  链接:

                  【讨论】:

                    猜你喜欢
                    • 2021-12-20
                    • 2013-04-07
                    • 2021-08-08
                    • 2011-11-22
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多