React 表单示例
SoEditor 1.3.0
为网站 CMS 而生
让内容编辑回归简单
一篇文章、一张产品介绍,或下一次发布。用熟悉的工具,把内容写好。
WYSIWYGHTML SourceCMS
点击后加载 · 内容仅保留在当前页面
安装与运行
/cms 组件从 1.3.0 起正式发布;原有 useSoEditorWorkspace 入口继续兼容。
sh
pnpm add @soeditor/editor@1.3.0 @soeditor/react@1.3.0 react react-dom点击示例的“开始体验”运行已发布的软件包。下载完整示例源码,解压后执行 pnpm install 和 pnpm dev,打开 react.html。
体验步骤
- 编辑文章文字,查看下面的绑定 HTML 是否同步。
- 点击“替换 HTML”,确认外部状态更新到编辑器;切换只读后不能继续编辑。
- 点击“提交表单”查看 textarea 的规范 HTML;“重置表单”恢复本次挂载的初始值。
- 点击“HTML Source”观察首次加载;“重建示例”用当前绑定值创建新实例。结束体验会移除整个 iframe。
完整示例源码
ts
import { createElement as h, StrictMode, useRef, useState } from 'react';
import type { FormEvent } from 'react';
import { createRoot } from 'react-dom/client';
import { SoEditor } from '@soeditor/react/cms';
import type { ClassicEditor } from '@soeditor/editor/cms';
import { createEditor, initial, label, options } from './shared.js';
function ArticleForm() {
const [html, setHtml] = useState(initial);
const [readonly, setReadonly] = useState(false);
const [version, setVersion] = useState(0);
const [output, setOutput] = useState('');
const [status, setStatus] = useState(label('正在加载', 'Loading'));
const editor = useRef<ClassicEditor | undefined>(undefined);
const button = (zh: string, en: string, onClick: () => void) =>
h('button', { type: 'button', onClick }, label(zh, en));
return h(
'form',
{
onSubmit: (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setOutput(
String(new FormData(event.currentTarget).get('body')),
);
},
},
h(
'p',
{ className: 'notice' },
label(
'React 组件预览:内容仅保存在本页。',
'React component preview: content stays in this page.',
),
),
h(
'div',
{ className: 'controls' },
button('替换 HTML', 'Replace HTML', () =>
setHtml('<h2>Updated article</h2><p>External value</p>'),
),
button('切换只读', 'Toggle readonly', () => setReadonly(!readonly)),
button('读取 HTML', 'Read HTML', () =>
setOutput(editor.current?.getData() ?? ''),
),
button('HTML Source', 'HTML Source', () => {
void editor.current?.setWorkspaceView('source');
}),
button('重建示例', 'Recreate demo', () => {
editor.current = undefined;
setVersion(version + 1);
}),
h('button', { type: 'submit' }, label('提交表单', 'Submit form')),
h('button', { type: 'reset' }, label('重置表单', 'Reset form')),
),
h('p', { role: 'status' }, status),
h(SoEditor, {
key: version,
name: 'body',
value: html,
onChange: setHtml,
readonly,
options,
createEditor,
onReady: (instance) => {
editor.current = instance;
setStatus(label('就绪', 'Ready'));
document.body.dataset.ready = 'true';
},
onError: (error) => setStatus(String(error)),
}),
h('output', { 'data-bound-value': '' }, html),
h('pre', null, output),
);
}
const host = document.getElementById('app');
if (!host) throw new Error('Missing app host');
const root = createRoot(host);
root.render(h(StrictMode, null, h(ArticleForm)));
window.addEventListener('pagehide', () => root.unmount(), { once: true });ts
import '@soeditor/editor/cms/styles.css';
import '../style.css';
import type { CreateClassicEditorOptions } from '@soeditor/editor/cms';
export const locale =
new URLSearchParams(location.search).get('lang') === 'en' ? 'en' : 'zh-CN';
document.documentElement.lang = locale;
export const label = (zh: string, en: string) => (locale === 'en' ? en : zh);
export const initial = '<h2>CMS article</h2><p>Edit this content.</p>';
export const options: CreateClassicEditorOptions = {
locale,
ariaLabel: label('文章正文', 'Article HTML'),
editingModes: ['wysiwyg', 'source'],
minHeight: 240,
};
export const createEditor = async (
host: HTMLElement,
configuration: CreateClassicEditorOptions,
) => {
const cms = await import('@soeditor/editor/cms/optional');
return cms.createClassicEditor(host, configuration);
};html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex, nofollow" />
<title>SoEditor react demo</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./frameworks/react.ts"></script>
</body>
</html>