如何比较两个文本字段内容并突出显示在 ReactJS 中更改的字符?

问题描述

我需要比较两个 material-ui 文本字段内容并突出显示两者中更改的字符。这基本上与此处提出的问题相同,但对于 ReactJS 而不是 C# Windows 窗体:

How to compare two rich text box contents and highlight the characters that are changed?

解决方法

非常感谢回复的人。我很有趣地想到了一个解决方案。希望这可以帮助其他人解决这个问题。

概念是我有两个带有内容的可编辑文本框,我强调了两者之间的差异。首先,让我们解决突出显示的问题。这很简单。我使用 Squiggs 的方法通过使用以下包来比较两个字符串:

https://github.com/kpdecker/jsdiff

利用 Diff 包,我创建了一个函数来比较两个字符串并返回结果:

function compareStrings(string1,string2) {
  let results = Diff.diffChars(string1,string2);

  let output = "";
  results.forEach((item) => {

    if (item.removed) {
      output += `<span style="background-color:yellow">${item.value}</span>`;
    } else if (!item.added) {
      output += `${item.value}`;
    }
  });

  return output;
}

在使用 Diff 包时,它将识别出的差异和相似之处分为不同的项目。它使处理结果变得相对容易。在上面的代码中,迭代 results 中的结果。如果某个项目的 removed 设置为 true,则它是 string1 中不在 string2 中的项目。如果 added 设置为 true,则它是 string2 中不在 string1 中的项目。如果两者都未设置为 true,则它是两个字符串中的一个项目。我的函数的输出将包含 string1 的所有字符。如果某个项目的 added 设置为 true,则该项目将被忽略。例如,如果 string1catstring2bat,则 output 将为 cat,但 c 以黄色突出显示.

那么如何将这个突出显示的文本插入到 Material-Ui 文本框中?好吧,我选择沿着使用 contentEditable 控件的路线走下去。您可以将 p 标记(和其他标记)转换为呈现 HTML 的可编辑文本框。这不是我喜欢的解决方案,因为它打开了处理 XSS 的麻烦,但似乎不可避免。如果有人遵循此解决方案,我恳请您实施客户端和服务器端输入卫生以防止 XSS。后者尤为重要,因为可以通过各种方法轻松绕过客户端验证,例如使用检查元素修改页面或利用数据包制作工具。

以下是 contentEditable 文本框在 HTML 中的样子:

<p
    id="textbox1"
    className="textbox"
    variant="outlined" 
    contentEditable={true}
/>

从这里,我可以将 compareStrings 的结果分配给它:

document.getElementById("textbox1").innerHTML = htmlData

所以最终结果将有两个 contentEditable 文本框。我必须调用 compareStrings 两次并将结果应用于两者。

当然,我只是详细介绍了解决这个问题的要点。还有其他生活质量功能,例如复制和粘贴,详情如下:

Highlight value of @material-ui/core TextField in React at specific indices

,

如果您能提供一些代码,那么了解您的代码当前是如何设置的以及如何实现该功能会更容易。我尝试按照您提到的内容创建一些内容(沙箱:https://codesandbox.io/s/sam-cards-forked-jyrlv?file=/src/App.js:0-1786

说明:

首先,我们需要区分一种方法来找出两个字符串之间的差异。这很简单,可以通过对每个字符串逐个字符进行迭代并将两者进行比较来完成。

const findDiff = (string1,string2) => {
  // we will use this to keep an eye on the two strings
  let index = 0
  
  while (index < string1.length || index < string2.length) {
    const left_char = string1[index]
    const right_char = string2[index]
    if (left_char !== right_char) {
        // they are different
    } else {
      // they are the same character
    }
    index++
  }
    return // return something here
}

findDiff(string1,string2);

现在,我们的问题是将它们渲染到屏幕上并突出显示它们。一种方法(它很丑但确实做到了)是:

  1. 跟踪不同字符的索引并将它们设置为状态
  2. 将字符串转换为数组
  3. 映射数组,如果在 state 中找到当前索引(意味着它是“已更改”的值),则返回一个 css 修改的跨度以“突出显示”它

我将其设置为在单击按钮时显示,您可以在这里看到: https://codesandbox.io/s/sam-cards-forked-jyrlv?file=/src/App.js:0-1786

import "./styles.css";
import { useState,useEffect } from "react";

const string1 = "abcdefg";
const string2 = "adcelig123";
const strar1 = string1.split("");
const strar2 = string2.split("");

export const YourComponent = () => {
  const [isHighlightActive,setIsHighlightActive] = useState(false);
  const [wrongCharIndexes] = useState(new Set());

  // re-calculate the differences between strings whenever they change
  useEffect(() => {
    findDiff(string1,string2);
  },[string1,string2]);

  const findDiff = (string1,string2) => {
    // we will use this to keep an eye on the two strings
    let index = 0;

    while (index < string1.length || index < string2.length) {
      const left_char = string1[index];
      const right_char = string2[index];
      if (left_char === right_char) {
        wrongCharIndexes.add(index);
      }
      index++;
    }
    return;
  };

  return (
    <div className="App">
      {isHighlightActive ? (
        // map through the two strings and render the highlighted character or regular character
        <>
          <p className="flex">
            {strar1.map((char,index) => {
              return wrongCharIndexes.has(index) ? (
                <span className="highlighted">{char}</span>
              ) : (
                <>{char}</>
              );
            })}
          </p>
          <p className="flex">
            {strar2.map((char,index) => {
              return wrongCharIndexes.has(index) ? (
                <span className="highlighted">{char}</span>
              ) : (
                <>{char}</>
              );
            })}
          </p>
        </>
      ) : (
        <div>
          <p>{string1}</p>
          <p>{string2}</p>
        </div>
      )}
      <button onClick={() => setIsHighlightActive((prev) => !prev)}>
        Click me
      </button>
    </div>
  );
};