Antd Upload + React-Cropper 实现图片自定义区域剪裁并上传功能

简介: 通过Upload组件结合react-Cropper实现图片的裁剪上传组件封装,剖析antd-img-crop源码实现的逻辑,对自己封装的组件进行进一步优化,改造!

Antd Upload + React-Cropper 实现图片自定义区域剪裁并上传功能

一.产生背景

最近做项目遇到一个功能是需要将图片进行剪裁后在上传,并且裁剪功能支持缩放,旋转以及自定义剪裁框的大小,最开始想到的是使用Ant Design中的antd-img-crop 进行图片的裁剪,但是在使用过程中发现,使用此组件裁剪图片不能够拖动改变裁剪框的大小,只能够固定某一种比例进行对图片的裁剪。在查阅了一下antd-img-crop相关文档发现这个组件是基于react-easy-crop进行二次封装实现的,当去查阅react-easy-crop相关文档在Issues中发现此插件并不支持自定义拖动改变裁剪框的大小,

fbd47e1b3094a09c128abdf93c21436c.png

这不得不让我放弃了上面这种方式。然而在多次查寻关于图片裁剪相关资料时,发现了react-Cropper库完美符合我的功能需求,他不仅仅满足antd-img-crop的绝大多数功能,还能够支持上面说的自定义拖动裁剪框(简直是太香了),详细查阅相关文档后,让我毫不犹豫的选择了它。

二.react-Cropper

介绍

react-Cropper是基于cropper.js库进行封装,里面支持所有cropper.js相关API。cropperjs是一款非常强大却又简单的图片裁剪工具,它可以进行非常灵活的配置,支持手机端使用,支持包括IE9以上的现代浏览器。可以自己选择裁剪的交互方式,如大小、纵横比等 还可以预览裁剪区域,确认裁剪后可以生成一个包含裁剪图的canvas对象,借助canvas的toDataURL方法可以生成一张Base64格式的图片。还有另外一种不使用canvas的方式,利用该工具丰富的api可以拿到裁剪区域相对于原图的各项数据,使用这些数据进行css绝对定位即可展示裁剪后的图,该方式可以保证图片不失真和完整。具体可查看:react-cropper

使用样例

官方使用demo如下:

importReact, { useRef } from"react";
importCropperfrom"react-cropper";
import"cropperjs/dist/cropper.css";
constDemo: React.FC= () => {
constcropperRef=useRef<HTMLImageElement>(null);
constonCrop= () => {
constimageElement: any=cropperRef?.current;
constcropper: any=imageElement?.cropper;
console.log(cropper.getCroppedCanvas().toDataURL());
  };
return (
<Croppersrc="https://raw.githubusercontent.com/roadmanfong/react-cropper/master/example/img/child.jpg"style={{ height: 400, width: "100%" }}
// Cropper.js optionsinitialAspectRatio={16/9}
guides={false}
crop={onCrop}
ref={cropperRef}
/>  );
};

常用API介绍

由于是基于cropper.js进行封装,所以支持所有相关API的使用,具体可以参考cropper.js文档,以下只列出比较常用的API属性用法以及相关作用

options

描述

取值

viewMode

视图模式

0: 没有限制 1: 限制裁剪框不超过画布的大小。 2:限制最小画布大小以适合容器。如果画布和容器的比例不同,则最小画布将被维度之一的额外空间包围。 3: 限制最小画布尺寸以填充适合容器。如果画布和容器的比例不同,容器将无法在其中一个维度中容纳整个画布。

dragMode

拖动模式

'crop': 创建一个新的裁剪框 'move': 移动画布 'none': 保持默认状态,不可拖动画布

src

图片地址

img地址

preview

是否启用预览

一个元素或一个元素数组或一个节点列表对象或一个有效的Document.querySelectorAll选择器

rotatable

是够可以启用画布旋转

Boolean false or true

initialAspectRatio

定义裁剪框的初始纵横比。默认情况下,它与画布(图像包装器)的纵横比相同。

Number or NAN

autoCropArea

定义自动裁剪区域大小(百分比)

Number 默认值0.8

onInitialized

获取裁剪框实例

(ref)=>ref

zoomTo

