gutenbergdocs/docs/how-to-guides/data-basics/4-building-a-create-page-form.md
2025-10-22 01:40:18 +08:00

11 KiB
Raw Blame History

构建创建页面表单

上一章节中我们创建了编辑页面功能,本章节我们将新增创建页面功能。以下是我们即将构建功能的预览:

步骤一:添加“创建新页面”按钮

首先我们构建一个用于显示创建页面表单的按钮,这与我们在第三章节构建的编辑按钮类似:

import { useDispatch } from '@wordpress/data';
import { Button, Modal, TextControl } from '@wordpress/components';

function CreatePageButton() {
	const [isOpen, setOpen] = useState( false );
	const openModal = () => setOpen( true );
	const closeModal = () => setOpen( false );
	return (
		<>
			<Button onClick={ openModal } variant="primary">
				创建新页面
			</Button>
			{ isOpen && (
				<Modal onRequestClose={ closeModal } title="创建新页面">
					<CreatePageForm
						onCancel={ closeModal }
						onSaveFinished={ closeModal }
					/>
				</Modal>
			) }
		</>
	);
}

function CreatePageForm() {
	// 暂时留空
	return <div/>;
}

很好!现在让MyFirstApp显示我们全新的按钮:

function MyFirstApp() {
	// ...
	return (
		<div>
			<div className="list-controls">
				<SearchControl onChange={ setSearchTerm } value={ searchTerm }/>
				<CreatePageButton/>
			</div>
			<PagesList hasResolved={ hasResolved } pages={ pages }/>
		</div>
	);
}

最终效果如下所示:

步骤二:提取受控页面表单

按钮就位后,我们可以全力构建表单。本教程重点在于数据管理,因此不会构建完整的页面编辑器。表单将仅包含一个字段:文章标题。

幸运的是,我们在第三章节构建的EditPageForm已经实现了80%的功能。大部分用户界面已就绪,我们将在CreatePageForm中复用这些组件。首先将表单UI提取为独立组件

function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
	// ...
	return (
		<PageForm
			title={ page.title }
			onChangeTitle={ handleChange }
			hasEdits={ hasEdits }
			lastError={ lastError }
			isSaving={ isSaving }
			onCancel={ onCancel }
			onSave={ handleSave }
		/>
	);
}

function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
	return (
		<div className="my-gutenberg-form">
			<TextControl
				__nextHasNoMarginBottom
				__next40pxDefaultSize
				label="页面标题:"
				value={ title }
				onChange={ onChangeTitle }
			/>
			{ lastError ? (
				<div className="form-error">错误{ lastError.message }</div>
			) : (
				false
			) }
			<div className="form-buttons">
				<Button
					onClick={ onSave }
					variant="primary"
					disabled={ !hasEdits || isSaving }
				>
					{ isSaving ? (
						<>
							<Spinner/>
							保存中
						</>
					) : '保存' }
				</Button>
				<Button
					onClick={ onCancel }
					variant="tertiary"
					disabled={ isSaving }
				>
					取消
				</Button>
			</div>
		</div>
	);
}

这段代码的质量优化不应改变应用程序的任何功能。让我们尝试编辑页面来确认:

很好!编辑表单依然存在,现在我们有了构建新CreatePageForm的基础模块。

整合所有代码

以下是本章节构建的全部内容:

function CreatePageForm( { onCancel, onSaveFinished } ) {
	const [title, setTitle] = useState();
	const { lastError, isSaving } = useSelect(
		( select ) => ( {
			lastError: select( coreDataStore )
				.getLastEntitySaveError( 'postType', 'page' ),
			isSaving: select( coreDataStore )
				.isSavingEntityRecord( 'postType', 'page' ),
		} ),
		[]
	);

	const { saveEntityRecord } = useDispatch( coreDataStore );
	const handleSave = async () => {
		const savedRecord = await saveEntityRecord(
			'postType',
			'page',
			{ title, status: 'publish' }
		);
		if ( savedRecord ) {
			onSaveFinished();
		}
	};

	return (
		<PageForm
			title={ title }
			onChangeTitle={ setTitle }
			hasEdits={ !!title }
			onSave={ handleSave }
			lastError={ lastError }
			onCancel={ onCancel }
			isSaving={ isSaving }
		/>
	);
}

function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
	const { page, lastError, isSaving, hasEdits } = useSelect(
		( select ) => ( {
			page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
			lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ),
			isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ),
			hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ),
		} ),
		[pageId]
	);

	const { saveEditedEntityRecord, editEntityRecord } = useDispatch( coreDataStore );
	const handleSave = async () => {
		const savedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId );
		if ( savedRecord ) {
			onSaveFinished();
		}
	};
	const handleChange = ( title ) => editEntityRecord( 'postType', 'page', page.id, { title } );

	return (
		<PageForm
			title={ page.title }
			onChangeTitle={ handleChange }
			hasEdits={ hasEdits }
			lastError={ lastError }
			isSaving={ isSaving }
			onCancel={ onCancel }
			onSave={ handleSave }
		/>
	);
}

