11 KiB
构建创建页面表单
在上一章节中我们创建了编辑页面功能,本章节我们将新增创建页面功能。以下是我们即将构建功能的预览:
步骤一:添加“创建新页面”按钮
首先我们构建一个用于显示创建页面表单的按钮,这与我们在第三章节构建的编辑按钮类似:
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通过getLastEntitySaveError和isSavingEntityRecord选择器获取错误和进度信息,两者都传递三个参数:( '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 }
/>
);
}
大功告成!以下是我们新表单的实际运行效果:




