- Оригинальный адрес:Build a Drag and Drop layout builder with React and ImmutableJS
- Оригинальный автор:Chris Kitson
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:fireairforce
- Корректор:Eternaldeath, portandbridge
Создайте компоновщик макетов с перетаскиванием (DnD) с помощью React и ImmutableJS.
『перетащить』 Существует огромный пользовательский спрос на такие действия, как создание веб-сайта (Wix) или интерактивного приложения (Trello). Нет сомнений в том, что этот тип взаимодействия создает очень крутой пользовательский опыт. В сочетании с некоторыми из новейших технологий пользовательского интерфейса мы можем создать действительно хорошее программное обеспечение.
Какова конечная цель этой статьи?
Я хочу создать компоновщик макетов с помощью перетаскивания, который позволяет пользователям создавать макеты с использованием ряда настраиваемых компонентов пользовательского интерфейса, в результате чего получается веб-сайт или веб-приложение.
Какие библиотеки мы будем использовать?
- React
- ImmutableJS
Уделите немного времени ниже, чтобы объяснить роль, которую они играют в создании этого проекта.
React
ReactНа основе декларативного программирования, что означает, что рендеринг основан на состоянии. Состояние — это просто объект JSON со свойствами, которые сообщают React, как он должен отображаться (стили и функциональность). В отличие от библиотек, которые манипулируют DOM (например, jQuery), мы не изменяем DOM напрямую, а модифицируя состояние и позволяя React позаботиться о DOM (DOM будет рассмотрен позже).
В этом проекте предполагается, что есть родительский компонент, который хранит состояние макета (объект JSON), и это состояние будет передаваться другим компонентам, которые в React являются компонентами без состояния.
Роль этих компонентов состоит в том, чтобы получить состояние от родительского компонента, а затем отобразить себя на основе его свойств.
Ниже приведен простой пример состояния объекта, имеющего тройную связь:
{
links: [{
name: "Link 1",
url: "http://link.one",
selected: false
}, {
name: "Link 2",
url: "http://link.two",
selected: true
}, {
name: "Link 3",
url: "http://link.three",
selected: false
}]
}
В приведенном выше примере мы можем выполнить итерацию по массиву ссылок, чтобы создать компонент без сохранения состояния для каждого элемента:
interface ILink {
name: string;
url: string;
selected: boolean;
}
const LinkComponent = ({ name, url, selected }: ILink) =>
<a href={url} className={selected ? 'selected': ''}>{name}</a>;
Вы можете видеть, как мы применяем класс css «selected» к компоненту массива ссылок на основе выбранного свойства, сохраненного в состоянии. Вот что отображается в браузере:
<a href="http://link.two" class="selected">Link 2</a>
ImmutableJS
Мы видели важность состояния в нашем проекте, это то, как рендерятся компоненты React.Единственный источник правды. Состояние в React хранится в неизменяемых структурах данных.
Короче говоря, это означает, что после создания объекта данныхпросто не могуПросто иди и измени его. Если только мы не создадим новый объект с измененным состоянием.
Давайте проиллюстрируем неизменность на другом простом примере:
interface ILink {
name: string;
url: string;
selected: boolean;
}
const link: ILink = {
name: "Link 1",
url: "http://link.one",
selected: false
}
В традиционном JavaScript вы бы обновили объект ссылки, выполнив следующие действия:
link.name = 'New name';
Если наше состояние неизменяемое, то описанная выше операция невозможна, тогда мы должны создать новый объект, свойство имени которого было изменено.
link = {...link, name: 'New name' };
Примечание. Для поддержки неизменности React предоставляет нам методthis.setState()
, мы можем использовать его, чтобы сообщить компоненту, что состояние изменилось и что компоненту необходимо выполнить повторную визуализацию, если какое-либо состояние изменится.
Это всего лишь базовый пример, но что, если вы хотите изменить многоуровневое вложенное свойство в сложной структуре состояния JSON?
ECMA Script 6 предоставляет нам несколько удобных операторов и методов для мутации объектов, но они не подходят для сложных структур данных, а это то, что нам нужно.ImmutableJSдля упрощения задачи.
Позже мы будем использовать ImmutableJS, а сейчас вам просто нужно знать, что он дает нам простые способы изменения сложного состояния.
Перетаскивание HTML5 (DnD)
Итак, мы знаем, что наше состояние — это неизменяемый объект JSON, а React обрабатывает компоненты, но нам нужно веселое взаимодействие с пользователем, верно?
Благодаря HTML5 это на самом деле очень просто, так как он предоставляет методы, которые мы можем использовать для определения того, когда компоненты перетаскиваются и куда их перетаскивать. Поскольку React предоставляет браузеру собственные HTML-элементы, мы можем использовать собственные методы обработки событий, чтобы упростить нашу реализацию.
Примечание. Мне сказали, что могут быть некоторые проблемы с DnD, реализованным с использованием HTML5, но если ничего другого, то это может быть справочный курс, и мы можем заменить его позже, если обнаружим проблемы.
В этом проекте у нас есть компоненты (HTML div), которые пользователь может перетаскивать, я назову ихперетаскиваемый компонент.
У нас также есть области, где пользователи могут размещать компоненты, я назову ихРазмещаемые компоненты.
Используйте собственные события HTML5, такие какonDragStart
,onDragOver
а такжеonDragDrop
, у нас также должно быть то, что нам нужно для изменения состояния приложения на основе взаимодействий DnD.
Ниже приведен пример перетаскиваемого компонента:
export interface IDraggableComponent {
name: string;
type: string;
draggable?: boolean;
onDragStart: (ev: React.DragEvent<HTMLDivElement>, name: string, type: string) => void;
}
export const DraggableComponent = ({
name,
type,
onDragStart,
draggable = true
}: IDraggableComponent) =>
<div className='draggable-component' draggable={draggable} onDragStart={(ev) => onDragStart(ev, name, type)}>{name}</div>;
В приведенном выше фрагменте кода мы визуализируем компонент React, который используетonDragStart
Событие сообщает родительскому компоненту, что мы начинаем перетаскивать компонент. Мы также можем пройтиdraggable
Свойство, чтобы переключить способность тащить ее.
Ниже приведен пример размещаемого компонента:
export interface IDroppableComponent {
name: string;
onDragOver: (ev: React.DragEvent<HTMLDivElement>) => void;
onDrop: (ev: React.DragEvent<HTMLDivElement>, componentName: string) => void;
}
export const DroppableComponent = ({
name,
onDragOver,
onDrop
}: IDroppableComponent) =>
<div className='droppable-component'
onDragOver={(ev: React.DragEvent<HTMLDivElement>) => onDragOver(ev)}
onDrop={(ev: React.DragEvent<HTMLDivElement>) => onDrop(ev, name)}>
<span>Drop components here!</span>
</div>;
В приведенном выше компоненте мы слушаемonDrop
события, поэтому мы можем обновлять состояние на основе новых компонентов, помещенных в размещаемые объекты.
Хорошо, время для быстрого подведения итогов и объединения всего этого:
Мы будем визуализировать весь макет, используя несколько несвязанных компонентов без состояния, основанных на объектах состояния в React. Взаимодействие с пользователем будет обрабатываться событиями HTML5 DnD, а время будет использовать ImmutableJS для запуска изменений в объектах состояния.
перетащить все
Теперь, когда у нас есть четкое представление о том, что делать и как с ними бороться, давайте рассмотрим некоторые из наиболее важных частей головоломки:
- состояние макета
- Перетащите компоненты конструктора
- Рендеринг вложенных компонентов внутри сетки
1. Состояние макета
Чтобы наши компоненты представляли бесконечные комбинации макетов, состояние должно быть гибким и расширяемым. Что нам также нужно иметь в виду, так это то, что если мы хотим представить дерево DOM любого заданного макета, это означает много приятной рекурсии для поддержки вложенных структур!
Нашему состоянию необходимо хранить большое количество компонентов, которые могут быть представлены следующими интерфейсами:
Если вы не знакомы с интерфейсами в JavaScript, вам следует взглянутьTypeScript— Вы, наверное, можете сказать, что я фанат этого. Он отлично работает с React.
export interface IComponent {
name: string;
type: string;
renderProps?: {
size?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
};
children?: IComponent[];
}
Я оставлю определение компонента минимальным, но вы можете расширить его по мере необходимости. я здесьrenderProps
Это определяет объект, поэтому мы можем предоставить компоненту состояние, чтобы сообщить ему, как отображать,children
Свойство дает нам рекурсию.
Для более высокого уровня я бы создал массив объектов для хранения компонентов, которые появятся в корне состояния.
Чтобы проиллюстрировать это, мы рекомендуем следующие макеты для разметки в HTML:
<div class="content-panel-1">
<div class="component">
Component 1
</div>
<div class="component">
Component 2
</div>
</div>
<div class="content-panel-2">
<div class="component">
Component 3
</div>
</div>
Чтобы представить это в состоянии, мы можем определить интерфейс для панели содержимого, который выглядит следующим образом:
export interface IContent {
id: string;
cssClass: string;
components: IComponent[];
}
Тогда наше состояние станет примерно такимIContent
множество:
const state: IContent[] = [
{
id: 'content-panel-1',
cssClass: 'content-panel-1',
components: [{
type: 'component1',
renderProps: {},
children: []
},
{
type: 'component2',
renderProps: {},
children: []
}]
},
{
id: 'content-panel-2',
cssClass: 'content-panel-2',
components: [{
type: 'component3',
renderProps: {},
children: []
}]
}
];
вchildren
Поместив другие компоненты в свойство массива, мы можем определить другие компоненты для создания вложенных древовидных структур, подобных DOM:
[0]
components:
[0]
children:
[0]
children:
[0]
...
2. Перетащите конструктор макетов
Компонент построителя макета будет выполнять ряд функций, таких как:
- Поддерживать и обновлять состояние компонента
- оказыватьперетаскиваемый компонента такжеРазмещаемые компоненты
- Отрисовка вложенных структур макета
- Инициировать события DnD HTML5
Код наверное такой:
export class BuilderLayout extends React.Component {
public state: IBuilderState = {
dashboardState: []
};
constructor(props: {}) {
super(props);
this.onDragStart = this.onDragStart.bind(this);
this.onDragDrop = this.onDragDrop.bind(this);
}
public render() {
}
private onDragStart(event: React.DragEvent <HTMLDivElement>, name: string, type: string): void {
event.dataTransfer.setData('id', name);
event.dataTransfer.setData('type', type);
}
private onDragOver(event: React.DragEvent<HTMLDivElement>): void {
event.preventDefault();
}
private onDragDrop(event: React.DragEvent <HTMLDivElement>, containerId: string): void {
}
}
Давайте пока проигнорируемrender()
функции, мы скоро увидим это снова.
У нас есть три события, которые мы привяжем к нашим компонентам «Dragable» и «Droppable».
onDragStart()
——Это событие, здесь мы устанавливаем некоторую информацию оevent
Детали компонентов в объекте, т.е.name
а такжеtype
.
onDragOver()
- С этим событием мы сейчас ничего делать не будем, по факту пропускаем.preventDefault()
Функция отключает поведение браузера по умолчанию.
Это оставляетonDragDrop()
События, это магия изменения неизменяемого состояния. Для того, чтобы изменить состояние, нам нужно несколько частей информации:
- Имя компонента для размещения --
name
существуетevent
набор объектовonDragStart()
. - Тип компонента для размещения --
type
существуетevent
набор объектовonDragStart()
. - где размещаются компоненты —
containerId
Передайте этот метод из удаляемого компонента.
существуетcontainerId
должен сообщить нам, где должен быть размещен новый компонент в состоянии. Вероятно, есть более чистый способ сделать это, но для описания местоположения я буду использовать список индексов, разделенных подчеркиванием.
Просмотрите нашу модель состояния:
[index]
components:
[index]
children:
[index]
children:
[index]
...
Представлено в строковом формате какcb_index_index_index_index
.
Номер индекса здесь описывает уровень глубины вложенной структуры, где компонент должен быть удален.
Теперь нам нужно позвонитьimmutableJSМощные функции, которые помогут нам изменить состояние приложения. Мы будемonDragDrop()
метод, который может выглядеть так:
private onDragDrop(event: React.DragEvent <HTMLDivElement>, containerId: string) {
const name = event.dataTransfer.getData('id');
const type = event.dataTransfer.getData('type');
const newComponent: IComponent = this.generateComponent(name, type);
const containerArray: string[] = containerId.split('_');
containerArray.shift(); // 忽略第一个参数,它是字符串前缀
const componentsPath: Array<number | string> = [] containerArray.forEach((id: string, index: number) => {
componentsPath.push(parseInt(id, INT_LENGTH));
componentsPath.push(index === 0 ? 'components' : 'children');
});
const { dashboardState } = this.state;
let componentState = fromJS(dashboardState);
componentState = componentState.setIn(componentsPath, componentState.getIn(componentsPath).push(newComponent));
this.setState({ dashboardState: componentState.toJS() });
}
Функциональность здесь происходит от того, что ImmutableJS предоставляет нам.setIn()
а также.getIn()
метод.
Они берут набор строк/значений, чтобы определить, где во вложенной модели состояния получить или установить значение. Это хорошо согласуется с тем, как мы генерируем размещаемые идентификаторы. Круто, правда?
fromJS()
а такжеtoJS()
Метод преобразует объект JSON в объект ImmutableJS и обратно.
Об ImmutableJS написано много, и я, возможно, напишу о нем отдельный пост в будущем. Извините, это всего лишь краткое введение!
3. Рендеринг вложенных компонентов внутри сетки
Наконец-то давайте посмотрим на методы рендеринга, упомянутые ранее. Я хочу поддержать систему GRID CSS, аналогичнуюMaterial responsive gridчтобы сделать наш макет более гибким. Он использует сетку из 12 столбцов, чтобы определить макет HTML следующим образом:
<div class="mdc-layout-grid">
<div class="mdc-layout-grid__inner">
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-6">
Left column
</div>
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-6">
Right column
</div>
</div>
</div>
Объединив это с вложенной структурой, которую представляет наше состояние, мы можем получить очень мощный построитель компоновки.
Прямо сейчас я просто исправляю размер сетки для макета с двумя столбцами (т.е. рекурсии, которую имеют два столбца в одном удаляемом компоненте).
Для этого у нас есть сетка перетаскиваемых компонентов, которая будет содержать два выпадающих элемента (по одному для каждого столбца).
Вот тот, который я создал ранее:
выше у меня естьGrid, Первый столбецCard, во втором столбце естьHeading.
Теперь я помещаю еще один в первый столбецGrid, есть один в первом столбцеHeading, во втором столбце естьCard.
Ты понимаешь это?
Вот пример того, как этого добиться с помощью псевдокода React:
-
прокручивать элементы содержимого (корень нашего состояния) и отображать
ContentBuilderDraggableComponent
с однимDroppableComponent
. -
Определите, относится ли компонент к типу Grid, затем визуализируйте
ContentBuilderGridComponent
, в противном случае отобразите обычныйDraggableComponent
. -
Визуализирует компонент Grid, отмеченный X подэлементами, по одному в каждом подэлементе.
ContentBuilderDraggableComponent
с однимDroppableComponent
.
class ContentBuilderComponent... {
render() {
return (
<ContentComponent>
components.map(...) {
<ContentBuilderDraggableComponent... />
}
<DroppableComponent... />
</ContentComponent>
)
}
}
class ContentBuilderDraggableComponent... {
render() {
if (type === GRID) {
return <ContentBuilderGridComponent... />
} else {
return <DraggableComponent ... />
}
}
}
class ContentBuilderGridComponent... {
render() {
<GridComponent...>
children.map(...) {
<GridItemComponent...>
gridItemChildren.map(...) {
<ContentBuilderDraggableComponent... />
<DroppableComponent... />
}
</GridItemComponent>
}
</GridComponent>
}
}
Что дальше?
Мы закончили эту статью, но я немного расширю ее в будущем. Вот несколько идей:
- Настройте параметры рендеринга компонента
- Сделайте компонент сетки настраиваемым
- Создание макетов HTML из сохраненных объектов состояния с использованием рендеринга на стороне сервера.
Надеюсь, вы сможете подписаться на меня, если нет, вот мой рабочий пример на GitHub, надеюсь, вам понравится. chriskitson/реагировать на перетаскивание построителя компоновки Конструктор макетов пользовательского интерфейса с перетаскиванием (DnD) с React и ImmutableJSgithub.com
Спасибо, что нашли время, чтобы прочитать мою статью.
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.