function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
	return (
		<div className="my-gutenberg-form">
			<TextControl
				__nextHasNoMarginBottom
				__next40pxDefaultSize
				label="页面标题:"
				value={ title }
				onChange={ onChangeTitle }
			/>
			{ lastError ? (
				<div className="form-error">错误{ lastError.message }</div>
			) : (
				false
			) }
			<div className="form-buttons">
				<Button
					onClick={ onSave }
					variant="primary"
					disabled={ !hasEdits || isSaving }
				>
					{ isSaving ? (
						<>
							<Spinner/>
							保存中
						</>
					) : '保存' }
				</Button>
				<Button
					onClick={ onCancel }
					variant="tertiary"
					disabled={ isSaving }
				>
					取消
				</Button>
			</div>
		</div>
	);
}

现在只需刷新页面即可体验表单功能:

后续步骤

步骤三构建CreatePageForm组件

CreatePageForm组件只需提供渲染PageForm组件所需的七个属性:

  • 标题
  • 标题变更处理函数
  • 编辑状态标识
  • 最后错误信息
  • 保存状态标识
  • 取消处理函数
  • 保存处理函数

具体实现如下:

标题、标题变更处理、编辑状态

EditPageForm组件更新并保存的是Redux状态中已存在的实体记录因此我们依赖editedEntityRecords选择器。

CreatePageForm不存在预先的实体记录只有空表单。用户输入的内容仅存在于本地表单可通过React的useState钩子进行跟踪:

function CreatePageForm( { onCancel, onSaveFinished } ) {
	const [title, setTitle] = useState();
	const handleChange = ( title ) => setTitle( title );
	return (
		<PageForm
			title={ title }
			onChangeTitle={ setTitle }
			hasEdits={ !!title }
			{ /* 其他属性 */ }
		/>
	);
}

保存处理、取消处理

EditPageForm中,我们通过saveEditedEntityRecord('postType', 'page', pageId )操作保存Redux状态中的编辑内容。

CreatePageForm既无Redux状态中的编辑内容也无pageId。此时需要调用的是saveEntityRecord操作名称中不含Edited它接收的是代表新实体记录的对象而非pageId。

传递给saveEntityRecord的数据会通过POST请求发送到对应REST API接口。例如执行以下操作

saveEntityRecord( 'postType', 'page', { title: "测试页面" } );

将向WordPress页面REST API接口发起POST请求请求体中包含单个字段title=测试页面

现在我们将其应用到CreatePageForm

function CreatePageForm( { onSaveFinished, onCancel } ) {
	// ...
	const { saveEntityRecord } = useDispatch( coreDataStore );
	const handleSave = async () => {
		const savedRecord = await saveEntityRecord(
			'postType',
			'page',
			{ title }
		);
		if ( savedRecord ) {
			onSaveFinished();
		}
	};
	return (
		<PageForm
			{ /* 其他属性 */ }
			onSave={ handleSave }
			onCancel={ onCancel }
		/>
	);
}

还需注意:新建页面默认不会被PagesList获取。根据REST API文档/wp/v2/pages接口在创建POST请求时默认生成status=draft的页面但返回GET请求的是status=publish的页面。解决方案是显式传递status参数

function CreatePageForm( { onSaveFinished, onCancel } ) {
	// ...
	const { saveEntityRecord } = useDispatch( coreDataStore );
	const handleSave = async () => {
		const savedRecord = await saveEntityRecord(
			'postType',
			'page',
			{ title, status: 'publish' }
		);
		if ( savedRecord ) {
			onSaveFinished();
		}
	};
	return (
		<PageForm
			{ /* 其他属性 */ }
			onSave={ handleSave }
			onCancel={ onCancel }
		/>
	);
}

请将此更改应用到本地的CreatePageForm组件,接下来处理剩余两个属性。

最后错误、保存状态

EditPageForm通过getLastEntitySaveErrorisSavingEntityRecord选择器获取错误和进度信息,两者都传递三个参数:( 'postType', 'page', pageId )

CreatePageForm没有pageId参数。此时可省略pageId参数来获取未指定ID的实体记录信息即新建记录useSelect调用与EditPageForm非常相似:

function CreatePageForm( { onCancel, onSaveFinished } ) {
	// ...
	const { lastError, isSaving } = useSelect(
		( select ) => ( {
			// 注意省略了pageId参数
			lastError: select( coreDataStore )
				.getLastEntitySaveError( 'postType', 'page' ),
			// 注意省略了pageId参数
			isSaving: select( coreDataStore )
				.isSavingEntityRecord( 'postType', 'page' ),
		} ),
		[]
	);
	// ...
	return (
		<PageForm
			{ /* 其他属性 */ }
			lastError={ lastError }
			isSaving={ isSaving }
		/>
	);
}

大功告成!以下是我们新表单的实际运行效果: