Contribute to this guide

guideReact 富文本编辑器组件

npm version

React 允许您使用称为组件的各个部分构建用户界面。CKEditor 5 可以用作此类组件之一。

从该软件包的 6.0.0 版本开始,您可以使用 CKEditor 5 提供的本机类型定义。查看有关 TypeScript 支持 的详细信息。

CKEditor 5 生成器

在我们的交互式生成器中,您可以快速体验 CKEditor 5。它提供了一个易于使用的用户界面,可以帮助您配置、预览和下载适合您需求的编辑器。您可以轻松地选择

  • 编辑器类型。
  • 您需要的功能。
  • 首选框架(React、Angular、Vue 或 Vanilla JS)。
  • 首选分发方法。

最后,您将获得适合您需求的即用型代码!

查看我们的交互式生成器

# 快速开始

# 设置项目

本指南假设您有一个 React 项目。您可以使用 Vite 创建一个基本的 React 项目。请参考 React 文档 了解如何在框架中设置项目。

# 从 npm 安装

首先,安装 CKEditor 5 软件包

  • ckeditor5 – 包含开源插件和功能的软件包。
  • ckeditor5-premium-features – 包含高级插件和功能的软件包。

根据您的配置和选定的插件,您可能需要安装第一个软件包或两个软件包。

npm install ckeditor5 ckeditor5-premium-features

然后,安装 用于 React 的 CKEditor 5 WYSIWYG 编辑器组件

npm install @ckeditor/ckeditor5-react

在您的项目中使用 <CKEditor> 组件。以下示例展示了如何在组件中使用开源和高级插件。

import { CKEditor } from '@ckeditor/ckeditor5-react';
import { ClassicEditor, Bold, Essentials, Italic, Mention, Paragraph, Undo } from 'ckeditor5';
import { SlashCommand } from 'ckeditor5-premium-features';

import 'ckeditor5/ckeditor5.css';
import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

function App() {
    return (
        <CKEditor
            editor={ ClassicEditor }
            config={ {
                toolbar: {
                    items: [ 'undo', 'redo', '|', 'bold', 'italic' ],
                },
                plugins: [
                    Bold, Essentials, Italic, Mention, Paragraph, SlashCommand, Undo
                ],
                licenseKey: '<YOUR_LICENSE_KEY>',
                mention: {
                    // Mention configuration
                },
                initialData: '<p>Hello from CKEditor 5 in React!</p>',
            } }
        />
    );
}

export default App;

请记住导入必要的样式表。ckeditor5 软件包包含开源功能的样式,而 ckeditor5-premium-features 软件包包含高级功能样式。

# 组件属性

<CKEditor> 组件支持以下属性

  • editor (必填)– 要使用的 Editor 构造函数。
  • data – 创建的编辑器的初始数据。请参阅 获取和设置数据 指南。
  • config – 编辑器配置。请参阅 配置 指南。
  • id – 编辑器 ID。当此属性更改时,组件将使用新数据重新启动编辑器,而不是将其设置在已初始化的编辑器上。
  • disabled – 布尔值。如果属性设置为 trueeditor 将切换到只读模式。
  • disableWatchdog – 布尔值。如果设置为 true,则将禁用 看门狗功能。默认情况下设置为 false
  • watchdogConfig用于 看门狗功能 的配置对象。
  • onReady – 当编辑器准备好具有 editor 实例时调用的函数。如果发生错误,此回调也会在组件重新初始化后调用。
  • onAfterDestroy – 在组件呈现的编辑器实例成功销毁后调用的函数。如果编辑器在错误后重新初始化,此回调也会触发。组件在调用此函数时不保证已挂载。
  • onChange – 当编辑器数据发生更改时调用的函数。请参阅 editor.model.document#change:data 事件。
  • onBlur – 当编辑器失去焦点时调用的函数。请参阅 editor.editing.view.document#blur 事件。
  • onFocus – 当编辑器获得焦点时调用的函数。请参阅 editor.editing.view.document#focus 事件。
  • onError – 当编辑器在初始化期间或运行时崩溃时调用的函数。它接收两个参数:错误实例和错误详细信息。错误详细信息是一个包含两个属性的对象
    • {String} phase: 'initialization'|'runtime' – 通知错误发生的时间(在编辑器或上下文初始化期间,或初始化后)。
    • {Boolean} willEditorRestart – 当为 true 时,表示编辑器组件将重新启动自身。

编辑器事件回调(onChangeonBluronFocus)接收两个参数

  1. 一个 EventInfo 对象。
  2. 一个 Editor 实例。

# 上下文功能

@ckeditor/ckeditor5-react 软件包为 上下文功能 提供了一个即用型组件,该功能在与一些 CKEditor 5 协作功能 一起使用时非常有用。

import { ClassicEditor, Context, Bold, Essentials, Italic, Paragraph, ContextWatchdog } from 'ckeditor5';
import { CKEditor, CKEditorContext } from '@ckeditor/ckeditor5-react';

import 'ckeditor5/ckeditor5.css';

function App() {
  return (
    <CKEditorContext
        context={ Context }
        contextWatchdog={ ContextWatchdog }
        onChangeInitializedEditors={ ( editors ) => {
            console.info( editors.editor1?.instance, editors.editor1?.yourAdditionalData );
        } }
    >
      <CKEditor
        editor={ ClassicEditor }
        config={ {
          plugins: [ Essentials, Bold, Italic, Paragraph ],
          toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
        } }
        data='<p>Hello from the first editor working with the context!</p>'
        contextItemMetadata={{
            name: 'editor1',
            yourAdditionalData: 2
        }}
        onReady={ ( editor ) => {
          // You can store the "editor" and use when it is needed.
          console.log( 'Editor 1 is ready to use!', editor );
        } }
      />

      <CKEditor
        editor={ ClassicEditor }
        config={ {
          plugins: [ Essentials, Bold, Italic, Paragraph ],
          toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
        } }
        data='<p>Hello from the second editor working with the context!</p>'
        onReady={ ( editor ) => {
          // You can store the "editor" and use when it is needed.
          console.log( 'Editor 2 is ready to use!', editor );
        } }
      />
    </CKEditorContext>
  );
}

export default App;

CKEditorContext 组件支持以下属性

  • context (必填)– CKEditor 5 上下文类
  • contextWatchdog (必填)– Watchdog 上下文类
  • config – CKEditor 5 上下文配置。
  • isLayoutReady – 当设置为 false 时延迟上下文创建的属性。一旦它为 true 或未设置,它将创建上下文和编辑器子项。当使用 CKEditor 5 注释或存在列表时很有用。
  • id – 上下文 ID。当此属性更改时,组件将使用其编辑器重新启动上下文,并根据当前配置对其进行重新初始化。
  • onChangeInitializedEditors – 当树中的任何编辑器初始化或销毁时调用的函数。它接收一个完全初始化的编辑器字典,其中键是在 CKEditor 组件上设置的 contextItemMetadata.name 属性的值。如果 contextItemMetadata 属性不存在,则编辑器的 ID 是键。可以在 CKEditor 组件中向 contextItemMetadata 添加其他数据,这些数据将传递给 onChangeInitializedEditors 函数。
  • onReady – 当上下文初始化但在树中的编辑器设置之前调用的函数。执行此函数后,您可以使用 context.editors.on('change', () => {}) 方法跟踪上下文树中的添加和删除。
  • onError – 当上下文在初始化期间或运行时崩溃时调用的函数。它接收两个参数:错误实例和错误详细信息。错误详细信息是一个包含两个属性的对象
    • {String} phase: 'initialization'|'runtime' – 通知错误发生的时间(在编辑器或上下文初始化期间,或初始化后)。
    • {Boolean} willContextRestart – 当为 true 时,表示上下文组件将重新启动自身。

可以在 CKEditor 5 协作示例 中找到公开上下文和经典编辑器的示例构建。

# 如何?

# 使用文档编辑器类型

如果您使用的是 文档(分离)编辑器,则需要 手动将工具栏添加到 DOM

import { useEffect, useRef, useState } from 'react';
import { DecoupledEditor, Bold, Essentials, Italic, Paragraph } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';

import 'ckeditor5/ckeditor5.css';

function App() {
    const editorToolbarRef = useRef( null );
    const [ isMounted, setMounted ] = useState( false );

    useEffect( () => {
        setMounted( true );

        return () => {
            setMounted( false );
        };
    }, [] );

    return (
        <div>
            <div ref={ editorToolbarRef }></div>
            <div>
                { isMounted && (
                    <CKEditor
                        editor={ DecoupledEditor }
                        data='<p>Hello from CKEditor 5 decoupled editor!</p>'
                        config={ {
                            plugins: [ Bold, Italic, Paragraph, Essentials ],
                            toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ]
                        } }
                        onReady={ ( editor ) => {
                            if ( editorToolbarRef.current ) {
                                editorToolbarRef.current.appendChild( editor.ui.view.toolbar.element );
                            }
                        }}
                        onAfterDestroy={ ( editor ) => {
                            if ( editorToolbarRef.current ) {
                                Array.from( editorToolbarRef.current.children ).forEach( child => child.remove() );
                            }
                        }}
                    />
                ) }
            </div>
        </div>
    );
}

export default App;

# 将编辑器与协作插件一起使用

我们提供了一些 可立即使用的集成,这些集成在 React 应用程序中提供协作编辑功能

在上述示例之上构建应用程序并非强制性,但它们应该可以帮助您入门。

# 本地化

CKEditor 5 支持 多种 UI 语言,官方 React 组件也支持。请按照以下说明在您的 React 应用程序中翻译 CKEditor 5。

与 CSS 样式表类似,这两个软件包都有单独的翻译。如以下示例所示导入它们。然后,将它们传递到 CKEditor 5 组件的 config 属性中的 translations 数组中。

import { ClassicEditor } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';
// More imports...

import coreTranslations from 'ckeditor5/translations/es.js';
import premiumFeaturesTranslations from 'ckeditor5-premium-features/translations/es.js';

// Style sheets imports...

function App() {
    return (
        <CKEditor
            editor={ ClassicEditor }
            config={ {
                translations: [ coreTranslations, premiumFeaturesTranslations ],
                initialData: '<p>Hola desde CKEditor 5 en React!</p>',
            } }
        />
    );
}

export default App;

有关更多信息,请参考 设置 UI 语言 指南。

# Jest 测试

Jest 是许多 React 应用程序使用的默认测试运行器。不幸的是,Jest 不会使用真正的浏览器。相反,它在使用 JSDOM 的 Node.js 中运行测试。JSDOM 不是完整的 DOM 实现,虽然它足以用于标准应用程序,但它无法模拟 CKEditor 5 所需的所有 DOM API。

为了测试 CKEditor 5,建议使用利用真实浏览器并提供完整 DOM 实现的测试框架。一些流行的选择包括

这些框架为测试 CKEditor 5 提供了更好的支持,并更准确地反映了编辑器在真实浏览器环境中的行为。

如果不可能,并且您仍然想使用 Jest,您可以模拟一些必需的 API。以下是模拟 CKEditor 5 使用的一些 API 的示例

import React, { useRef } from 'react';
import { render, waitFor, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';

import { DecoupledEditor, Essentials, Paragraph } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';

beforeAll( () => {
    window.scrollTo = jest.fn();

    window.ResizeObserver = class ResizeObserver {
        observe() {}
        unobserve() {}
        disconnect() {}
    };

    for ( const key of [ 'InputEvent', 'KeyboardEvent' ] ) {
        window[ key ].prototype.getTargetRanges = () => {
            const range = new StaticRange( {
                startContainer: document.body.querySelector( '.ck-editor__editable p' ),
                startOffset: 0,
                endContainer: document.body.querySelector( '.ck-editor__editable p' ),
                endOffset: 0
            } );

            return [ range ];
        };
    }

    Range.prototype.getClientRects = () => ( {
        item: () => null,
        length: 0,
        [ Symbol.iterator ]: function* () {}
    } );
} );

const SomeComponent = ( { value, onChange } ) => {
    const editorRef = useRef();

    return (
        <div
            style={{
                border: '1px solid black',
                padding: 10,
            }}
        >
            <CKEditor
                editor={ DecoupledEditor }
                config={{
                    plugins: [ Essentials, Paragraph ],
                }}
                onReady={ (editor) => {
                    editorRef.current = editor;
                } }
                data={ value }
                onChange={ () => {
                    onChange( editorRef.current?.getData() );
                } }
            />
        </div>
    );
};

it( 'renders', async () => {
    render( <SomeComponent value="this is some content" /> );

    await waitFor( () => expect( screen.getByText( /some content/ ) ).toBeTruthy());
} );

it( 'updates', async () => {
    const onChange = jest.fn();
    render( <SomeComponent value="this is some content" onChange={onChange} /> );

    await waitFor( () => expect( screen.getByText( /some content/ ) ).toBeTruthy() );

    await userEvent.click( document.querySelector( '[contenteditable="true"]' ) );

    userEvent.keyboard( 'more stuff' );

    await waitFor( () => expect( onChange ).toHaveBeenCalled() );
} );

上面显示的模拟只测试了两个基本场景,可能需要添加更多场景,这些场景可能会随着每个编辑器版本的发布而改变。

# 贡献和报告问题

用于 React 的富文本编辑器组件的源代码在 GitHub 上的 https://github.com/ckeditor/ckeditor5-react 中提供。