У нас есть базовые знания, мы поиграли с React, и теперь у нас есть настроенные инструменты проекта. Давайте приступим к созданию нашего пользовательского блока.
Что мы создаем
Мы собираемся создать пользовательский блок карточки с изображением, заголовком и подписью. Это распространенный шаблон проектирования в Интернете, и он также позволяет нам взглянуть на некоторые основные компоненты Gutenberg, а также основные элементы WordPress, такие как Медиа библиотека. Мы также поиграем с некоторой логикой отображения с JSX для внешней разметки.

В этом уроке мы сосредоточимся исключительно на аспекте CMS этого блока. Вы можете расширить этот блок, добавив в него стили интерфейса.
Начало работы
Первое, что мы собираемся сделать, это открыть block.js
файл, который мы создали в предыдущем разделе. В активной папке плагинов он находится по адресу blocks/src/block/block.js
.Если вы работаете с файловой структурой, созданной с помощью create-guten-block, вы можете начать с удаления всего содержимого block.js
и написания кода с нуля.
Справа вверху этого файла добавьте следующее:
const { RichText, MediaUpload, PlainText } = wp.editor;
const { registerBlockType } = wp.blocks;
const { Button } = wp.components;
Мы рассмотрели деструктуризацию в части 4.
Давайте импортируем наши файлы Sass.
import './style.scss';
import './editor.scss';
Теперь давайте начнем писать компонент для нашего блока. Прямо под этими строками добавьте следующее:
registerBlockType('card-block/main', {
title: 'Card',
icon: 'heart',
category: 'common'
});
Этот код говорит Gutenberg: «У меня есть блок, который вы можете добавить в свою коллекцию. Он называется« Карточка », у него есть значок« сердце », и он должен находиться в категории« общие »». Это базовое определение нашего компонента, поэтому давайте добавим еще немного кода.
Вы также можете узнать функцию registerBlockType
из функции PHP register_block_type
, которую мы использовали в прошлой статье. В то время как эта функция регистрирует блок на стороне сервера WordPress, эта функция регистрирует наш блок в экосистеме React на стороне клиента. И та, и другая необходимы, чтобы иметь блок, использующий React, и их зарегистрированные имена card-block/main
должны совпадать.
Добавьте следующий код, но убедитесь , что вы поставить запятую после 'common'
, так это выглядит следующим образом : 'common',
.
Вот код:
attributes: {
title: {
source: 'text',
selector: '.card__title'
},
body: {
type: 'array',
source: 'children',
selector: '.card__body'
},
imageAlt: {
attribute: 'alt',
selector: '.card__image'
},
imageUrl: {
attribute: 'src',
selector: '.card__image'
}
}
Здесь мы определяем редактируемые атрибуты нашего блока и селектор DOM, которым они назначены. Этот объект attribute
работает очень похоже на объект state
React . У него даже есть очень похожий метод обновления setAttributes
.
Об атрибутах и State
Он может выглядеть как простой объект JavaScript, но этот кусок attributes
вводит целый ряд новых концепций в темы WordPress, не в последнюю очередь это State.
В Gutenberg атрибуты отслеживают текущее состояние данных в блоке. Атрибуты являются ближайшей параллелью, которую мы можем провести к пользовательским полям в Gutenberg, но они существуют только в контексте Gutenberg и JavaScript. Давайте возьмем атрибут title
, например:
title: {
source: 'text',
selector: 'card__title'
},
Когда Gutenberg запускается, он говорит: «Мне нужно найти какой-то текст внутри вызываемого селектора.card__title
и заполнить значение тем title
, что я найду».
Атрибуты в Gutenberg не связаны напрямую с базой данных, как с пользовательскими полями post_meta
. Записи source
и selector
это инструкции для Gutenberg, чтобы заполнить состояние каждого блока. Когда мы загружаем редактор, он следует этим инструкциям и присваивает значениеtitle
на основе разметки, сохраненной в базе данных между комментариями HTML, которые указывают блок этого типа. Мы не видим значения title
в attributes
, который мы регистрируем, но если бы мы получили доступ к props.attributes.title
, мы бы получили любой text
, который есть в .card__title
.
Добавим функцию редактирования
Добавьте следующее после закрытия }
объекта attributes
. Как и раньше, обязательно добавьте запятую, чтобы она выглядела следующим образом },
.
Добавьте следующий код после этого:
edit({ attributes, className, setAttributes }) {
return (
);
}
Итак, мы используем назначение деструктурирования, чтобы выбрать переданные параметры для функции редактирования. Двумя наиболее важными являются attributes
и setAttributes
.
Параметр attributes
такой же, как блок attributes
, но в текущем, реактивном состоянии. Это означает, что если функция setAttributes
обновляет одно из значений attributes
, она будет автоматически обновляться в любом месте, которое ссылается на него, что аналогично нашему компоненту React из части 4.
В этой функции есть return
. Мы собираемся вставить туда немного JSX. Добавьте в скобки return
следующее:
<div className="container">
<MediaUpload
onSelect={ media => { setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
type="image"
value={ attributes.imageID }
render={ ({ open }) => getImageButton(open) }
/>
<PlainText
onChange={ content => setAttributes({ title: content }) }
value={ attributes.title }
placeholder="Your card title"
className="heading"
/>
<RichText
onChange={ content => setAttributes({ body: content }) }
value={ attributes.body }
multiline="p"
placeholder="Your card text"
/>
</div>
Хорошо, здесь много чего происходит, но это все, что мы рассмотрели в предыдущих частях этой серии. У нас есть контейнер с тремя существующими компонентами Gutenberg. Для каждого мы устанавливаем релевантное attribute
значение: его значение, соответствующий заполнитель и onChange/onSelect
обработчик. Мы также <MediaUpload />
передаем пользовательское средство рендеринга , о котором мы вскоре расскажем.
Каждый обработчик onChange
представляет собой маленькое выражение, которое проходит новое содержание, которое инициировало onChange
в функции setAttributes
, где мы устанавливаем , какие attributes
обновлять. Это обновление затем распространяется на любую ссылку на этот атрибут, где содержимое будет обновляться как по волшебству. Элемент <MediaUpload />
предвтсавляет событие onSelect
, которое вызывается, когда пользователь выбирает или загружает элемент в медиа — библиотеке.
Есть собственный атрибут render
, который ссылается на функцию getImageButton
. Давайте напишем это. Над return
в функции edit
добавьте следующее:
const getImageButton = (openEvent) => {
if(attributes.imageUrl) {
return (
<img
src={ attributes.imageUrl }
onClick={ openEvent }
className="image"
/>
);
}
else {
return (
<div className="button-container">
<Button
onClick={ openEvent }
className="button button-large"
>
Pick an image
</Button>
</div>
);
}
};
Что эта функция делает это обнаруживает, есть ли imageUrl
в объекте attributes
. Если он есть, он отобразит этот тег <img />
и позволит пользователю кликнуть по нему, чтобы выбрать другой. Если изображения нет, он будет отображать WordPress <Button />
который предлагает пользователю выбрать изображение. Она вызывает то же самое openEvent
, что было передано в функцию.
Добавим функцию сохранения
Теперь у нас есть редакторская часть блока, написанная Gutenberg, и это самая сложная часть. Теперь все, что нам нужно сделать, это сказать Gutenberg, что мы хотим, чтобы блок делал с контентом. С помощью тех же самых реактивных данных attributes
мы также можем отображать нашу внешнюю разметку в режиме реального времени. Это означает, что когда кто-то переключается в режим редактирования HTML на блоке, это будет актуально. Если вы редактируете его в режиме редактирования HTML, визуальный режим также будет обновляться.
После функцииedit
добавьте запятую, чтобы она выглядела следующим образом, },
а затем добавьте следующее в новой строке:
save({ attributes }) {
const cardImage = (src, alt) => {
if(!src) return null;
if(alt) {
return (
<img
className="card__image"
src={ src }
alt={ alt }
/>
);
}
// No alt set, so let's hide it from screen readers
return (
<img
className="card__image"
src={ src }
alt=""
aria-hidden="true"
/>
);
};
return (
<div className="card">
{ cardImage(attributes.imageUrl, attributes.imageAlt) }
<div className="card__content">
<h3 className="card__title">{ attributes.title }</h3>
<div className="card__body">
{ attributes.body }
</div>
</div>
</div>
);
}
Выглядит очень похоже на функцию edit
.
Мы начнем с использования назначения деструктурирования, чтобы извлечь attributes
из переданных параметров, как и в предыдущей функции edit
.
Затем у нас есть другая вспомогательная функция изображения, которая сначала определяет, есть ли изображение, и возвращает его, и возвращает null
если его нет. Помните: мы возвращаем null
в JSX, если хотим, чтобы он ничего не отображал. Следующая вещь, которую делает этот помощник, это визуализация слегка измененного тега <img />
, если есть альтернативный текст или нет. Для последнего он скрывает его от программы чтения с экрана, добавляя aria-hidden="true"
и устанавливая пустой атрибут alt
.
И, наконец, наш return
возвращает блок .card
с чистой, управляемой БЭМ разметкой, которая будет загружаться в начало нашей темы.
Добавим стиль
Откройте editor.scss
, который живет в том же каталоге, что и block.js
. Добавьте следующее:
@import '../common';
.gutenberg { // This may need to be .wp-block in WordPress 5+
.container {
border: 1px solid $gray;
padding: 1rem;
}
.button-container {
text-align: center;
padding: 22% 0;
background: $off-white;
border: 1px solid $gray;
border-radius: 2px;
margin: 0 0 1.2rem 0;
}
.heading {
font-size: 1.5rem;
font-weight: 600;
}
.image {
height: 15.7rem;
width: 100%;
object-fit: cover;
}
}
Это немного CSS, чтобы придать нашему блоку карточный стиль. Все это вложено в класс .gutenberg
. Это для борьбы со спецификой некоторых основных стилей. Внутри редактора есть обертка <div class="gutenberg"
области редактора блока, поэтому мы можем быть уверены, что затрагиваем только те элементы этой вложенности. Вы, вероятно, также заметите, что мы импортируем другой файл Sass, так что давайте заполним его.
/*
* Common SCSS can contain your common variables, helpers and mixins
* that are shared between all of your blocks.
*/
// Colors
$gray: #cccccc;
$off-white: #f1f1f1;
Вот как должен выглядеть весь файл block.js
:
const { RichText, MediaUpload, PlainText } = wp.editor;
const { registerBlockType } = wp.blocks;
const { Button } = wp.components;
// Import our CSS files
import './style.scss';
import './editor.scss';
registerBlockType('card-block/main', {
title: 'Card',
icon: 'heart',
category: 'common',
attributes: {
title: {
source: 'text',
selector: '.card__title'
},
body: {
type: 'array',
source: 'children',
selector: '.card__body'
},
imageAlt: {
attribute: 'alt',
selector: '.card__image'
},
imageUrl: {
attribute: 'src',
selector: '.card__image'
}
},
edit({ attributes, className, setAttributes }) {
const getImageButton = (openEvent) => {
if(attributes.imageUrl) {
return (
<img
src={ attributes.imageUrl }
onClick={ openEvent }
className="image"
/>
);
}
else {
return (
<div className="button-container">
<Button
onClick={ openEvent }
className="button button-large"
>
Pick an image
</Button>
</div>
);
}
};
return (
<div className="container">
<MediaUpload
onSelect={ media => { setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
type="image"
value={ attributes.imageID }
render={ ({ open }) => getImageButton(open) }
/>
<PlainText
onChange={ content => setAttributes({ title: content }) }
value={ attributes.title }
placeholder="Your card title"
className="heading"
/>
<RichText
onChange={ content => setAttributes({ body: content }) }
value={ attributes.body }
multiline="p"
placeholder="Your card text"
formattingControls={ ['bold', 'italic', 'underline'] }
isSelected={ attributes.isSelected }
/>
</div>
);
},
save({ attributes }) {
const cardImage = (src, alt) => {
if(!src) return null;
if(alt) {
return (
<img
className="card__image"
src={ src }
alt={ alt }
/>
);
}
// No alt set, so let's hide it from screen readers
return (
<img
className="card__image"
src={ src }
alt=""
aria-hidden="true"
/>
);
};
return (
<div className="card">
{ cardImage(attributes.imageUrl, attributes.imageAlt) }
<div className="card__content">
<h3 className="card__title">{ attributes.title }</h3>
<div className="card__body">
{ attributes.body }
</div>
</div>
</div>
);
}
});
Запустим webpack. Находясь в текущей директории плагинов в терминале, запустите это:
npx webpack --watch
Это немного отличается от предыдущей части серии, потому что мы добавили --watch
аргумент. Он в основном следит за вашими js
файлами и перезапускает webpack
если они меняются.
Откроем редактор
Давайте загрузим редактор Gutenberg, загрузив сообщение в бэкэнд WordPress. В редакторе Gutenberg, нажмите на маленький значок плюса и посмотрите на вкладку «блоки», и вот он, наш новый блок карточки:

Добавьте туда немного контента.
Вот короткое видео:
Давайте перечислим то, что вы узнали в этой серии:
- Вы изучили некоторые основы современного JavaScript, освоив ES6
- Вы узнали основы JSX
- Вы взяли эти новые знания и создали компонент React .
- Затем вы узнали, как работает webpack, и написали масштабируемый конфигурационный файл для разработки блоков Gutenberg.
- Наконец, вы создали пользовательский блок карточки Gutenberg.