基于react的字符串模板替换html方案解析

前言

在定制化的场景中,后端返回给我们一段字符串文本,我们需要对里面的某些内容进行样式的高亮(变颜色,字体大小等)。

比如,后端返回给我们这么一段字符串:

"2022-02-02全天呈现畅通状态,全天拥堵指数:1.06,环比昨日0.3%"
复制代码

现在我们想对字符串文本的 2022-02-02畅通1.060.3%进行不同类型的样式高亮处理。效果如下:

image.png

这样的情况,我们该如何处理了?

问题拆分

我们可以后端协商,定义通用的规则,将不同类型的高亮文本进行分类,通过特殊的标记,替换成相应的样式。

方案

在该案例中,我们将高亮文本分为时间、状态、数值、比率,定义特殊的标记。

描述类型:

  • 时间 ${date}
  • 数值 ${number}
  • 比率 ${ratio}
  • 状态 ${status}

描述格式:

${xxx}数据${/xxx}
复制代码

示例:

约定后端返回如下的格式实例:

"${date}2022-02-02${/date}全天呈现${status}畅通${/status}状态,全天拥堵指数:${number}1.06${/number},环比昨日${ratio}0.3%${/ratio}"
复制代码

重点:

  • 识别特殊标记,并替换标记;
  • 将字符串转译成html;

这里我们通过正则表达式匹配特殊标记,通过replace方法替换特殊字符。再使用react的dangerouslySetInnerHTML设置 HTML。

第一版

具体代码如下:

import React from "react";

const formatStringRenderToJsxElement = (value: string) => {
  if (value === null || value === undefined) {
    return "--";
  }

  if (typeof value === "string") {
    // 时间
    const dateStartReg = RegExp("\\${date}", "g");
    const dateEndReg = RegExp("\\${/date}", "g");

    // 数值
    const numberStartReg = RegExp("\\${number}", "g");
    const numberEndReg = RegExp("\\${/number}", "g");

    // 比率
    const ratioStartReg = RegExp("\\${ratio}", "g");
    const ratioEndReg = RegExp("\\${/ratio}", "g");

    // 状态
    const statusStartReg = RegExp("\\${status}", "g");
    const statusEndReg = RegExp("\\${/status}", "g");

    const result = value
      .replace(dateStartReg, '<span style="color: #00bcd4">')
      .replace(dateEndReg, "</span>")
      .replace(numberStartReg, '<span style="color: #4285ff">')
      .replace(numberEndReg, "</span>")
      .replace(ratioStartReg, '<span style="color: #ff0000">')
      .replace(ratioEndReg, "</span>")
      .replace(statusStartReg, '<span style="color: #1fc371">')
      .replace(statusEndReg, "</span>");

    return <span dangerouslySetInnerHTML={{ __html: result }} style={style}></span>;
  }

  return undefined;
};
复制代码

第二版

这里我们把代码精简一下,将传入的值抽取,变得通用一些,添加一些类型的判断,防止注入攻击的逻辑等。

具体代码如下:

const formatStringRenderToJsxElement = (
  value: string | number,
  config: { mark: string; replaceValue: string;}[]
) => {
  if (value === null || value === undefined) {
    return "--";
  }

  if (typeof value === "number") {
    return value;
  }

  if (typeof value === "string") {
    // 防止注入攻击
    let result = value.replace(RegExp("<", "g"), "&lt;").replace(RegExp(">", "g"), "&gt");

    config?.forEach((item) => {
      result = result.replace(RegExp(item.mark, "g"), item.replaceValue);
    });
    return <span dangerouslySetInnerHTML={{ __html: result }}></span>;
  }

  return value;
};
复制代码

使用示例:

const value = "${date}2022-02-02${/date}全天呈现${status}畅通${/status}状态,全天拥堵指数:${number}1.06${/number},环比昨日${ratio}0.3%${/ratio}";
const config = [
  { mark: "\\${date}", replaceValue: '<span style="color: #9E2323">' },
  { mark: "\\${/date}", replaceValue: "</span>" },
  { mark: "\\${status}", replaceValue: '<span style="color: #F28135">' },
  { mark: "\\${/status}", replaceValue: "</span>" },
  { mark: "\\${number}", replaceValue: '<span style="color: #FEC919">' },
  { mark: "\\${/number}", replaceValue: "</span>" },
  { mark: "\\${ratio}", replaceValue: '<span style="color: #1FC371">' },
  { mark: "\\${/ratio}", replaceValue: "</span>" },
];

formatStringRenderToJsxElement(value, config)
复制代码

总结

再不同的业务场景中,我们可以对特殊标记进行不同的定义,方便扩展,原则上避免使用常用符号,避免因用户输入的数据和标记一样,导致检测逻辑异常。关于代码注入html,这块可能会有XSS(跨站脚本攻击),这里需要额外注意,特殊情况下, 需要做安全检测处理。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享