将画布(图像包装器)缩放到绝对比例。

Numer

minCropBoxWidth

裁剪框的最小宽度

Number

minCropBoxHeight

裁剪框的最小高度

Number

guides

是够显示裁剪框网格

Boolean false or true

三.实现思路

首先我们明确要使用antd 的Upload组件进行文件的上传以及处理上传前,中,后的一些事件以及图片的回显,所有我们必须要保证我们默认的上传流程不受影响的情况下加入图片剪裁逻辑。实现思路具体为,通过Upload上传组件获取到上传文件的文件信息,在其中的beforeUpload方法中能够拿到上传的文件信息,通过FileReader()方法将文件信息读取并且转换成URL,然后将生成的URL转交给图片剪切组件,此时在文件上传组件中需添加监听机制,监听裁剪图片是否完成,所以在beforeUpload方法中需要返回一个promise对象,里面启用一个事件循环去读取文件裁剪状态。当文件裁剪完成后,通过裁剪组件实例的getCroppedCanvas方法将数据转换成Blob对象 在通过File(),将Blob转换成file对象,再将获取到file对象转交给上传组件,完成后续的上传逻辑。实现方式总结下主要是对上传文件数据进行拦截,经过裁剪处理后再将数据放入主上传流程中。具体流程如下图所示:

0bc1d156691c3056db9f923f94b11274.png

四.具体实现

1.创建剪切组件

首先安装react-cropper依赖,根据官方文档进行安装

npminstall--savereact-cropper

根据官方实例创建cropper实例组件,由于我们是在上传文件时触发剪切,所有我们可以参照antd-img-crop的实现方式,将cropper组件放入Modal弹框中,在上传时触发弹框显示进行图片裁剪。

<><Upload        {...otherProps}
beforeUpload={handleBeforeUpload}
>        {children}
</Upload><Modalvisible={visible}
onCancel={handleOnCancel}
onOk={handleOk}
title={'裁剪图片'}
destroyOnClose><Cropperstyle={{ height: 400, width: '100%' }}
zoomTo={0} // 默认缩放src={imgSrc} // 原始图片路径dragMode="move"// 拖拽模式minCropBoxHeight={100} // 剪切框最小高度minCropBoxWidth={100} // 剪切框最小宽度rotatable// 启用旋转autoCropArea={1} // 默认裁剪框与画布比例checkOrientation={false} // 是否启用检查方向ref={cropperRef} // 获取裁剪组件实例guides// 显示裁剪框网格信息          {...cropperProps}
/><divclassName={styles.slider}><buttontype="button"className={styles.btn1} onClick={() => { handleClick('add'); }}></button><Slidervalue={rotate} min={-180} max={180} onChange={setRotate} /><buttontype="button"className={styles.btn2} onClick={() => { handleClick('duce'); }}></button></div></Modal></>

2.创建剪切后的File对象

在Modal弹框中的handleOk事件中获取剪切后的数据

const { type } =fileRef.current;
const { cropper } =cropperRef.current;
awaitcropper.getCroppedCanvas({
imageSmoothingQuality: 'high',
fillColor: 'transparent',
      }).toBlob((_blob) => {
imageBlob.current=_blob;
      }, type);

其中getCroppedCanvas(options)可选参数有如下:

height:输出画布的目标高度。  

minWidth: 输出画布的最小目标宽度,默认值为0。  

minHeight: 输出画布的最小目标高度,默认值为0。  

maxWidth: 输出画布的最大目标宽度,默认值为Infinity。  

maxHeight: 输出画布的最大目标高度,默认值为Infinity。  

fillColor:填充输出画布中任何 alpha 值的颜色,默认值为transparent.  

imageSmoothingEnabled:设置为更改图像是否平滑(true,默认)或不平滑(false)

imageSmoothingQuality:设置图像平滑的质量“低”(默认)、“中”或“高”之一

3.接管beforeUpload方法,监听剪切结果

上面我们说过我们需要在    beforeUpload方法中启用监听,所以我们需要将传进来的beforeUpload方法进行重写,赋予监听的能力

589997482a036fdd614fe1cd0d45231d.png

