使用 ReactJS 突出显示文本
Highlight text using ReactJS
我试图突出显示与查询匹配的文本,但我不知道如何让标签显示为 HTML 而不是文本。
var Component = React.createClass({
_highlightQuery: function(name, query) {
var regex = new RegExp("(" + query + ")", "gi");
return name.replace(regex, "<strong></strong>");
},
render: function() {
var name = "Javascript";
var query = "java"
return (
<div>
<input type="checkbox" /> {this._highlightQuery(name, query)}
</div>
);
}
});
当前输出:Java脚本
期望输出:Java脚本
默认情况下,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>
);
}
这应该有效:
var Component = React.createClass({
_highlightQuery: function(name, query) {
var regex = new RegExp("(" + query + ")", "gi");
return "<span>"+name.replace(regex, "<strong></strong>")+"</span>";
},
render: function() {
var name = "Javascript";
var query = "java"
return (
<div>
<input type="checkbox" />{JSXTransformer.exec(this._highlightQuery(name, query))}
</div>
);
}
});
基本上,您是在动态生成 React 组件。如果需要,您可以将 <span>
标记放在 render()
函数中而不是 _highlightQuery()
函数中。
我建议您使用不同的方法。创建一个组件,比如 <TextContainer />
,其中包含 <Text />
个元素。
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;
如您所见,文本容器包含当前查询的状态。现在,<Text />
组件可能是这样的:
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;
因此,根组件具有当前查询状态。当它的状态被改变时,它会触发 children 的 render()
方法。每个 child 将接收新查询作为新道具,并输出文本,突出显示与查询匹配的部分。
这是我简单的 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>;
}
它 returns 一个跨度,其中请求的部分用 <b> </b>
标记突出显示。如果需要,可以简单地修改它以使用另一个标签。
更新:为了避免唯一键丢失警告,这里有一个基于 span 并为匹配部分设置 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>;
}
已经有react component on NPM做你想做的事了:
var Highlight = require('react-highlighter');
[...]
<Highlight search={regex}>{name}</Highlight>
下面是一个使用标准 <mark>
标签高亮文本的 React 组件示例:
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"/>
const escapeRegExp = (str = '') => (
str.replace(/([.?*+^$[\]\(){}|-])/g, '\')
);
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>
这是我的解决方案。
我试图专注于简单性和性能,所以我避免了涉及在 React 之外手动操作 DOM 或 dangerouslySetInnerHTML
.
等不安全方法的解决方案
此外,此解决方案负责将后续匹配项合并为一个 <span/>
,从而避免出现冗余跨度。
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 示例。
将匹配标记为函数
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;
我需要在包含 HTML 标签的评论中搜索。
例如:我的一条评论如下例所示
你好世界
<div>Hello<strong>World</strong></div>
所以,我想在所有这些评论中搜索并突出显示搜索结果。
众所周知,我们可以使用 HTML 标签 <mark>
来突出显示文本
所以。我创建了一个辅助函数,它执行在包含搜索文本的文本中添加 <mark>
标签的任务。
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>
如果需要更多解决方案帮助,请告诉我。
基于@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, "\");
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>
);
}
使用 react-mark.js
您可以简单地:
<Marker mark="hello">
Hello World
</Marker>
链接:
我从上面扩展了@Henok T 的版本,以便能够突出显示由 space 分割的多个文本部分,但将字符串放在引号或双引号中。
例如text "some text" 'some other text' text2
的亮点
将突出显示文本:
text
some text
some other text
text2
在给定的文本中。
const Highlighted = ({text = '', highlight = ''}: { text: string; highlight: string; }) => {
if (!highlight.trim()) {
return <span>{text}</span>
}
var highlightRegex = /'([^']*)'|"([^"]*)"|(\S+)/gi; // search for all strings but keep strings with "" or '' together
var highlightArray = (highlight.match(highlightRegex) || []).map(m => m.replace(highlightRegex, ''));
// join the escaped parts with | to a string
const regexpPart= highlightArray.map((a) => `${_.escapeRegExp(a)}`).join('|');
// add the regular expression
const regex = new RegExp(`(${regexpPart})`, '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>
)
}
我试图突出显示与查询匹配的文本,但我不知道如何让标签显示为 HTML 而不是文本。
var Component = React.createClass({
_highlightQuery: function(name, query) {
var regex = new RegExp("(" + query + ")", "gi");
return name.replace(regex, "<strong></strong>");
},
render: function() {
var name = "Javascript";
var query = "java"
return (
<div>
<input type="checkbox" /> {this._highlightQuery(name, query)}
</div>
);
}
});
当前输出:Java脚本
期望输出:Java脚本
默认情况下,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>
);
}
这应该有效:
var Component = React.createClass({
_highlightQuery: function(name, query) {
var regex = new RegExp("(" + query + ")", "gi");
return "<span>"+name.replace(regex, "<strong></strong>")+"</span>";
},
render: function() {
var name = "Javascript";
var query = "java"
return (
<div>
<input type="checkbox" />{JSXTransformer.exec(this._highlightQuery(name, query))}
</div>
);
}
});
基本上,您是在动态生成 React 组件。如果需要,您可以将 <span>
标记放在 render()
函数中而不是 _highlightQuery()
函数中。
我建议您使用不同的方法。创建一个组件,比如 <TextContainer />
,其中包含 <Text />
个元素。
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;
如您所见,文本容器包含当前查询的状态。现在,<Text />
组件可能是这样的:
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;
因此,根组件具有当前查询状态。当它的状态被改变时,它会触发 children 的 render()
方法。每个 child 将接收新查询作为新道具,并输出文本,突出显示与查询匹配的部分。
这是我简单的 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>;
}
它 returns 一个跨度,其中请求的部分用 <b> </b>
标记突出显示。如果需要,可以简单地修改它以使用另一个标签。
更新:为了避免唯一键丢失警告,这里有一个基于 span 并为匹配部分设置 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>;
}
已经有react component on NPM做你想做的事了:
var Highlight = require('react-highlighter');
[...]
<Highlight search={regex}>{name}</Highlight>
下面是一个使用标准 <mark>
标签高亮文本的 React 组件示例:
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"/>
const escapeRegExp = (str = '') => (
str.replace(/([.?*+^$[\]\(){}|-])/g, '\')
);
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>
这是我的解决方案。
我试图专注于简单性和性能,所以我避免了涉及在 React 之外手动操作 DOM 或 dangerouslySetInnerHTML
.
此外,此解决方案负责将后续匹配项合并为一个 <span/>
,从而避免出现冗余跨度。
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 示例。
将匹配标记为函数 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;
我需要在包含 HTML 标签的评论中搜索。
例如:我的一条评论如下例所示
你好世界
<div>Hello<strong>World</strong></div>
所以,我想在所有这些评论中搜索并突出显示搜索结果。
众所周知,我们可以使用 HTML 标签 <mark>
所以。我创建了一个辅助函数,它执行在包含搜索文本的文本中添加 <mark>
标签的任务。
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>
如果需要更多解决方案帮助,请告诉我。
基于@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, "\");
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>
);
}
使用 react-mark.js
您可以简单地:
<Marker mark="hello">
Hello World
</Marker>
链接:
我从上面扩展了@Henok T 的版本,以便能够突出显示由 space 分割的多个文本部分,但将字符串放在引号或双引号中。
例如text "some text" 'some other text' text2
的亮点
将突出显示文本:
text
some text
some other text
text2
在给定的文本中。
const Highlighted = ({text = '', highlight = ''}: { text: string; highlight: string; }) => {
if (!highlight.trim()) {
return <span>{text}</span>
}
var highlightRegex = /'([^']*)'|"([^"]*)"|(\S+)/gi; // search for all strings but keep strings with "" or '' together
var highlightArray = (highlight.match(highlightRegex) || []).map(m => m.replace(highlightRegex, ''));
// join the escaped parts with | to a string
const regexpPart= highlightArray.map((a) => `${_.escapeRegExp(a)}`).join('|');
// add the regular expression
const regex = new RegExp(`(${regexpPart})`, '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>
)
}