# 迁移区块以实现 iframe 编辑器兼容性

## 概述

iframe 集成是现代化编辑体验持续努力的一部分。在模板编辑器原始 iframe 迁移的基础上，WordPress 正朝着在 iframe 内运行文章编辑器的方向发展。

本指南鼓励迁移区块至 API 版本 3，为文章编辑器计划的 iframe 集成做准备。它有助于提前验证区块在 iframe 编辑器中的工作状态，并协助更新区块以确保其在 iframe 环境中正常运行。

## 什么是 iframe 编辑器？

### iframe 编辑器的优势

从技术角度来看，iframe 编辑器提供了几个重要优势：

-   **样式隔离**：管理后台样式不再影响编辑器内容，无需重置管理后台 CSS 规则。内容样式不再影响管理后台界面，因此区块和主题的 CSS 规则不再需要添加前缀。
-   **视口相对单位**：视口相对 CSS 单位（`vw`、`vh`）能正确工作。编辑器内容的尺寸通常与管理后台页面尺寸不同，因此若无 iframe，像 `vw` 这样的单位将相对于管理后台页面计算。
-   **媒体查询**：媒体查询能原生工作，无需脆弱的变通方案。
-   **更易开发**：区块和主题开发者受益，因为前端样式几乎无需调整即可直接使用。这也适用于轻量级区块，其编辑器 DOM 结构与前端匹配。
-   **选区处理**：由于编辑器内容位于独立窗口，编辑器中的选区可以保持可见，同时编辑器 UI（例如 URL 输入字段）中也可以存在（折叠的）选区。

iframe 化的文章编辑器将通过减少样式冲突和提高布局准确性，使区块和主题开发者的工作更加轻松。

### 文章编辑器何时以 iframe 形式工作？

虽然包括模板编辑器在内的大多数编辑器已采用 iframe 模式，但为了向后兼容，当前文章编辑器仅在满足以下条件时才会以 iframe 形式工作（由 [useShouldIframe 钩子](https://github.com/WordPress/gutenberg/blob/cd4fae71551e0ebf27472da1d7bbdfce91a131ec/packages/edit-post/src/components/layout/use-should-iframe.js#L16) 决定）：

- **启用 Gutenberg 插件时：** 当前主题为区块主题 或 所有已注册区块的 `apiVersion` 均为 3 或更高版本
- **未启用 Gutenberg 插件时：** 所有已注册区块的 `apiVersion` 均为 3 或更高版本

简而言之，若您尚未在 iframe 编辑器中完成区块的全面测试，通过保持 `apiVersion` 为 2，可在多数情况下阻止文章编辑器以 iframe 形式运行。当确认您的区块能在 iframe 编辑器中正常工作后，即可迁移至 `apiVersion` 3。

### 文章编辑器何时会以 iframe 方式工作？

**在 WordPress 7.0 中，无论已注册区块的 `apiVersion` 如何，文章编辑器都计划始终以 iframe 方式工作**。

在此之前，为了鼓励开发者在 iframe 编辑器中测试，WordPress 6.9 引入了以下开发者警告和模式变更：

-   **浏览器控制台警告**：当区块以 `apiVersion` 2 或更低版本注册时，WordPress 会在浏览器控制台中显示以下消息：
  ```
  Block with API version 2 or lower is deprecated since version 6.9. See: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/block-migration-for-iframe-editor-compatibility/
	Note: The block "my-plugin/my-block" is registered with API version 2. This means that the post editor may work as a non-iframe editor. Since all editors are planned to work as iframes in the future, set the `apiVersion` field to 3 and test the block inside the iframe editor.
  ```
-   **block.json 模式更新**：[block.json 模式](https://github.com/WordPress/gutenberg/blob/trunk/schemas/json/block.json) 已更新，仅允许新创建或更新的区块使用 `apiVersion: 3`。旧版本（`1` 或 `2`）将不再通过模式验证。

## 如何在 iframe 文章编辑器中测试你的区块

所有核心区块已使用 `apiVersion` 3，因此只需将你的 `apiVersion` 更改为 3，即可让你的区块在 iframe 文章编辑器中工作。

但请确保不存在其他注册版本为 2 或更低的第三方区块。如果注册了版本 2 或更低的区块，文章编辑器可能无法作为 iframe 编辑器正常工作。

## iframe 编辑器的技术考量

大多数区块无需修改即可在 iframe 编辑器中运行，但以下技术注意事项和需知事项已记录如下。

### 文档与窗口

iframe 将拥有与当前作为父窗口的管理页面不同的 `document` 和 `window`。编辑器脚本加载于管理页面中，因此通过访问 `document` 或 `window` 来操作内容将不再有效。

大多数使用 React 编写的区块应能继续正常工作，除非您依赖 `document` 或 `window`。修复方法是创建引用以访问相关文档 ([`ownerDocument`](https://developer.mozilla.org/en-US/docs/Web/API/Node/ownerDocument)) 或窗口 ([`defaultView`](https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView))。无论是否使用 iframe，这都是良好的实践，可避免使用全局变量。

### Using useRef

```javascript
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRef, useEffect } from '@wordpress/element';

export default function Edit() {
	const ref = useRef();

	useEffect( () => {
		const { ownerDocument } = ref.current;
		const { defaultView } = ownerDocument;
		defaultView.addEventListener( ... );
		return () => {
			defaultView.removeEventListener( ... );
		};
	}, [] );

	const blockProps = useBlockProps( { ref } );

	return (
		<div { ...blockProps }>
			Hello world!
		</div>
	);
}
```

### 使用 useRefEffect（推荐）

如果附加事件处理程序，请注意当 ref 发生变化时，`useEffect` 回调不会被调用。因此，建议使用新的 `useRefEffect` API，该 API 在 ref 发生变化时（除了传递的任何依赖项之外）*会*调用给定的回调函数。

```javascript
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';

export default function Edit() {
	const ref = useRefEffect( ( element ) => {
		const { ownerDocument } = element;
		const { defaultView } = ownerDocument;
		defaultView.addEventListener( ... );
		return () => {
			defaultView.removeEventListener( ... );
		};
	}, [] );

	const blockProps = useBlockProps( { ref } );

	return (
		<div { ...blockProps }>
			Hello world!
		</div>
	);
}
```

### 其他框架和库

对于编辑器，jQuery 等脚本会在父窗口（管理页面）中加载，这没有问题。当使用这些脚本与 iframe 中的区块交互时，您应该传递元素引用。

```javascript
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';
import jQuery from 'jquery';

export default function Edit() {
	const ref = useRefEffect( ( element ) => {
		jQuery( element ).masonry( … );
		return () => {
			jQuery( element ).masonry( 'destroy' );
		}
	}, [] );

	const blockProps = useBlockProps( { ref } );

	return (
		<div { ...blockProps }>
			Hello world!
		</div>
	);
}
```

### 如果库使用了全局的 window 或 document 且你无法控制怎么办？

向该库提交 issue 或 PR，建议其使用 `ownerDocument` 和 `defaultView` 替代全局对象。理想情况下，任何库都应允许以 iframe 内的元素作为目标进行初始化。这并非不可能。欢迎随时联系我们提及此问题。

在此期间，你可以使用在 iframe 内加载的脚本。我们已经将所有前端脚本加载到 iframe 中以解决此类情况，但请注意，理想情况下你根本不应使用在 iframe 中加载的脚本。你可以使用 `defaultView` 来访问脚本。

```javascript
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/element';
import jQuery from 'jquery';

export default function Edit() {
	const ref = useRefEffect( ( element ) => {
		const { ownerDocument } = element;
		const { defaultView } = ownerDocument;

		// 使用在 iframe 中加载的脚本。
		// 脚本是异步加载的，因此检查脚本是否已加载。
		// 依赖项加载完成后，区块将重新渲染。
		if ( ! defaultView.jQuery ) {
			return;
		}

		defaultView.jQuery( element ).masonry( … );
		return () => {
			defaultView.jQuery( element ).masonry( 'destroy' );
		}
	} );

	const blockProps = useBlockProps( { ref } );

	return (
		<div { ...blockProps }>
			Hello world!
		</div>
	);
}
```