consthandleBeforeUpload= (file, fileList) => {
consttest=beforeUpload(file, fileList);
if (test) {
fileRef.current=file;
constreader=newFileReader();
reader.readAsDataURL(file);
reader.onload= (e) => {
setImgSrc(e.target.result);
setVisiable(true);
      };
returnnewPromise((resolve, reject) => {
timer=setInterval(() => { // 监听剪切是否完成if (imageBlob.current) {
window.clearInterval(timer);
letcroppedFile=null;
if (imageBlob.current!=='fail') {
croppedFile=newFile([imageBlob.current], file.name, {
type: file.type,
lastModified: Date.now(),
              });
croppedFile.uid=file.uid;
            }
imageBlob.current=null;
fileRef.current=null;
croppedFile?resolve(croppedFile) : reject();
          }
        }, 100);
      });
    }
returnfalse;
  };

4.增加图片旋转功能

通过获取cropper实例,调用rotateTo方法实现图片的旋转。在Modal添加旋转滑动条,切换事件等。

const [rotate, setRotate] =useState(0);
constsetCropperRotate= (_rotate) => {
const { cropper } =cropperRef?.current|| {};
if (cropper) {
cropper.rotateTo(_rotate);
    }
  };
consthandleClick= (type) => {
if (type==='add') {
const_rotate=rotate-10;
setRotate(_rotate<-180?-180 : _rotate);
    } else {
const_rotate=rotate+10;
setRotate(_rotate>180?180 : _rotate);
    }
  };
<divclassName={styles.slider}><buttontype="button"className={styles.btn1} onClick={() => { handleClick('add'); }}></button><Slidervalue={rotate} min={-180} max={180} onChange={setRotate} /><buttontype="button"className={styles.btn2} onClick={() => { handleClick('duce'); }}></button></div>

5.组件的使用

<CropUpLoadname="avatar"showUploadList={false}
accept=".png,.jpg,.jpeg"onChange={handleChange}
customRequest={handleRequest}
disabled={loading}
beforeUpload={handleBeforeCrop}
><divclassName={styles.uploadBtn} style={{ background: 'rgb(244, 117, 85)', color: '#fff' }}><PictureFilledstyle={{ fontSize: 14, top: -1, position: 'relative' }} /></div></CropUpLoad>

6.效果截图

8a6acf4c2d81844cdc12c7e4f0929439.png

103b2b2806592cd4ce8fed12cf78cf67.png

五.剖析antd-img-crop部分实现逻辑,源码,改造当前组件功能

1.antd-img-crop使用分析

首先我们看看官方给的例子用法

importImgCropfrom'antd-img-crop';
<ImgCroprotate><Uploadaction="https://www.mocky.io/v2/5cc8019d300000980a055e76"listType="picture-card"fileList={fileList}
onChange={onChange}
onPreview={onPreview}
>        {fileList.length<5&&'+ Upload'}
</Upload></ImgCrop

很明显的差别是antd-img-crop是用一个组件包裹Upload实现的 而非我们实现的直接将Upload封装到组件内部,对外暴露出原始的upload组件api。这样虽然是满足需求 但是最为一个业务组件的封装不能将其他的组件耦合在我们的组件内部。可是它是如何接管upload上传文件数据的呢,我们开始分析它内部实现的原理

2.源码分析

constgetUpload=useCallback(() => {
constupload=Array.isArray(children) ?children[0] : children;
const { beforeUpload, accept, ...restUploadProps } =upload.props;
beforeUploadRef.current=beforeUpload;
return {
...upload,
props: {
...restUploadProps,
accept: accept||'image/*',
beforeUpload: (file, fileList) => {
returnnewPromise(async (resolve, reject) => {
if (beforeCrop&&!(awaitbeforeCrop(file, fileList))) {
reject();
return;
            }
fileRef.current=file;
resolveRef.current= (newFile) => {
onModalOk?.(newFile);
resolve(newFile);
            };
rejectRef.current= (uploadErr) => {
onUploadFail?.(uploadErr);
reject(uploadErr);
            };
constreader=newFileReader();
reader.addEventListener('load', () =>setImage(reader.result));
reader.readAsDataURL(file);
          });
        },
      },
    };
  }, [beforeCrop, children, onModalOk, onUploadFail]);

以上为antd-img-crop组件接管beforeUpload的部分源码,从源码上面分析,首先通过children获取到了传递到组件中的Upload组件实例,从实例中解析beforeUpload,accept方法,然后重新构造了这个Upload组件 并且将其中的beforeUpload,以及其中的resolve,reject状态通过ref的方式存储起来,这里与我们封装的组件进行对比可以发现,我们是通过一个计时器循环来判断剪切是否完成,进儿进行相应的resolve与reject,然而它是将此方法存储起来 但拿到剪切后的图片数据时,直接调用ref中的方法进行数据的传递。相比我实现的计时器的方式,这种方式大大减少了程序的不必要开销。我们也可以仿照它的实现逻辑进行对本组件的改造。

constonBlob=async (blob) => {
letnewFile=newFile([blob], name, { type });
newFile.uid=uid;
if (typeofbeforeUploadRef.current!=='function') {
returnresolveRef.current(newFile);
      }
constres=beforeUploadRef.current(newFile, [newFile]);
if (typeofres!=='boolean'&&!res) {
console.error('beforeUpload must return a boolean or Promise');
return;
      }
if (res===true) returnresolveRef.current(newFile);
if (res===false) returnrejectRef.current('not upload');
if (res&&typeofres.then==='function') {
try {
constpassedFile=awaitres;
consttype=Object.prototype.toString.call(passedFile);
if (type==='[object File]'||type==='[object Blob]') newFile=passedFile;
resolveRef.current(newFile);
        } catch (err) {
rejectRef.current(err);
        }
      }
    };

通过onBlob方法可以发现,antd-img-crop并没有对原有的beforeUpload方法进行覆盖重写,而是对外重新暴露了一个beforeCrop方法,此方法返回了当前选择的文件对象,我们可以通过此方法先对需要剪切的图片进行第一次规则筛选,在剪切完成时调用onBlob方法时对原来的beforeUpload方法进执行,将剪切后生成的文件对象最为入参进行上传组件的图片筛选,这样做到了两个组件的独立,功能互不影响。在我们封装的上传组件中,我们是先执行了Upload的beforeUpload方法后拿到图片后再进行上传。而且并没有考虑到在beforeUpload方法中返回promise对象的情况,这样使得我们的组件有十分大的局限性。

3.组件改造

此次改造,主要是仿照antd-img-crop部分实现逻辑,针对上述源发分析产生的问题进行对组件的更近一步封装

  1. 改变组件定时器的方式获取剪切结果,改为Ref存储方法的方式。(增加getUpload方法,获取上传组件,接管上传文件对象)
constgetUpload=useCallback(() => {
constupload=Array.isArray(children) ?children[0] : children;
const { beforeUpload, accept, ...restUploadProps } =upload.props;
beforeUploadRef.current=beforeUpload;
return {
...upload,
props: {
...restUploadProps,
accept: accept||'image/*',
beforeUpload: (file, fileList) => {
returnnewPromise(async (resolve, reject) => {
if (beforeCrop&&!(awaitbeforeCrop(file, fileList))) {
reject();
return;
            }
fileRef.current=file;
resolveRef.current= (newFile) => {
resolve(newFile);
            };
rejectRef.current= (uploadErr) => {
reject(uploadErr);
            };
constreader=newFileReader();
reader.readAsDataURL(file);
reader.onload= (e) => {
setImgSrc(e.target.result);
setVisiable(true);
            };
          });
        },
      },
    };
  }, [children]);
  1. 对外暴露beforeCrop方法,对其进行裁剪弹框的图片校验,增加beforeUpload方法的调用,使其Upload组件独立。(改造 handleOk 方法,将剪切完成的file对象转交给beforeUpload方法执行Upload组件逻辑)
consthandleOk=async() => {
// TODO拿到截图的url,执行上传功能try {
const { type, name, uid } =fileRef.current;
const { cropper } =cropperRef.current;
awaitcropper.getCroppedCanvas({
imageSmoothingQuality: 'high',
fillColor: 'transparent',
      }).toBlob(async(_blob) => {
letcroppedFile=newFile([_blob], name, {
type,
lastModified: Date.now(),
        });
croppedFile.uid=uid;
if (typeofbeforeUploadRef.current!=='function') {
resolveRef.current(croppedFile);
        }
constres=beforeUploadRef.current(croppedFile, [croppedFile]);
if (typeofres!=='boolean'&&!res) {
return;
        }
if (res===true) resolveRef.current(croppedFile);
if (res===false) rejectRef.current();
if (res&&typeofres.then==='function') {
try {
constpassedFile=awaitres;
const_type=Object.prototype.toString.call(passedFile);
if (_type==='[object File]'||_type==='[object Blob]') croppedFile=passedFile;
resolveRef.current(croppedFile);
          } catch (err) {
rejectRef.current(err);
          }
        }
resolveRef.current(croppedFile);
      }, type);
    } catch {
message.error('裁剪失败');
rejectRef.current('裁剪失败');
    } finally {
setImgSrc('');
setVisiable(false);
setRotate(0);
    }
  };
  1. 组件调用方式
<CropUpLoadbeforeCrop={() => {
// TODO剪切图片选择校验returntrue;
}
><Uploadname="avatar"showUploadList={false}
accept=".png,.jpg,.jpeg"// eslint-disable-next-line no-undefonChange={handleChange}
customRequest={handleRequest}
disabled={loading}
beforeUpload={handleBeforeCrop}
><divclassName={styles.uploadBtn} style={{ background: 'rgb(244, 117, 85)', color: '#fff' }}><PictureFilledstyle={{ fontSize: 14, top: -1, position: 'relative' }} /></div></Upload></CropUpLoad>

六.归纳总结

本次图片裁剪上传组件封装,虽然满足了当前业务的需求,但是相比antd Upload官方的裁剪组件还相差甚远,只是实现了基本的功能需求,优化的地方还有很多。但是也让自己也有所收获,至少懂得了其实现的基本原理,在后续需满足其他场景下相信也能很快的对其进行优化。其实对于前端组件封装我自己的理解来说,还是要多读一些源码,在源码中我们能够很快的找出别人实现方式与自己所实现的方式之间的差异,在这种差异中去思考哪一种方式更加完美,让组件更加低耦合,可扩展性更强,做一个复用性较强的组件。最后大家在开发过程中,可以相互借鉴,减少大家的时间,同时,也极大减少了维护的成本。

七.知识点

1.useRef的使用

  返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。

返回的 ref 对象在组件的整个生命周期内保持不变 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里.

 可参考:React—useRef

2.useCallback,useMemo的使用

useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

 使用场景:

 有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

 useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

3.memo的作用

  如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

  React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

  可参考:memo

4.forwardRef

  引用传递(Ref forwading)是一种通过组件向子组件自动传递 引用ref 的技术。对于应用者的大多数组件来说没什么作用。但是对于有些重复使用的组件,可能有用。例如某些input组件,需要控制其focus,本来是可以使用ref来控制,但是因为该input已被包裹在组件中,这时就需要使用Ref forward来透过组件获得该input的引用。

例子:通过组件获得input的引用

importReact, { Component, createRef, forwardRef } from'react';
constFocusInput=forwardRef((props, ref) => (
<inputtype="text"ref={ref} />));
classForwardRefextendsComponent {
constructor(props) {
super(props);
this.ref=createRef();
    }
componentDidMount() {
const { current } =this.ref;
current.focus();
    }
render() {
return (
<div><p>forwardref</p><FocusInputref={this.ref} /></div>        );
    }
}
exportdefaultForwardRef;

5.Bolb对象

  一直以来,JS都没有比较好的可以直接处理二进制的方法。而Blob的存在,允许我们可以通过JS直接操作二进制数据。一个Blob对象就是一个包含有只读原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。Blob对象可以看做是存放二进制数据的容器,此外还可以通过Blob设置二进制数据的MIME类型。

  创建Blob对象有三种方式:

  1.   通过构造函数
varblob=newBlob(dataArr:Array<any>, opt:{type:string});
// dataArray:数组,包含了要添加到Blob对象中的数据,数据可以是任意多个ArrayBuffer,ArrayBufferView, Blob,//或者 DOMString对象。//opt:对象,用于设置Blob对象的属性(如:MIME类型)
  1.   通过Blob.slice()
Blob.slice(start:number, end:number, contentType:string)
//此方法返回一个新的Blob对象,包含了原Blob对象中指定范围内的数据
  1.   canvas.toBlob()
varcanvas=document.getElementById("canvas");
canvas.toBlob(function(blob){
console.log(blob);
});

  Blob对象作为一个装填二进制数据的基本对象,其作用也仅仅是一个容器,而真正的业务功能则需要通过FileReader、URL、Canvas等对象实现。

相关文章
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2
|
2月前
|
前端开发 JavaScript 网络架构
react对antd中Select组件二次封装
本文介绍了如何在React中对Ant Design(antd)的Select组件进行二次封装,包括创建MSelect组件、定义默认属性、渲染Select组件,并展示了如何使用Less进行样式定义和如何在项目中使用封装后的Select组件。
89 2
react对antd中Select组件二次封装
|
3月前
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
26天前
|
移动开发 前端开发 JavaScript
React DnD:实现拖拽功能的终极方案?
本文首发于微信公众号“前端徐徐”,介绍了一个强大的 React 拖拽库——React DnD。React DnD 帮助开发者轻松创建复杂的拖拽界面,适用于 Trello 风格的应用、列表重排序、可拖拽的 UI 组件等场景。文章详细介绍了 React DnD 的基本信息、主要特点、使用场景及快速上手指南。
65 3
React DnD:实现拖拽功能的终极方案?
|
2月前
|
前端开发
React给antd中TreeSelect组件左侧加自定义图标icon
本文介绍了如何在React中为Ant Design的TreeSelect组件的每个树节点添加自定义图标,并解决了因缺少key属性而导致的警告问题,展示了如何通过递归函数处理treeData数据并为每个节点添加图标。
115 2
React给antd中TreeSelect组件左侧加自定义图标icon
|
2月前
|
移动开发 前端开发
react项目配合diff实现文件对比差异功能
在React项目中,可以使用`diff`库实现文件内容对比差异功能。首先安装`diff`库,然后在组件中引入并使用`Diff.diffChars`或`Diff.diffLines`方法比较文本差异。通过循环遍历`diff`结果,可以生成不同样式的HTML元素来高亮显示文本差异。
115 1
react项目配合diff实现文件对比差异功能
|
2月前
|
前端开发
react使用antd中的Checkbox实现多选
在React项目中,通过Ant Design的Checkbox组件实现多选。引入Checkbox,使用Checkbox.Group来管理Checkbox,设置`value`属性绑定选中项数组,通过`onChange`更新数组。维护一个全选状态,根据选中项数量与总数决定全选按钮状态。全选按钮的`onChange`事件用于控制所有Checkbox的选中状态。
88 1
react使用antd中的Checkbox实现多选
|
2月前
|
前端开发
React添加路径别名alias、接受props默认值、并二次封装antd中Modal组件与使用
本文介绍了在React项目中如何添加路径别名alias以简化模块引入路径,设置组件props的默认值,以及如何二次封装Ant Design的Modal组件。文章还提供了具体的代码示例,包括配置Webpack的alias、设置defaultProps以及封装Modal组件的步骤和方法。
68 1
React添加路径别名alias、接受props默认值、并二次封装antd中Modal组件与使用
|
2月前
|
前端开发 JavaScript 区块链
react18函数组件+antd使用指南-使用代码集合以及报错记录汇总
本文介绍了多个React开发中常见的问题及其解决方案,包括但不限于:1)`useForm`实例未连接到任何`Form`元素的警告及解决方法;2)监听页面滚动事件的实现方式;3)React 18与antd 5.8.6中定制主题的方法;4)React结合antd 4.x版本自定义主题色的步骤;5)解决`ResizeObserver loop`相关报错的技巧;6)处理React设计表单时遇到的CDN资源加载失败问题;7)解决onClick事件传参问题;8)修复类型错误等。每部分均提供详细分析与实用代码示例,帮助开发者快速定位并解决问题。
46 2
|
2月前
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
54 4
React技术栈-React路由插件之自定义组件标签