You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React项目自定义CKEditor 5出现getAttribute空引用错误如何修复?

问题描述

在React项目中替换CKEditor构建版本时出现错误:使用CKEditor 5在线构建工具生成包含SourceEditing插件的CustomEditor配置文件(config.ts),替代原本正常运行的@ckeditor/ckeditor5-build-classic中的ClassicEditor后,抛出TypeError: Cannot read properties of null (reading 'getAttribute')错误。

相关代码

config.ts

/**
 * @license Copyright (c) 2014-2023, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */

import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';

import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
import { CKBox } from '@ckeditor/ckeditor5-ckbox';
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
import type { EditorConfig } from '@ckeditor/ckeditor5-core';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { Heading } from '@ckeditor/ckeditor5-heading';
import {
    Image,
    ImageCaption,
    ImageStyle,
    ImageToolbar,
    ImageUpload,
    PictureEditing
} from '@ckeditor/ckeditor5-image';
import { Indent } from '@ckeditor/ckeditor5-indent';
import { Link } from '@ckeditor/ckeditor5-link';
import { List } from '@ckeditor/ckeditor5-list';
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
import { SourceEditing } from '@ckeditor/ckeditor5-source-editing';
import { Table, TableToolbar } from '@ckeditor/ckeditor5-table';
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
import { Undo } from '@ckeditor/ckeditor5-undo';

class CustomEditor extends ClassicEditor {
    public static override builtinPlugins = [
        Autoformat,
        BlockQuote,
        Bold,
        CKBox,
        CloudServices,
        Essentials,
        Heading,
        Image,
        ImageCaption,
        ImageStyle,
        ImageToolbar,
        ImageUpload,
        Indent,
        Italic,
        Link,
        List,
        MediaEmbed,
        Paragraph,
        PasteFromOffice,
        PictureEditing,
        SourceEditing,
        Table,
        TableToolbar,
        TextTransformation,
        Undo
    ];

    public static override defaultConfig: EditorConfig = {
        toolbar: {
            items: [
                'heading',
                '|',
                'bold',
                'italic',
                'link',
                'bulletedList',
                'numberedList',
                '|',
                'outdent',
                'indent',
                '|',
                'imageUpload',
                'blockQuote',
                'insertTable',
                'mediaEmbed',
                'undo',
                'redo',
                'sourceEditing'
            ]
        },
        language: 'en',
        image: {
            toolbar: [
                'imageTextAlternative',
                'toggleImageCaption',
                'imageStyle:inline',
                'imageStyle:block',
                'imageStyle:side'
            ]
        },
        table: {
            contentToolbar: [
                'tableColumn',
                'tableRow',
                'mergeTableCells'
            ]
        }
    };
}

export default CustomEditor;

TDEditorNew组件代码

import React from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";

import CustomEditor from "./config";

import axios from "axios";
import * as authHelper from "@/app/modules/auth/core/AuthHelpers";

import {
  requestGET,
  requestPOST_NEW,
  requestPUT_NEW,
  API_URL,
  FILE_URL,
} from "@/utils/baseAPI";
const uploadAdapter = (loader) => {
  const { token } = authHelper.getAuth();

  return {
    upload: () => {
      return new Promise(async (resolve, reject) => {
        try {
          const file = await loader.file;
          const response = await axios.request({
            method: "POST",
            url: `${API_URL}/api/v1/attachments/public`,
            data: {
              files: file,
            },
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: `Bearer ${token}`,
            },
          });
          resolve({
            default: `${FILE_URL}${response.data.data[0].url}`,
          });
        } catch (error) {
          console.log(error);
        }
      });
    },
    abort: () => {},
  };
};
function uploadPlugin(editor) {
  editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
    return uploadAdapter(loader);
  };
}
const TDEditorNew = ({ data, onChange }) => {
  return (
    <CKEditor
      editor={CustomEditor}
      config={{
        extraPlugins: [uploadPlugin],
      }}
      data={data}
      onChange={(event, editor) => {
        const data = editor.getData();
        onChange(data);
      }}
    />
  );
};

export default TDEditorNew;

组件使用代码

import TDEditorNew from '@/app/components/TDEditorNew';
...
<div className='col-xl-12 col-lg-12'>
                  <FormItem label='content' name='content'>
                    <TDEditorNew
                      data={form.getFieldValue('content')}
                      onChange={(newContent) => {
                        form.setFieldsValue({ content: newContent });
                      }}
                    />
                  </FormItem>
                </div>

错误栈信息

@ckeditor_ckeditor5-react.js?v=be2d4ad6:1084 TypeError: Cannot read properties of null (reading 'getAttribute')
    at _IconView._updateXMLContent (chunk-PUXRYV7X.js?v=be2d4ad6:2330:27)
    at _IconView.render (chunk-PUXRYV7X.js?v=be2d4ad6:2313:10)
    at _IconView.<anonymous> (chunk-AKXJUVNH.js?v=be2d4ad6:7658:37)
    at _IconView.fire (chunk-AKXJUVNH.js?v=be2d4ad6:7337:35)
    at <computed> [as render] (chunk-AKXJUVNH.js?v=be2d4ad6:7661:21)
    at ViewCollection._renderViewIntoCollectionParent (chunk-PUXRYV7X.js?v=be2d4ad6:1125:12)
    at ViewCollection.<anonymous> (chunk-PUXRYV7X.js?v=be2d4ad6:1019:12)
    at ViewCollection.fire (chunk-AKXJUVNH.js?v=be2d4ad6:7337:35)
    at ViewCollection.addMany (chunk-PUXRYV7X.js?v=be2d4ad6:9371:12)
    at ViewCollection.add (chunk-PUXRYV7X.js?v=be2d4ad6:9347:17) {phase: 'initialization', willEditorRestart: false}
原因分析

这个错误的根源是CKEditor的图标资源未正确加载或打包缺失。当自定义构建编辑器时,若插件依赖的SVG图标资源未被正确引入,IconView组件在尝试读取DOM元素的getAttribute方法时,会因为元素为null而抛出异常。常见诱因包括:

  • 项目中CKEditor各依赖包版本与在线构建工具选择的版本不一致
  • 在线构建生成的代码遗漏了图标相关的打包配置
  • 自定义编辑器配置中未正确包含图标处理相关的基础插件
修复方案

1. 统一CKEditor依赖版本

检查项目package.json中所有@ckeditor/ckeditor5-*开头的依赖版本,确保它们与在线构建工具中选择的版本完全一致。版本不匹配会导致资源引用冲突,引发图标加载失败。

2. 重新生成并替换自定义构建包

  • 打开CKEditor 5在线构建工具,选择ClassicEditor作为基础构建
  • 添加SourceEditing插件,确认所有插件的版本同步
  • 下载最新的构建包,替换项目中的config.ts文件,并根据构建包的提示更新package.json中的依赖版本

3. 确保图标资源正确打包

如果手动维护config.ts,需确认构建流程中包含了CKEditor的图标资源处理:

  • 确保构建工具(如Vite、Webpack)能正确解析SVG文件
  • 若使用在线构建工具,下载的包已默认包含图标资源,直接替换即可

4. 验证上传插件兼容性

确认自定义编辑器中已包含FileRepository插件(从你的代码看已包含,因为依赖ImageUpload),且上传插件的逻辑未干扰编辑器初始化流程。


内容的提问来源于stack exchange,提问作者Chung Truong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 21:57:05