552 lines
18 KiB
Markdown
552 lines
18 KiB
Markdown
|
|
## 接下来做什么?
|
|||
|
|
|
|||
|
|
* **上一篇:** [构建页面列表](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md)
|
|||
|
|
* **下一篇:** [构建创建页面表单](/docs/how-to-guides/data-basics/4-building-a-create-page-form.md)
|
|||
|
|
* (可选)在 block-development-examples 代码库中查看[完整示例应用](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8)
|
|||
|
|
|
|||
|
|
# 构建编辑表单
|
|||
|
|
|
|||
|
|
本节内容将为我们的应用添加*编辑*功能。以下是即将构建功能的预览图:
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
### 步骤一:添加“编辑”按钮
|
|||
|
|
|
|||
|
|
要实现*编辑*功能首先需要添加编辑按钮,让我们从在`PagesList`组件中添加按钮开始:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { Button } from '@wordpress/components';
|
|||
|
|
import { decodeEntities } from '@wordpress/html-entities';
|
|||
|
|
|
|||
|
|
const PageEditButton = () => (
|
|||
|
|
<Button variant="primary">
|
|||
|
|
编辑
|
|||
|
|
</Button>
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
function PagesList( { hasResolved, pages } ) {
|
|||
|
|
if ( ! hasResolved ) {
|
|||
|
|
return <Spinner />;
|
|||
|
|
}
|
|||
|
|
if ( ! pages?.length ) {
|
|||
|
|
return <div>暂无结果</div>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<table className="wp-list-table widefat fixed striped table-view-list">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<td>标题</td>
|
|||
|
|
<td style={{width: 120}}>操作</td>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
{ pages?.map( ( page ) => (
|
|||
|
|
<tr key={page.id}>
|
|||
|
|
<td>{ decodeEntities( page.title.rendered ) }</td>
|
|||
|
|
<td>
|
|||
|
|
<PageEditButton pageId={ page.id } />
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
) ) }
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`PagesList`组件中唯一的变化是新增了标为“操作”的列:
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
### 步骤二:显示编辑表单
|
|||
|
|
|
|||
|
|
我们的按钮外观不错但尚未实现功能。要显示编辑表单,首先需要创建它:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { Button, TextControl } from '@wordpress/components';
|
|||
|
|
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
|
|||
|
|
return (
|
|||
|
|
<div className="my-gutenberg-form">
|
|||
|
|
<TextControl
|
|||
|
|
__nextHasNoMarginBottom
|
|||
|
|
__next40pxDefaultSize
|
|||
|
|
value=''
|
|||
|
|
label='页面标题:'
|
|||
|
|
/>
|
|||
|
|
<div className="form-buttons">
|
|||
|
|
<Button onClick={ onSaveFinished } variant="primary">
|
|||
|
|
保存
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={ onCancel } variant="tertiary">
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
现在让按钮触发显示刚创建的编辑表单。由于本教程不侧重网页设计,我们将使用需要最少代码量的[`Modal`](https://developer.wordpress.org/block-editor/reference-guides/components/modal/)组件将两者连接。更新`PageEditButton`如下:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { Button, Modal, TextControl } from '@wordpress/components';
|
|||
|
|
|
|||
|
|
function PageEditButton({ pageId }) {
|
|||
|
|
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="编辑页面">
|
|||
|
|
<EditPageForm
|
|||
|
|
pageId={pageId}
|
|||
|
|
onCancel={closeModal}
|
|||
|
|
onSaveFinished={closeModal}
|
|||
|
|
/>
|
|||
|
|
</Modal>
|
|||
|
|
) }
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
现在点击*编辑*按钮,您将看到如下模态框:
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
很好!我们现在有了可操作的基础用户界面。
|
|||
|
|
|
|||
|
|
### 步骤三:在表单中填充页面详情
|
|||
|
|
|
|||
|
|
我们需要让`EditPageForm`显示当前编辑页面的标题。您可能注意到它并未接收`page`属性,仅接收`pageId`。这没有问题,Gutenberg Data让我们能够轻松在任何组件中访问实体记录。
|
|||
|
|
|
|||
|
|
这里我们需要使用[`getEntityRecord`](/docs/reference-guides/data/data-core.md#getentityrecord)选择器。得益于`MyFirstApp`中的`getEntityRecords`调用,记录列表已准备就绪,甚至无需发起额外的HTTP请求——我们可以直接获取缓存记录。
|
|||
|
|
|
|||
|
|
您可以在浏览器开发工具中这样尝试:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', 9 ); // 将9替换为实际页面ID
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
接下来更新`EditPageForm`:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
|
|||
|
|
const page = useSelect(
|
|||
|
|
select => select( coreDataStore ).getEntityRecord( 'postType', 'page', pageId ),
|
|||
|
|
[pageId]
|
|||
|
|
);
|
|||
|
|
return (
|
|||
|
|
<div className="my-gutenberg-form">
|
|||
|
|
<TextControl
|
|||
|
|
__nextHasNoMarginBottom
|
|||
|
|
__next40pxDefaultSize
|
|||
|
|
label='页面标题:'
|
|||
|
|
value={ page.title.rendered }
|
|||
|
|
/>
|
|||
|
|
{ /* ... */ }
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
现在效果应如图所示:
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
### 步骤五:保存表单数据
|
|||
|
|
|
|||
|
|
既然我们已经能够编辑页面标题,接下来要确保能够保存它。在 Gutenberg 数据系统中,我们使用 `saveEditedEntityRecord` 操作将变更保存到 WordPress REST API。该操作会发送请求、处理结果,并更新 Redux 状态中的缓存数据。
|
|||
|
|
|
|||
|
|
以下示例可在浏览器开发者工具中尝试:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 将数字9替换为实际页面ID
|
|||
|
|
wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'page', 9, { title: '更新后的标题' } );
|
|||
|
|
wp.data.dispatch( 'core' ).saveEditedEntityRecord( 'postType', 'page', 9 );
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
以上代码片段保存了新标题。与之前不同,现在 `getEntityRecord` 会反映更新后的标题:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 将数字9替换为实际页面ID
|
|||
|
|
wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', 9 ).title.rendered
|
|||
|
|
// 返回:"更新后的标题"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 REST API 请求完成后,实体记录会立即更新以反映所有已保存的变更。
|
|||
|
|
|
|||
|
|
这是带有生效*保存*按钮的 `EditPageForm` 组件示例:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
const { saveEditedEntityRecord } = useDispatch( coreDataStore );
|
|||
|
|
const handleSave = () => saveEditedEntityRecord( 'postType', 'page', pageId );
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="my-gutenberg-form">
|
|||
|
|
{/* ... */}
|
|||
|
|
<div className="form-buttons">
|
|||
|
|
<Button onClick={ handleSave } variant="primary">
|
|||
|
|
保存
|
|||
|
|
</Button>
|
|||
|
|
{/* ... */}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
虽然功能已实现,但还需修复一个问题:表单模态框不会自动关闭,因为我们从未调用 `onSaveFinished`。幸运的是,`saveEditedEntityRecord` 返回的 Promise 会在保存操作完成后解析。让我们在 `EditPageForm` 中利用这个特性:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
const handleSave = async () => {
|
|||
|
|
await saveEditedEntityRecord( 'postType', 'page', pageId );
|
|||
|
|
onSaveFinished();
|
|||
|
|
};
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步骤六:错误处理
|
|||
|
|
|
|||
|
|
此前我们乐观地假设*保存*操作总能成功。但实际操作可能因以下原因失败:
|
|||
|
|
|
|||
|
|
* 网站可能宕机
|
|||
|
|
* 更新内容可能无效
|
|||
|
|
* 页面可能已被他人删除
|
|||
|
|
|
|||
|
|
为了在出现这些问题时通知用户,我们需要进行两处调整。当更新失败时,我们不希望关闭表单模态框。仅当更新确实成功时,`saveEditedEntityRecord` 返回的 Promise 才会解析为更新后的记录。若出现异常,则会解析为空值。我们可以利用这一点来保持模态框开启状态:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
const handleSave = async () => {
|
|||
|
|
const updatedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId );
|
|||
|
|
if ( updatedRecord ) {
|
|||
|
|
onSaveFinished();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
很好!现在让我们来显示错误信息。可以通过 `getLastEntitySaveError` 选择器获取失败详情:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 将数字9替换为实际页面ID
|
|||
|
|
wp.data.select( 'core' ).getLastEntitySaveError( 'postType', 'page', 9 )
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
以下是在 `EditPageForm` 中的具体应用:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
const { lastError, page } = useSelect(
|
|||
|
|
select => ({
|
|||
|
|
page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
|
|||
|
|
lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId )
|
|||
|
|
}),
|
|||
|
|
[ pageId ]
|
|||
|
|
)
|
|||
|
|
// ...
|
|||
|
|
return (
|
|||
|
|
<div className="my-gutenberg-form">
|
|||
|
|
{/* ... */}
|
|||
|
|
{ lastError ? (
|
|||
|
|
<div className="form-error">
|
|||
|
|
错误:{ lastError.message }
|
|||
|
|
</div>
|
|||
|
|
) : false }
|
|||
|
|
{/* ... */}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
太棒了!现在 `EditPageForm` 已能完全感知错误状态。
|
|||
|
|
|
|||
|
|
让我们通过触发无效更新来查看错误提示效果。由于文章标题很难引发错误,我们可以将 `date` 属性设置为 `-1` —— 这必定会触发验证错误:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
const handleChange = ( title ) => editEntityRecord( 'postType', 'page', pageId, { title, date: -1 } );
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
刷新页面后,打开表单修改标题并点击保存,您将看到如下错误提示:
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
非常好!现在我们可以**恢复 `handleChange` 函数的先前版本**,继续下一步操作。
|
|||
|
|
|
|||
|
|
### 步骤七:状态指示器
|
|||
|
|
|
|||
|
|
我们的表单还存在最后一个问题:缺乏视觉反馈。在表单消失或显示错误信息之前,我们无法完全确定*保存*按钮是否生效。
|
|||
|
|
|
|||
|
|
现在我们将解决这个问题,并向用户传达两种状态:_保存中_和_未检测到更改_。相关的选择器是`isSavingEntityRecord`和`hasEditsForEntityRecord`。与`getEntityRecord`不同,这些选择器从不发起HTTP请求,仅返回当前实体记录状态。
|
|||
|
|
|
|||
|
|
让我们在`EditPageForm`中使用它们:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
const { isSaving, hasEdits, /* ... */ } = useSelect(
|
|||
|
|
select => ({
|
|||
|
|
isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ),
|
|||
|
|
hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ),
|
|||
|
|
// ...
|
|||
|
|
}),
|
|||
|
|
[ pageId ]
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
现在我们可以使用`isSaving`和`hasEdits`在保存过程中显示加载动画,并在无编辑内容时禁用保存按钮:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function EditPageForm( { pageId, onSaveFinished } ) {
|
|||
|
|
// ...
|
|||
|
|
return (
|
|||
|
|
// ...
|
|||
|
|
<div className="form-buttons">
|
|||
|
|
<Button onClick={ handleSave } variant="primary" disabled={ ! hasEdits || isSaving }>
|
|||
|
|
{ isSaving ? (
|
|||
|
|
<>
|
|||
|
|
<Spinner/>
|
|||
|
|
保存中
|
|||
|
|
</>
|
|||
|
|
) : '保存' }
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
onClick={ onCancel }
|
|||
|
|
variant="tertiary"
|
|||
|
|
disabled={ isSaving }
|
|||
|
|
>
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
// ...
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
请注意,当没有编辑内容或页面正在保存时,我们会禁用*保存*按钮。这是为了防止用户意外重复点击按钮。
|
|||
|
|
|
|||
|
|
此外,由于`@wordpress/data`不支持中断正在进行的*保存*操作,我们也相应禁用了*取消*按钮。
|
|||
|
|
|
|||
|
|
实际运行效果如下:
|
|||
|
|
|
|||
|
|

|
|||
|
|

|
|||
|
|
|
|||
|
|
### 整体联调
|
|||
|
|
|
|||
|
|
所有组件都已就位,太棒了!以下是我们本章构建的完整代码:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { useDispatch } from '@wordpress/data';
|
|||
|
|
import { Button, Modal, TextControl } from '@wordpress/components';
|
|||
|
|
|
|||
|
|
function PageEditButton( { pageId } ) {
|
|||
|
|
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="编辑页面">
|
|||
|
|
<EditPageForm
|
|||
|
|
pageId={ pageId }
|
|||
|
|
onCancel={ closeModal }
|
|||
|
|
onSaveFinished={ closeModal }
|
|||
|
|
/>
|
|||
|
|
</Modal>
|
|||
|
|
) }
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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 (
|
|||
|
|
<div className="my-gutenberg-form">
|
|||
|
|
<TextControl
|
|||
|
|
__nextHasNoMarginBottom
|
|||
|
|
__next40pxDefaultSize
|
|||
|
|
label="页面标题:"
|
|||
|
|
value={ page.title }
|
|||
|
|
onChange={ handleChange }
|
|||
|
|
/>
|
|||
|
|
{ lastError ? (
|
|||
|
|
<div className="form-error">错误:{ lastError.message }</div>
|
|||
|
|
) : (
|
|||
|
|
false
|
|||
|
|
) }
|
|||
|
|
<div className="form-buttons">
|
|||
|
|
<Button
|
|||
|
|
onClick={ handleSave }
|
|||
|
|
variant="primary"
|
|||
|
|
disabled={ ! hasEdits || isSaving }
|
|||
|
|
>
|
|||
|
|
{ isSaving ? (
|
|||
|
|
<>
|
|||
|
|
<Spinner/>
|
|||
|
|
保存中
|
|||
|
|
</>
|
|||
|
|
) : '保存' }
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
onClick={ onCancel }
|
|||
|
|
variant="tertiary"
|
|||
|
|
disabled={ isSaving }
|
|||
|
|
>
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步骤四:实现页面标题字段的可编辑功能
|
|||
|
|
|
|||
|
|
我们的*页面标题*字段存在一个问题:无法编辑。它接收固定值但在输入时不会更新。我们需要一个 `onChange` 处理函数。
|
|||
|
|
|
|||
|
|
您可能在其他 React 应用中也见过类似的模式,这被称为["受控组件"](https://reactjs.org/docs/forms.html#controlled-components):
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
function VanillaReactForm({ initialTitle }) {
|
|||
|
|
const [title, setTitle] = useState( initialTitle );
|
|||
|
|
return (
|
|||
|
|
<TextControl
|
|||
|
|
__nextHasNoMarginBottom
|
|||
|
|
__next40pxDefaultSize
|
|||
|
|
value={ title }
|
|||
|
|
onChange={ setTitle }
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 Gutenberg 数据中更新实体记录与此类似,但不同之处在于,我们不使用 `setTitle` 将数据存储在本地(组件级别)状态,而是使用 `editEntityRecord` 操作将更新存储在 *Redux* 状态中。以下是在浏览器的开发工具中尝试的方法:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
// 我们需要一个有效的页面 ID 来调用 editEntityRecord,因此使用 getEntityRecords 获取第一个可用的 ID。
|
|||
|
|
const pageId = wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' )[0].id;
|
|||
|
|
|
|||
|
|
// 更新标题
|
|||
|
|
wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'page', pageId, { title: '更新后的标题' } );
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
此时,您可能会问,`editEntityRecord` 比 `useState` 好在哪里?答案是它提供了一些额外功能。
|
|||
|
|
|
|||
|
|
首先,我们可以像检索数据一样轻松地保存更改,并确保所有缓存都能正确更新。
|
|||
|
|
|
|||
|
|
其次,通过 `editEntityRecord` 应用的更改可以通过 `undo` 和 `redo` 操作轻松撤销或重做。
|
|||
|
|
|
|||
|
|
最后,由于更改存储在 *Redux* 状态中,它们是“全局的”,可以被其他组件访问。例如,我们可以让 `PagesList` 显示当前编辑的标题。
|
|||
|
|
|
|||
|
|
关于最后一点,让我们看看使用 `getEntityRecord` 访问刚刚更新的实体记录时会发生什么:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', pageId ).title
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
它并未反映编辑后的内容。这是怎么回事?
|
|||
|
|
|
|||
|
|
实际上,`<PagesList />` 渲染的是 `getEntityRecord()` 返回的数据。如果 `getEntityRecord()` 反映了更新后的标题,那么用户在 `TextControl` 中输入的任何内容也会立即显示在 `<PagesList />` 中。这并不是我们想要的效果。在用户决定保存之前,编辑内容不应泄漏到表单之外。
|
|||
|
|
|
|||
|
|
Gutenberg 数据通过区分*实体记录*和*已编辑的实体记录*来解决这个问题。*实体记录*反映来自 API 的数据,忽略任何本地编辑,而*已编辑的实体记录*则在数据基础上应用了所有本地编辑。两者同时存在于 Redux 状态中。
|
|||
|
|
|
|||
|
|
让我们看看调用 `getEditedEntityRecord` 会发生什么:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
wp.data.select( 'core' ).getEditedEntityRecord( 'postType', 'page', pageId ).title
|
|||
|
|
// "更新后的标题"
|
|||
|
|
|
|||
|
|
wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', pageId ).title
|
|||
|
|
// { "rendered": "<原始未更改的标题>", "raw": "..." }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如您所见,实体记录的 `title` 是一个对象,而已编辑实体记录的 `title` 是一个字符串。
|
|||
|
|
|
|||
|
|
这并非偶然。像 `title`、`excerpt` 和 `content` 这样的字段可能包含[短代码](https://developer.wordpress.org/apis/handbook/shortcode/)或[动态区块](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md),这意味着它们只能在服务器上渲染。对于这些字段,REST API 同时暴露了 `raw` 标记和 `rendered` 字符串。例如,在区块编辑器中,`content.rendered` 可用于视觉预览,而 `content.raw` 可用于填充代码编辑器。
|
|||
|
|
|
|||
|
|
那么,为什么已编辑实体记录的 `content` 是一个字符串?由于 JavaScript 无法正确渲染任意的区块标记,它仅存储 `raw` 标记,而不包含 `rendered` 部分。由于这是一个字符串,整个字段就变成了字符串。
|
|||
|
|
|
|||
|
|
现在我们可以相应地更新 `EditPageForm`。我们可以使用 [`useDispatch`](/packages/data/README.md#usedispatch) 钩子访问操作,类似于使用 `useSelect` 访问选择器:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { useDispatch } from '@wordpress/data';
|
|||
|
|
|
|||
|
|
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
|
|||
|
|
const page = useSelect(
|
|||
|
|
select => select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
|
|||
|
|
[ pageId ]
|
|||
|
|
);
|
|||
|
|
const { editEntityRecord } = useDispatch( coreDataStore );
|
|||
|
|
const handleChange = ( title ) => editEntityRecord( 'postType', 'page', pageId, { title } );
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="my-gutenberg-form">
|
|||
|
|
<TextControl
|
|||
|
|
__nextHasNoMarginBottom
|
|||
|
|
__next40pxDefaultSize
|
|||
|
|
label="页面标题:"
|
|||
|
|
value={ page.title }
|
|||
|
|
onChange={ handleChange }
|
|||
|
|
/>
|
|||
|
|
<div className="form-buttons">
|
|||
|
|
<Button onClick={ onSaveFinished } variant="primary">
|
|||
|
|
保存
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={ onCancel } variant="tertiary">
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
我们添加了一个 `onChange` 处理函数,通过 `editEntityRecord` 操作跟踪编辑,然后将选择器更改为 `getEditedEntityRecord`,以便 `page.title` 始终反映更改。
|
|||
|
|
|
|||
|
|
现在的效果如下:
|
|||
|
|
|
|||
|
|

|