简体   繁体   English

如何使用 JSX 突出显示字符串中的匹配项?

[英]How to highlight matches within a string with JSX?

I have a custom autocomplete, so when you type, it will display a list of suggestions based on the input value.我有一个自定义自动完成功能,因此当您键入时,它会根据输入值显示建议列表。 In the list, I would like to bold the characters that are the same as the input value.在列表中,我想将与输入值相同的字符加粗。

So if I have a list of suggestions: "alligator", "lima", "lime", and I typed "li", then the suggestions would look like this:因此,如果我有一个建议列表:“alligator”、“lima”、“lime”,然后我输入了“li”,那么建议将如下所示:

  • al li gator鳄鱼
  • li ma李妈
  • li me

I have this simple map in my jsx file:我的 jsx 文件中有这个简单的map

<ul>
  {matches.map(function(match, idx){
    let re = new RegExp(value, 'g');
    let str = match.replace(re, '<b>'+ value +'</b>');
    return <li key={idx}>{str}</li>
  })}
</ul>

where value is the input value.其中value是输入值。 It displays the list but in this string format它显示列表,但以这种字符串格式

  • al<b>li</b>gator al<b>ligator</b>鳄鱼
  • <b>li</b>ma <b>李</b>马
  • <b>li</b>me <b>我</b>我

Not sure how to go about with React.不确定如何使用 React go。 I thought of using dangerouslyinnerhtml or something like that, but I think that's a last resort thing.我想过使用dangerouslyinnerhtml或类似的东西,但我认为这是最后的手段。 I would like to avoid that if possible.如果可能的话,我想避免这种情况。

This my autocomplete component:这是我的自动完成组件:

class Autocomplete extends Component{

    constructor(props){
        super(props);

        this.state = {
            value: '',
            matches: [],
            showMatches: false

        }

    }

    searchListing(){
        api.call {
           that.setState({
                showMatches: true, 
                matches: a
            });
        })
        }

    }

    handleOnChangeInput(e){

        let value = e.target.value;
        this.setState({ value: value})

        if(value !== ''){
            this.searchListing(e);
        }else{
            // console.log("value", e.target.value);          
            this.setState({
                showMatches: false,
                matches: []
            })
        }
    }

    render(){

        let matches = this.state.matches;
        let value = this.state.value;
        let matchesHtml;

        if(this.state.showMatches){
            matchesHtml = <ul>
                            {matches.map(function(match, idx){
                                let re = new RegExp(value, 'g');
                                let str = match.replace(re, '<b>'+ value +'</b>');
                                return <li key={idx} dangerouslySetInnerHTML={{__html: str}}></li>

                            })}
                        </ul>
        }
        return(
            <div>
               <input placeholder="type a name" onChange={this.handleOnChangeInput}/>

               {matchesHtml}
            </div>
        );
    }
}

Writing your own highlighting code could lead down a rabbit hole.编写自己的突出显示代码可能会导致陷入困境。 In my answer, I assume only simple text (no HTML within the strings, no charset edge cases) and valid non-escaped RegExp pattern string.在我的回答中,我只假设简单的文本(字符串中没有 HTML,没有字符集边缘情况)和有效的非转义RegExp模式字符串。


Instead of building a new string, you could build a new array, in which you could put JSX .您可以构建一个新数组,而不是构建一个新字符串,您可以在其中放置 JSX

A React component can also return an array of elements : React 组件还可以返回元素数组

 render() { // No need to wrap list items in an extra element! return [ // Don't forget the keys :) <li key="A">First item</li>, <li key="B">Second item</li>, <li key="C">Third item</li>, ]; }

The logic behind背后的逻辑

As a simple proof of concept, here's the logic we could use:作为一个简单的概念证明,这是我们可以使用的逻辑:

 const defaultHighlight = s => <em>{s}</em>; // Needed if the target includes ambiguous characters that are valid regex operators. const escapeRegex = v => v.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, "\\\\$&"); /** * Case insensitive highlight which keeps the source casing. * @param {string} source text * @param {string} target to highlight within the source text * @param {Function} callback to define how to highlight the text * @returns {Array} */ const highlightWord = (source, target, callback) => { const res = []; if (!source) return res; if (!target) return source; const regex = new RegExp(escapeRegex(target), 'gi'); let lastOffset = 0; // Uses replace callback, but not its return value source.replace(regex, (val, offset) => { // Push both the last part of the string, and the new part with the highlight res.push( source.substr(lastOffset, offset - lastOffset), // Replace the string with JSX or anything. (callback || defaultHighlight)(val) ); lastOffset = offset + val.length; }); // Push the last non-highlighted string res.push(source.substr(lastOffset)); return res; }; /** * React component that wraps our `highlightWord` util. */ const Highlight = ({ source, target, children }) => highlightWord(source, target, children); const TEXT = 'This is a test.'; const Example = () => ( <div> <div>Nothing: "<Highlight />"</div> <div>No target: "<Highlight source={TEXT} />"</div> <div>Default 'test': "<Highlight source={TEXT} target="test" />"</div> <div>Multiple custom with 't': "<Highlight source={TEXT} target="t"> {s => <span className="highlight">{s}</span>} </Highlight>" </div> <div>Ambiguous target '.': "<Highlight source={TEXT} target="."> {s => <span className="highlight">{s}</span>} </Highlight>" </div> </div> ); // Render it ReactDOM.render( <Example />, document.getElementById("react") );
 .highlight { background-color: yellow; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="react"></div>

No need to use dangerouslySetInnerHTML here.无需在此处使用dangerouslySetInnerHTML SetInnerHTML。

This highlightWord function can take any function to wrap the matched string.这个highlightWord函数可以采用任何函数来包装匹配的字符串。

highlight(match, value) // default to `s => <em>{s}</em>`
// or
highlight(match, value, s => <span className="highlight">{s}</span>);

I'm doing minimal regex string escaping based on another answer on Stack Overflow.我正在根据Stack Overflow上的另一个答案进行最少的正则表达式字符串转义


The Highlight component Highlight组件

As shown, we can create a component so it's "more react"!如图所示,我们可以创建一个组件,使其“更具反应性”!

/**
 * React component that wraps our `highlightWord` util.
 */
const Highlight = ({ source, target, children }) => 
  highlightWord(source, target, children);

Highlight.propTypes = {
  source: PropTypes.string,
  target: PropTypes.string,
  children: PropTypes.func,
};

Highlight.defaultProps = {
  source: null,
  target: null,
  children: null,
};

export default Highlight;

It uses a render prop , so you'd have to change your rendering to:它使用render prop ,因此您必须将渲染更改为:

<ul>
  {matches.map((match, idx) => (
    <li key={idx}>
      <Highlight source={match} target={value}>
        {s => <strong>{s}</strong>}
      </Highlight>
    </li>
  ))}
</ul>

Just use dangerouslySetInnerHTML but take attention it make to inadvertently expose your users to a cross-site scripting (XSS) attack只需使用dangerouslySetInnerHTML ,但要注意它会无意中使您的用户暴露于跨站点脚本 (XSS) 攻击

...
const valueToBold = (match: string) => {
   const regex = new RegExp(searchFilterValue, 'g');
   return match.replace(regex, '<b>$&</b>');
};
return (
...
 <ul>
   {matches.map((match, idx)=> ( 
     <li key={idx}>
      <span dangerouslySetInnerHTML={{ __html:valueToBold(match) }} />
     </li>
    ))}
 </ul>
...
)

You just append your mapper as children inside your auto complete component.您只需将您的映射器作为子项添加到您的自动完成组件中。

<CustomAutocomplete>
  <ul>
    {
      matches.map(function(match, idx){
        let re = new RegExp(value, 'g');
        let str = match.replace(re, '<b>'+ value +'</b>');
        return (<li key={idx}>{str}</li>)
      })
    }
  </ul>
</CustomAutocomplete>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM