React封裝UEditor富文本編輯器的實現(xiàn)步驟
UEditor 作為經(jīng)典富文本編輯器,在后臺系統(tǒng)中仍有廣泛應(yīng)用,但原生 UEditor 與 React 生態(tài)適配性差。本文剝離業(yè)務(wù)接口和冗余配置,聚焦核心封裝邏輯,拆解從實例管理到圖片處理的關(guān)鍵實現(xiàn)思路。
一、基礎(chǔ)架構(gòu):核心設(shè)計思路
1. 類型與狀態(tài)管理(核心代碼片段)
import React, { useEffect, useRef, forwardRef, useImperativeHandle, useCallback } from 'react';
// 核心狀態(tài)管理(非響應(yīng)式狀態(tài)用useRef避免重渲染)
const UEditor = forwardRef((props, ref) => {
const editorContainer = useRef<HTMLDivElement>(null); // 編輯器容器
const ueInstance = useRef<any>(null); // UEditor實例
const isPlaceholderActive = useRef(false); // 占位符狀態(tài)
const isUploading = useRef(false); // 上傳狀態(tài)標(biāo)記
// 解構(gòu)核心props(僅保留通用配置)
const {
value = '',
onChange,
style,
placeholder = '請輸入內(nèi)容...',
onBlur
} = props;
// 對外暴露核心方法(關(guān)鍵:讓父組件能操作編輯器)
useImperativeHandle(ref, () => ({
getContent: () => {
const content = ueInstance.current?.getContent() || '';
return isPlaceholderActive.current ? '' : content;
},
setContent: (content: string) => {
// 內(nèi)容設(shè)置邏輯(后文詳解)
},
forceImageFixedSize: () => {
// 圖片尺寸修正邏輯(后文詳解)
}
}));
});關(guān)鍵設(shè)計點:
- 用
forwardRef+useImperativeHandle暴露編輯器核心方法,滿足父組件自定義操作需求; - 用
useRef管理 UEditor 實例、狀態(tài)標(biāo)記等非響應(yīng)式數(shù)據(jù),避免狀態(tài)變化觸發(fā)組件重渲染; - 僅保留通用 Props,剝離業(yè)務(wù)化配置,保證組件通用性。
二、核心功能:關(guān)鍵痛點解決
1. 實例生命周期管理(避免內(nèi)存泄漏)
// 動態(tài)加載UEditor腳本(核心:按需加載,避免重復(fù)加載)
const loadScript = useCallback((src: string) => {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) return resolve(true);
const script = document.createElement('script');
script.src = src;
script.onload = () => resolve(true);
script.onerror = () => reject(new Error(`加載失敗: ${src}`));
document.body.appendChild(script);
});
}, []);
// 安全銷毀編輯器(關(guān)鍵:卸載時徹底清理)
const safeDestroyEditor = useCallback(() => {
if (!ueInstance.current) return;
try {
// 移除所有事件監(jiān)聽器
const listeners = ['contentChange', 'blur', 'afterUpload'];
listeners.forEach(event => {
ueInstance.current.removeAllListeners?.(event);
});
// 從UEditor全局實例中移除
window.UE?.delEditor(ueInstance.current.key);
ueInstance.current = null;
} catch (error) {
console.warn('UEditor銷毀失敗:', error);
}
}, []);
// 生命周期綁定(核心:組件掛載初始化,卸載銷毀)
useEffect(() => {
// 初始化邏輯(后文詳解)
initEditor();
return () => safeDestroyEditor();
}, [initEditor, safeDestroyEditor]);關(guān)鍵要點:
- 動態(tài)加載腳本:避免 UEditor 資源全局引入,按需加載且防止重復(fù)加載;
- 銷毀邏輯:不僅銷毀實例,還要移除所有事件監(jiān)聽器,徹底避免內(nèi)存泄漏;
- 生命周期綁定:通過
useEffect將實例創(chuàng)建 / 銷毀與組件生命周期嚴(yán)格同步。
2. 圖片樣式控制(解決尺寸混亂問題)
// 圖片尺寸統(tǒng)一修正(核心:清除原生樣式,應(yīng)用響應(yīng)式規(guī)則)
const safeImageSizeFix = useCallback((force = false) => {
if (!ueInstance.current) return;
try {
const doc = ueInstance.current.document || document;
const imgElements = doc.body.querySelectorAll('img');
imgElements.forEach((img: HTMLImageElement) => {
// 清除UEditor原生添加的寬高樣式/屬性
img.style.width = '';
img.style.height = '';
img.removeAttribute('width');
img.removeAttribute('height');
// 應(yīng)用統(tǒng)一的響應(yīng)式樣式
img.style.maxWidth = '100%';
img.style.height = 'auto';
});
} catch (error) {
console.warn('圖片尺寸修正失敗:', error);
}
}, []);
// 監(jiān)聽圖片插入事件(核心:插入/上傳后自動修正)
const setupImageHandlers = useCallback(() => {
if (!ueInstance.current) return;
// 圖片插入/上傳后觸發(fā)修正
ueInstance.current.addListener('afterInsertImage', () => {
setTimeout(() => safeImageSizeFix(true), 100);
});
// 覆蓋原生插入方法,確保樣式生效
const originalInsertImage = ueInstance.current.insertImage;
ueInstance.current.insertImage = function (...args) {
const result = originalInsertImage.call(this, ...args);
setTimeout(() => safeImageSizeFix(true), 50);
return result;
};
}, [safeImageSizeFix]);核心解決思路:
- 清除原生樣式:UEditor 插入圖片會自動添加寬高屬性 / 樣式,需徹底清除;
- 響應(yīng)式樣式:統(tǒng)一設(shè)置
maxWidth: 100%+height: auto,保證圖片適配容器; - 事件監(jiān)聽:圖片插入 / 上傳后自動觸發(fā)修正,覆蓋原生方法確保無遺漏。
3. 占位符功能(解決上傳沖突)
// 設(shè)置占位符(核心:自定義樣式,標(biāo)記占位狀態(tài))
const setPlaceholderText = useCallback(() => {
if (!ueInstance.current) return;
const placeholderHtml = `<p style="color: #999; font-style: italic;">${placeholder}</p>`;
ueInstance.current.setContent(placeholderHtml);
isPlaceholderActive.current = true;
onChange?.('');
}, [placeholder, onChange]);
// 上傳前激活編輯器(關(guān)鍵:避免占位符干擾上傳)
const activateEditorForUpload = useCallback(() => {
if (!ueInstance.current || !isPlaceholderActive.current) return;
// 清除占位符,插入零寬空格(有內(nèi)容但不顯示)
ueInstance.current.setContent('<p>​</p>');
isPlaceholderActive.current = false;
isUploading.current = true;
// 超時重置上傳狀態(tài)(安全措施)
setTimeout(() => {
isUploading.current = false;
}, 3000);
}, [placeholder]);
// 焦點/失焦處理(核心:區(qū)分上傳狀態(tài),避免沖突)
const setupPlaceholderHandlers = useCallback(() => {
if (!ueInstance.current) return;
// 聚焦時清除占位符(非上傳場景)
ueInstance.current.addListener('focus', () => {
if (!isUploading.current && isPlaceholderActive.current) {
ueInstance.current.setContent('');
isPlaceholderActive.current = false;
}
});
// 失焦時顯示占位符(非上傳場景)
ueInstance.current.addListener('blur', () => {
if (isUploading.current) return;
const content = ueInstance.current.getContent();
if (!content || content === '<p><br></p>') {
setPlaceholderText();
}
onBlur?.();
});
}, [setPlaceholderText, onBlur]);核心痛點解決:
- 上傳沖突:占位符狀態(tài)下編輯器無實際內(nèi)容,上傳會失敗,需在上傳前插入零寬空格;
- 狀態(tài)區(qū)分:標(biāo)記上傳狀態(tài),焦點 / 失焦邏輯中跳過占位符處理,避免干擾上傳;
- 體驗優(yōu)化:占位符樣式自定義,失焦時自動顯示,聚焦時自動清除。
4. 內(nèi)容同步(React 式狀態(tài)管理)
// 初始化編輯器(核心:內(nèi)容初始化+事件監(jiān)聽)
const initEditor = useCallback(async () => {
if (!editorContainer.current) return;
// 加載UEditor核心腳本(ueditor.config.js/ueditor.all.min.js等)
await loadScript('/UEditor/ueditor.config.js');
await loadScript('/UEditor/ueditor.all.min.js');
// 創(chuàng)建UEditor實例
ueInstance.current = window.UE.getEditor(editorContainer.current.id, {
initialFrameHeight: 240,
autoHeightEnabled: false,
enableAutoSave: false // 禁用自動保存,避免與React狀態(tài)沖突
});
// 編輯器就緒后初始化內(nèi)容和事件
ueInstance.current.addListener('ready', () => {
// 初始化內(nèi)容:有值則設(shè)置,無值則顯示占位符
if (value) {
ueInstance.current.setContent(value);
} else {
setPlaceholderText();
}
// 監(jiān)聽內(nèi)容變化,同步到React狀態(tài)
ueInstance.current.addListener('contentChange', () => {
if (isPlaceholderActive.current || isUploading.current) return;
const content = ueInstance.current.getContent();
onChange?.(content);
});
// 初始化圖片處理、占位符、上傳等邏輯
setupImageHandlers();
setupPlaceholderHandlers();
});
}, [loadScript, value, onChange, setPlaceholderText]);
// 監(jiān)聽外部value變化(核心:同步父組件狀態(tài)到編輯器)
useEffect(() => {
if (!ueInstance.current?.isReady) return;
const currentContent = ueInstance.current.getContent();
if (value !== currentContent && !isPlaceholderActive.current) {
ueInstance.current.setContent(value);
// 內(nèi)容變化后修正圖片尺寸
setTimeout(() => safeImageSizeFix(true), 50);
}
}, [value, safeImageSizeFix]);核心同步邏輯:
- 編輯器→React:監(jiān)聽
contentChange事件,過濾占位符 / 上傳狀態(tài),同步內(nèi)容到父組件; - React→編輯器:監(jiān)聽
value變化,實時更新編輯器內(nèi)容,同時修正圖片尺寸; - 初始化:編輯器就緒后根據(jù)
value初始化內(nèi)容,保證初始狀態(tài)一致。
三、封裝總結(jié):核心思路提煉
本次封裝并非重寫 UEditor,而是適配 React 生態(tài)規(guī)則,核心思路可總結(jié)為:
- 生命周期同步:實例創(chuàng)建 / 銷毀與組件掛載 / 卸載綁定,避免內(nèi)存泄漏;
- 狀態(tài)隔離:非響應(yīng)式狀態(tài)用
useRef管理,響應(yīng)式狀態(tài)通過props/onChange同步; - 痛點針對性解決:
- 圖片樣式:清除原生樣式 + 統(tǒng)一響應(yīng)式規(guī)則 + 事件監(jiān)聽自動修正;
- 占位符:上傳前激活編輯器,區(qū)分上傳狀態(tài)避免沖突;
- 內(nèi)容同步:雙向綁定,兼顧 React 狀態(tài)和 UEditor 原生內(nèi)容;
- 擴(kuò)展與通用:暴露核心方法,剝離業(yè)務(wù)配置,保證組件復(fù)用性。
該思路同樣適用于其他原生 JS 編輯器(如 CKEditor、KindEditor)向 React 的適配,核心是遵循 React 的開發(fā)范式,將原生庫能力封裝為符合 React 習(xí)慣的組件。
到此這篇關(guān)于React封裝UEditor富文本編輯器的實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)React封裝UEditor 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React中實現(xiàn)編輯框自動獲取焦點與失焦更新功能
在React應(yīng)用中,編輯框的焦點控制和數(shù)據(jù)回填是一個常見需求,本文將介紹如何使用useRef和useEffect鉤子,在組件中實現(xiàn)輸入框自動獲取焦點及失焦后更新數(shù)據(jù)的功能,文中通過代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下2024-01-01
解決antd的Table組件使用rowSelection屬性實現(xiàn)多選時遇到的bug
這篇文章主要介紹了解決antd的Table組件使用rowSelection屬性實現(xiàn)多選時遇到的bug問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
TypeScript在React項目中的使用實踐總結(jié)
這篇文章主要介紹了TypeScript在React項目中的使用總結(jié),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
react性能優(yōu)化useMemo與useCallback使用對比詳解
這篇文章主要為大家介紹了react性能優(yōu)化useMemo與useCallback使用對比詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
react國際化化插件react-i18n-auto使用詳解
這篇文章主要介紹了react國際化化插件react-i18n-auto使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
React Router 中實現(xiàn)嵌套路由和動態(tài)路由的示例
React Router 是一個非常強(qiáng)大和靈活的路由庫,它為 React 應(yīng)用程序提供了豐富的導(dǎo)航和 URL 管理功能,能夠幫助我們構(gòu)建復(fù)雜的單頁應(yīng)用和多頁應(yīng)用,這篇文章主要介紹了React Router 中如何實現(xiàn)嵌套路由和動態(tài)路由,需要的朋友可以參考下2023-05-05

