前端diff文件对比使用worker进行优化
worker的使用:React中使用worker线程
diff实现文件对比差异功能:react项目配合diff实现文件对比差异功能
本文只是简单记录,如遇到相同问题的,没看懂我写的可以私信联系:
以下是对文件对比进行优化的写法:
diff.js
/*
* @Descripttion:
* @version:
* @Author: ZhangJunQing
* @Date: 2021-11-12 17:35:44
* @LastEditors: ZhangJunQing
* @LastEditTime: 2022-05-12 16:15:59
*/
import React, {
Fragment } from 'react'
import {
Modal, Divider, Spin } from 'antd';
import Button from '@com/Button'
import Worker from './workerJS/differ.worker'
// const Diff = require('diff');
const pStyleObj = {
fontSize: "0",
marginBottom: "0",
padding: "0",
lineHeight: "2px",
wordBreak: "break-word",
width: "100%",
// minHeight: `${560}px`,
overflowX: "auto"
}
const styleObj = {
lineHeight: "22px",
border: "2px solid #fff",
paddingTop: "2px",
paddingBottom: "2px",
borderRadius: "4px",
fontSize: "16px",
paddingLeft: "10px",
marginBottom: "0",
}
export default class DiffString extends React.PureComponent {
/**
* oldStr 为上次文件 已生效配置
*
* newStr 为最新文件 待下发
*
*
* isModalVisible modal显示与否
*
*/
static defaultProps = {
isModalVisible: false,
title: "预览文件差异",
leftTitle: "已生效配置",
rightTitle: "待下发",
changeIsModalVisibleState: () => {
},
oldStr: "",
newStr: "",
isDiffWords: false, //是否开启单词对比 但是这个也是在行对比的基础上进行改造的
// 这个变量是为了 空行 使两边相同的内容行对齐
isSplitBRFlag: true,//是否为 对齐查看 这个变量目前不做动态改变 和changeisSplitBRFlagFun方法配合
headerHeight: 1,
}
/**
*
* 注意:
*
* 当开启 单词对比后 isDiffWords :true 需要 设置 isSplitBRFlag false
*
* * contextBoxLoading 加载文件的loading
*/
state = {
isOriginFlag: false, //切换比较模式 默认为文件差异对比 true为源文件对比
defaultAddColor: "green",
defaultDelColor: "red",
defaultColor: "grey",
DiffLeft: [],
DiffRight: [],
leftSourceList: [],
rightSourceList: [],
contextBoxLoading: false,
}
myWorker = null
componentDidMount() {
this.myWorker = new Worker();
}
componentWillReceiveProps(props) {
const {
newStr, oldStr, isModalVisible } = props
if (!isModalVisible) {
this.myWorker.terminate()
return false
} else {
this.myWorker = new Worker();
}
if (newStr === '' && oldStr === '') return
console.time("收到数据直至结束花费时间")
this.setState({
contextBoxLoading: true
})
this.myWorker.postMessage({
newStr: newStr.replace(/\r/ig, ''), oldStr: oldStr.replace(/\r/ig, '') })
const that = this
this.myWorker.onmessage = function (e) {
const {
DiffLeft, leftSourceList, rightSourceList } = e.data
that.setState({
DiffLeft,
DiffRight: DiffLeft,
leftSourceList,
rightSourceList,
contextBoxLoading: false
}, () => {
console.timeEnd("收到数据直至渲染结束花费时间")
})
}
}
componentWillUnmount() {
this.myWorker.terminate()
}
// 源文件切换变量
changeOriginFileFun = () => {
this.setState({
isOriginFlag: !this.state.isOriginFlag,
})
}
// 是否为 对齐查看
// changeisSplitBRFlagFun = () => {
// this.setState({
// isSplitBRFlag: !this.state.isSplitBRFlag
// })
// }
/**
* 每次进来都显示 对比文件页面
*/
handleCancel() {
this.setState({
isOriginFlag: false,//默认还是显示 对比文件页面
})
this.props.changeIsModalVisibleState()
}
/**
* 这个方法 事件委托
*
* 点击p标签 改变当前p标签的样式
* 以及更改对面等行的p标签的样式
*
* 点击左边 有背景的为defaultDelColor
* 点击右边 有背景的为defaultAddColor
* 不是增加不是删除的的为defaultColor
*
* 2px solid #fff 默认边框
* 如果只给当前点击的加边框 对引起整个页面抖动
*
* 所有每一个p标签都添加一个边框 颜色为底色
*
* 这样每次点击的时候把所有的颜色都重置
*
* 然后再将点击的p换颜色 即可
*/
changeRightLineFun = (flag, ev) => {
ev = ev || window.event;
let target = ev.target || ev.srcElement;
let index;
// 点击空P标签
if (target.style.userSelect === 'none') {
return false;
}
let {
defaultAddColor, defaultDelColor } = this.state
let RightFileId = flag === 'right' ? document.getElementById('RightFileId') : document.getElementById('LeftFileId')
let LeftFileId = flag === 'right' ? document.getElementById('LeftFileId') : document.getElementById('RightFileId')
let LeftFileIdP = LeftFileId.getElementsByTagName('p')
let RightFileIdP = RightFileId.getElementsByTagName('p')
if (target.nodeName.toLowerCase() == "p") {
for (let i = 0; i < RightFileIdP.length; i++) {
if (RightFileIdP[i] === target)
index = i;
RightFileIdP[i].style.border = "2px solid #fff"
}
for (let i = 0; i < LeftFileIdP.length; i++) {
if (LeftFileIdP[i] === target)
index = i;
LeftFileIdP[i].style.border = "2px solid #fff"
}
if (flag === 'right') {
if (target.parentNode.style.color === defaultAddColor) {
target.style.border = `2px solid ${
defaultAddColor}`;
} else {
target.style.border = "2px solid #000";
}
} else {
if (target.parentNode.style.color === defaultDelColor) {
target.style.border = `2px solid ${
defaultDelColor}`;
} else {
target.style.border = "2px solid #000";
}
}
LeftFileIdP[index].style.border = "2px solid #000";
}
}
// 左右盒子样式
returnDivStyle = (item) => {
let {
defaultAddColor, defaultDelColor, defaultColor, } = this.state;
return {
color: item ?.added ? defaultAddColor :
item ?.removed ? defaultDelColor : defaultColor,
fontWeight: item ?.added ? '600' :
item ?.removed ? '600' : '600',
...pStyleObj
}
}
// 判断是否换行
returBrOrNULL = (idx, item) => {
return idx !== item.value.split('\n').length - 1 ? <br /> : null
}
// 显示P标签
returnPStyle = () => {
let {
defaultColor, } = this.state;
return {
backgroundColor: "#fff", whiteSpace: "pre", cursor: 'pointer', color: defaultColor, ...styleObj }
}
// 隐藏P标签
returnTransparentP = (item) => {
return (
item ?.value && item.value.split('\n').map((child, idx) => {
//隐藏的元素 为了空格
return <Fragment key={
idx} >
{
child && <p style={
{
whiteSpace: "pre", color: "transparent", userSelect: "none", ...styleObj }} >占位置</p>}
{
this.returBrOrNULL(idx, item)}
</Fragment>
})
)
}
// 左侧和右侧标题
returnH2Fun = (title) => {
const {
headerHeight } = this.props
return <h2 style={
{
fontWeight: "700", fontSize: "18px", lineHeight: "25px", height: `${
25 * headerHeight}px` }}>{
title}</h2>
}
// 返回右侧domlist
returnRightDomFun = () => {
let {
isOriginFlag, defaultAddColor, defaultDelColor, defaultColor, DiffRight, rightSourceList } = this.state;
const {
isSplitBRFlag, isDiffWords } = this.props
let domList = isOriginFlag ? rightSourceList : DiffRight
return domList.map((item, index) => {
return (
<Fragment key={
index} >
<div
onClick={
(e) => {
this.changeRightLineFun("right", e) }}
style={
this.returnDivStyle()}>{
!item ? null : item.removed ? (isSplitBRFlag ?
//隐藏的元素 为了空格
this.returnTransparentP(item) : null)
: item.value.split('\n').map((i, idx) => {
return <Fragment key={
idx} >
{
/* 默认 */}
{
i && !isDiffWords ? <p style={
{
backgroundColor: `${
item.added ? "rgb(221,255,221)" : "#fff"}`, whiteSpace: "pre", cursor: 'pointer', ...styleObj }} key={
idx}>{
i}</p> : null}
{
/* 开启单词对比 */}
{
i && isDiffWords ? <p style={
this.returnPStyle()} >{
i.split(':')[0]}:<span style={
{
backgroundColor: `${
item.added ? "rgb(221,255,221)" : "#fff"}`, color: item.added ? defaultAddColor :
item.removed ? defaultDelColor : defaultColor,
}}>{
i.split(':')[1]}</span></p> : null}
{
this.returBrOrNULL(idx, item)}
</Fragment>
})} </div>
</Fragment>
)
})
}
// 返回左侧domlist
returnLeftDomFun = () => {
let {
isOriginFlag, defaultAddColor, defaultDelColor, defaultColor, DiffLeft, leftSourceList } = this.state;
const {
isSplitBRFlag, isDiffWords } = this.props
let domList = isOriginFlag ? leftSourceList : DiffLeft;
return domList.map((item, index) => {
return (
<Fragment key={
index} >
<div
onClick={
(e) => {
this.changeRightLineFun("left", e) }}
style={
this.returnDivStyle(item)}>{
!item ? null : item.added ? (isSplitBRFlag ?
this.returnTransparentP(item) : null)
: item.value.split('\n').map((i, idx) => {
return <Fragment key={
idx} >
{
/* 默认 */}
{
i && !isDiffWords ? <p style={
{
backgroundColor: `${
item.removed ? "rgb(255,227,227)" : "#fff"}`, whiteSpace: "pre", cursor: 'pointer', ...styleObj }} >{
i}</p> : null}
{
/* 开启单词对比 */}
{
i && isDiffWords ? <p style={
this.returnPStyle()} >{
i.split(':')[0]}:<span style={
{
backgroundColor: `${
item.removed ? "rgb(255,227,227)" : "#fff"}`, color: item.added ? defaultAddColor :
item.removed ? defaultDelColor : defaultColor,
}}>{
i.split(':')[1]}</span></p> : null}
{
this.returBrOrNULL(idx, item)}
</Fragment>
})} </div>
</Fragment>
)
})
}
render() {
let {
isOriginFlag, defaultAddColor, defaultDelColor, defaultColor, contextBoxLoading, } = this.state
let {
title, width, isModalVisible, oldStr, newStr, fileName, isDiffWords, isSplitBRFlag, rightTitle, leftTitle, headerHeight, ...rest } = this.props
return !isModalVisible ? null : (
<Modal
title={
<div style={
{
display: "flex", justifyContent: "space-between" }}><span style={
{
fontWeight: "500" }}>{
title}</span><span style={
{
paddingLeft: "50px" }}>{
fileName ? `文件名称:${
fileName}` : ""}</span><h4 style={
{
float: "right" }}>灰色:<span style={
{
color: defaultColor }}>无变化</span>;绿色:<span style={
{
color: defaultAddColor }}>新增</span>;红色:<span style={
{
color: defaultDelColor }}>删除</span></h4></div>}
visible={
isModalVisible}
maskClosable={
true}
style={
{
top: "20%", minWidth: "1000px" }}
closable={
false}
width={
width}
{
...rest}
footer={
[
<Button Type="noIcon" key="1" title={
!isOriginFlag ? "查看源文件" : "查看对比文件"} onClick={
() => this.changeOriginFileFun()}></Button>,
// <Button Type="noIcon" key={1} title="切换对比方式" onClick={() => this.changeisSplitBRFlagFun()}></Button>,
<Button Type="noIcon" key="2" title="关闭" onClick={
() => this.handleCancel()}></Button>
]}
>
<Spin tip="请稍等,可能需要一些时间..." spinning={
contextBoxLoading} size="large" style={
{
width: "100%", height: '100%' }}>
{
/* 绑定 key 目前这个key有两种形式 false true 字符的形式 正好对应 查看源文件和对比文件两个key */}
<div style={
{
height: "600px", overflowY: "scroll", width: "100%" }} key={
String(isOriginFlag)}>
{
/* // 为了每次都处于文件顶端 */}
<div style={
{
wordBreak: "break-all", display: "flex", width: "100%" }} >
<div style={
{
width: "49%", paddingLeft: "20px" }} id="LeftFileId">
{
this.returnH2Fun(leftTitle)}
{
/* {Diff.diffLines(oldStr.replace(/\r/ig, ''), isOriginFlag ? oldStr.replace(/\r/ig, '') : newStr.replace(/\r/ig, '')) */}
{
this.returnLeftDomFun()}
</div>
<Divider type="vertical" style={
{
width: "1%", height: "auto", minHeight: '600px' }} />
<div style={
{
width: "49%", paddingLeft: "10px", paddingRight: "10px" }} id="RightFileId" >
{
this.returnH2Fun(rightTitle)}
{
/* {Diff.diffLines(isOriginFlag ? newStr.replace(/\r/ig, '') : oldStr.replace(/\r/ig, ''), newStr.replace(/\r/ig, '')) */}
{
this.returnRightDomFun()}
</div>
</div>
</div>
</Spin>
</Modal>
)
}
}
worker.js
/*
* @Descripttion:
* @version:
* @Author: ZhangJunQing
* @Date: 2022-04-09 14:20:17
* @LastEditors: ZhangJunQing
* @LastEditTime: 2022-04-15 15:57:37
*/
onmessage = function (e) {
//监听主进程的消息
const {
newStr, oldStr } = e.data
console.time("处理数据花费时间")
const DIFF = require('diff')
let DiffLeft = DIFF.diffLines(oldStr, newStr)
// 相同类型的字符串进行对比很快就可以完成 几乎忽略不计
let rightSourceList = DIFF.diffLines(newStr, newStr)
let leftSourceList = DIFF.diffLines(oldStr, oldStr)
console.timeEnd("处理数据花费时间")
console.log("Diffw Worker data already change finish!")
postMessage({
DiffLeft, rightSourceList, leftSourceList })//向主进程发送消息
}
目录结构:
使用diff组件
<DiffString
isModalVisible={
this.state.isModalVisible}
contextBoxLoading={
this.state.contextBoxLoading}
rightTitle={
'文件名:' + this.state.fileNameArr[0]}
leftTitle={
'文件名:' + this.state.fileNameArr[1]}
width={
"66%"}
style={
{
top: "10%" }}
oldStr={
this.state.oldStr}
newStr={
this.state.newStr}
headerHeight={
2}
changeIsModalVisibleState={
this.changeIsModalVisibleState}
/>
最终样式: