Javascript 如何将降价的一小部分解析为React组件?
我有一个非常小的Markdown子集以及一些自定义html,我想将它们解析为React组件。例如,我想转换以下字符串:Javascript 如何将降价的一小部分解析为React组件?,javascript,arrays,regex,reactjs,markdown,Javascript,Arrays,Regex,Reactjs,Markdown,我有一个非常小的Markdown子集以及一些自定义html,我想将它们解析为React组件。例如,我想转换以下字符串: 你好*asdf**你好!干!今天 进入以下数组: [“你好”、asdf,“,你好吗?”,“你”今天在做什么? 然后从React呈现函数返回它(React将以格式化的HTML正确呈现数组) 基本上,我想让用户选择使用一组非常有限的标记,将他们的文本转换为样式化组件(在某些情况下,我自己的组件!) 使用危险的HTML是不明智的,我不想引入外部依赖,因为它们都很重,我只需要非常基本的
你好*asdf**你好!干!今天
进入以下数组:
[“你好”、asdf,“,你好吗?”,“你”今天在做什么?
然后从React呈现函数返回它(React将以格式化的HTML正确呈现数组)
基本上,我想让用户选择使用一组非常有限的标记,将他们的文本转换为样式化组件(在某些情况下,我自己的组件!)
使用危险的HTML是不明智的,我不想引入外部依赖,因为它们都很重,我只需要非常基本的功能
我目前正在做类似的事情,但它非常脆弱,不适用于所有情况。我想知道是否有更好的方法:
function matchStrong(result, i) {
let match = result[i].match(/(^|[^\\])\*(.*)\*/);
if (match) { result[i] = <strong key={"ms" + i}>{match[2]}</strong>; }
return match;
}
function matchItalics(result, i) {
let match = result[i].match(/(^|[^\\])_(.*)_/); // Ignores \_asdf_ but not _asdf_
if (match) { result[i] = <em key={"mi" + i}>{match[2]}</em>; }
return match;
}
function matchCode(result, i) {
let match = result[i].match(/(^|[^\\])```\n?([\s\S]+)\n?```/);
if (match) { result[i] = <code key={"mc" + i}>{match[2]}</code>; }
return match;
}
// Very brittle and inefficient
export function convertMarkdownToComponents(message) {
let result = message.match(/(\\?([!*_`+-]{1,3})([\s\S]+?)\2)|\s|([^\\!*_`+-]+)/g);
if (result == null) { return message; }
for (let i = 0; i < result.length; i++) {
if (matchCode(result, i)) { continue; }
if (matchStrong(result, i)) { continue; }
if (matchItalics(result, i)) { continue; }
}
return result;
}
函数匹配强(结果,i){
让match=result[i]。match(/(^|[^\\])\*(.*)\*/);
如果(匹配){result[i]={match[2]};}
复赛;
}
函数匹配斜体(结果,i){
让match=result[i]。match(/(^ |[^\\])_(.*)/);//忽略\\u asdf,但不忽略\\u asdf_
如果(匹配){result[i]={match[2]};}
复赛;
}
函数匹配代码(结果,i){
让match=result[i]。match(/(^ |[^\\])``\n?([\s\s]+)\n?`/);
如果(匹配){result[i]={match[2]}
;}
复赛;
}
//非常脆弱和低效
导出函数转换器MarkdownToComponents(消息){
让result=message.match(/(\\?([!*\u`+-]{1,3})([\s\s]+?)\2)\s |([^\\!*\u`+-]+)/g);
如果(result==null){返回消息;}
for(设i=0;i这导致了这个问题。看起来您正在寻找一个非常基本的小解决方案。不是像
那样的“超级怪兽”反应(降低它的价格
:)
我想推荐你,它看起来很轻很漂亮!只要1kb,而且非常简单,如果您需要任何其他语法特性,您可以使用它并扩展它
支持的标记列表
更新
刚注意到react组件,一开始就错过了。因此,我相信,以库为例,实现定制所需的组件,从而在不危险地设置HTML的情况下完成任务,这对您来说是非常好的。图书馆很小,很干净。玩得开心点!:)
使用此表替换匹配的标记
replace有一个重载,可以将捕获的组作为参数,我们使用这些捕获的项来查找表并生成替换字符串
[更新]我已经更新了代码,我保留了第一个代码,以防其他人不需要react组件,您可以看到它们之间没有什么区别。
您可以这样做:
var table = {
"*":{
"begin":"<strong>",
"end":"</strong>"
},
"_":{
"begin":"<em>",
"end":"</em>"
},
"!":{
"begin":"<MyComponent onClick={this.action}>",
"end":"</MyComponent>"
},
};
//inside your compoenet
mapData(myMarkdown){
return myMarkdown.split(' ').map((w)=>{
if(w.startsWith('*') && w.endsWith('*') && w.length>=3){
w=w.substr(1,w.length-2);
w=<strong>{w}</strong>;
}else{
if(w.startsWith('_') && w.endsWith('_') && w.length>=3){
w=w.substr(1,w.length-2);
w=<em>{w}</em>;
}else{
if(w.startsWith('!') && w.endsWith('!') && w.length>=3){
w=w.substr(1,w.length-2);
w=<YourComponent onClick={this.action}>{w}</YourComponent>;
}
}
}
return w;
})
}
render(){
let content=this.mapData('hello *asdf* *how* _are_ you !doing! today');
return {content};
}
//在组件内部
地图数据(myMarkdown){
返回myMarkdown.split(“”).map((w)=>{
如果(w.startsWith('*')和&w.endsWith('*')和&w.length>=3){
w=w.substr(1,w.length-2);
w={w};
}否则{
如果(w.startsWith('u')和w.endsWith('u')&和w.length>=3){
w=w.substr(1,w.length-2);
w={w};
}否则{
如果(w.startsWith(“!”)&w.endsWith(“!”)&w.length>=3){
w=w.substr(1,w.length-2);
w={w};
}
}
}
返回w;
})
}
render(){
让content=this.mapData('hello*asdf**how*\u you!doing!today');
返回{content};
}
一个纯使用Javascript和ReactJs的工作解决方案,没有危险的HTML。
方法
逐字符搜索标记元素。一旦遇到一个标记,就搜索该标记的结束标记,然后将其转换为html
代码段中支持的标记
- 大胆的
- 斜体字
- em
- 前
const preTag=“đ”
常数映射={
“*”:“b”,
“!”:“我”,
":"em",,
[preTag]:“pre”
}
类应用程序扩展了React.Component{
构造函数(){
超级()
this.getData=this.getData.bind(this)
}
状态={
数据:[]
}
getData(){
让str=document.getElementById(“ta1”).value
//如果任何标记包含多个字符,请将其替换为使用频率较低的字符并使用它
str=str.replace(/``/gi,preTag)
常数tempArr=[]
const tagsArr=Object.keys(映射)
设strIndexOf=0;
for(设i=0;i=0&&str[i-1]!=“\\”){
tempArr.push(str.substring(0,i).split(“\\”).join(“”)。split(preTag.join(“”)
str=str.substr(i+1);
i=0;
对于(设j=0;j=0&&str[j-1]!=“\\”){
const Tag=map[str[j]];
tempArr.push({str.substring(0,j).split(“\\”).join(“”})
str=str.substr(j+1);
i=0;
打破
}
}
}
}
tempArr.push(str.split(\\“”)。join(“”)
这是我的国家({
数据:tempArr,
})
}
render(){
返回(
渲染它
{this.state.data.map(x=>x)}
)
}
}
ReactDOM.render(
,
document.getElementById('root'))
);代码>
它是如何工作的?
它起作用了
var table = {
"*":{
"begin":"<strong>",
"end":"</strong>"
},
"_":{
"begin":"<em>",
"end":"</em>"
},
"!":{
"begin":"<MyComponent onClick={this.action}>",
"end":"</MyComponent>"
},
};
//inside your compoenet
mapData(myMarkdown){
return myMarkdown.split(' ').map((w)=>{
if(w.startsWith('*') && w.endsWith('*') && w.length>=3){
w=w.substr(1,w.length-2);
w=<strong>{w}</strong>;
}else{
if(w.startsWith('_') && w.endsWith('_') && w.length>=3){
w=w.substr(1,w.length-2);
w=<em>{w}</em>;
}else{
if(w.startsWith('!') && w.endsWith('!') && w.length>=3){
w=w.substr(1,w.length-2);
w=<YourComponent onClick={this.action}>{w}</YourComponent>;
}
}
}
return w;
})
}
render(){
let content=this.mapData('hello *asdf* *how* _are_ you !doing! today');
return {content};
}
// Instead of creating hardcoded variables, we can make the code more extendable
// by storing all the possible tags we'll work with in a Map. Thus, creating
// more tags will not require additional logic in our code.
const tags = new Map(Object.entries({
"*": "strong", // bold
"!": "button", // action
"_": "em", // emphasis
"\uFFFF": "pre", // Just use a very unlikely to happen unicode character,
// We'll replace our multi-length symbols with that one.
}));
// Might be useful if we need to discover the symbol of a tag
const tagSymbols = new Map();
tags.forEach((v, k) => { tagSymbols.set(v, k ); })
const rawMarkdown = `
This must be *bold*,
This also must be *bo_ld*,
this _entire block must be
emphasized even if it's comprised of multiple lines_,
This is an !action! it should be a button,
\`\`\`
beep, boop, this is code
\`\`\`
This is an asterisk\\*
`;
class App extends React.Component {
parseMarkdown(source) {
let currentTag = "";
let currentContent = "";
const parsedMarkdown = [];
// We create this variable to track possible escape characters, eg. "\"
let before = "";
const pushContent = (
content,
tagValue,
props,
) => {
let children = undefined;
// There's the need to parse for empty lines
if (content.indexOf("\n\n") >= 0) {
let before = "";
const contentJSX = [];
let chunk = "";
for (let i = 0; i < content.length; i++) {
if (i !== 0) before = content[i - 1];
chunk += content[i];
if (before === "\n" && content[i] === "\n") {
contentJSX.push(chunk);
contentJSX.push(<br />);
chunk = "";
}
if (chunk !== "" && i === content.length - 1) {
contentJSX.push(chunk);
}
}
children = contentJSX;
} else {
children = [content];
}
parsedMarkdown.push(React.createElement(tagValue, props, children))
};
for (let i = 0; i < source.length; i++) {
const chunk = source[i];
if (i !== 0) {
before = source[i - 1];
}
// Does our current chunk needs to be treated as a escaped char?
const escaped = before === "\\";
// Detect if we need to start/finish parsing our tags
// We are not parsing anything, however, that could change at current
// chunk
if (currentTag === "" && escaped === false) {
// If our tags array has the chunk, this means a markdown tag has
// just been found. We'll change our current state to reflect this.
if (tags.has(chunk)) {
currentTag = tags.get(chunk);
// We have simple content to push
if (currentContent !== "") {
pushContent(currentContent, "span");
}
currentContent = "";
}
} else if (currentTag !== "" && escaped === false) {
// We'll look if we can finish parsing our tag
if (tags.has(chunk)) {
const symbolValue = tags.get(chunk);
// Just because the current chunk is a symbol it doesn't mean we
// can already finish our currentTag.
//
// We'll need to see if the symbol's value corresponds to the
// value of our currentTag. In case it does, we'll finish parsing it.
if (symbolValue === currentTag) {
pushContent(
currentContent,
currentTag,
undefined, // you could pass props here
);
currentTag = "";
currentContent = "";
}
}
}
// Increment our currentContent
//
// Ideally, we don't want our rendered markdown to contain any '\'
// or undesired '*' or '_' or '!'.
//
// Users can still escape '*', '_', '!' by prefixing them with '\'
if (tags.has(chunk) === false || escaped) {
if (chunk !== "\\" || escaped) {
currentContent += chunk;
}
}
// In case an erroneous, i.e. unfinished tag, is present and the we've
// reached the end of our source (rawMarkdown), we want to make sure
// all our currentContent is pushed as a simple string
if (currentContent !== "" && i === source.length - 1) {
pushContent(
currentContent,
"span",
undefined,
);
}
}
return parsedMarkdown;
}
render() {
return (
<div className="App">
<div>{this.parseMarkdown(this.props.rawMarkdown)}</div>
</div>
);
}
}
ReactDOM.render(<App rawMarkdown={rawMarkdown.replace(/```/g, "\uFFFF")} />, document.getElementById('app'));