Понимание того, как динамически манипулировать объектной моделью документа (DOM), является ключевым навыком в веб-разработке. DOM действует как связующее звено между интерактивным пользовательским интерфейсом на основе экрана и структурным представлением веб-контента.

В этой статье я расскажу о проблеме рендеринга DOM, которая была частью недавнего интервью по интерфейсу. Я сосредоточусь на ясности и понимании, поскольку мы разбираем каждую часть решения одну за другой.

Вопрос. Создайте DomRenderer, способный отображать предоставленный объект DOM, используя следующий вызов метода:

// Object of DOM elements
const app = {
    type: "div",
    props: {
        id: "app",
        children: [
            {
                type: "h1",
                props: {
                    class: "header",
                    children: "Hello, World!"
                }
            },
            {
                type: "h2",
                props: {
                    class: "sub-header",
                    children: "I'm a subtitle!"
                }
            },
            {
                type: "div",
                props: {
                    class: "content",
                    children: [
                        {
                            type: "span",
                            props: {
                                class: "caption-red",
                                children: "This is a general caption 1-1"
                            }
                        },
                        {
                            type: "div",
                            props: {
                                class: "caption",
                                children: [
                                     {
                                        type: "span",
                                        props: {
                                            class: "caption-blue",
                                            children:
                                                "This is an secondary caption 2-1"
                                        }
                                    },
                                    {
                                        type: "span",
                                        props: {
                                            class: "caption-blue",
                                            children:
                                                "This is an secondary caption 2-2"
                                        }
                                    },
                                ]
                            }
                        }
                    ]
                }
            }
        ]
    }
};

/*
Todo: Build a DomRenderer that can render the above DOM object with this invocation:
const renderer = new DomRenderer();
renderer.render(app, document.body);
*/

class DomRenderer {
    render(app, body) {
        let rootElement = document.createElement(app.type);
        
        this.traverseAndAppendChildren(app, rootElement)
        
        body.appendChild(rootElement);
    }
    
    //todo
    traverseAndAppendChildren(segment, ele) {
      
    }
}


const renderer = new DomRenderer();
renderer.render(app, document.body);

Решение. Процесс рендеринга рекурсивно проходит через иерархию, создавая элементы DOM и добавляя их к своим родительским элементам.

  traverseAndAppendChildren(segment, ele) {
        const createdEl = document.createElement(segment.type);
       
        if(segment.props.id) {
            createdEl.setAttribute("id", segment.props.id)
        } else {
            createdEl.setAttribute("class", segment.props.class)
        }
       
        if(typeof segment.props.children === "string") {
            createdEl.textContent = segment.props.children;
        } else {
            for (let objOfArr of segment.props.children) {
                this.traverseAndAppendChildren(objOfArr, createdEl);
            }
        }
       
        ele.appendChild(createdEl);
    }
}

Пояснение:

1. Структура объекта: объект приложения определяет иерархическую структуру элементов DOM. Каждый элемент представлен как объект с типом, свойствами и, необязательно, дочерними элементами.

2. Класс DomRenderer: класс DomRenderer инкапсулирует процесс рендеринга. Он содержит два метода: render и traverseAndAppendChildren.

3. Метод рендеринга. Этот метод вызывается с объектом приложения и целевым элементом DOM (телом). Он создает корневой элемент, указанный в объекте приложения, используя свойство типа. Затем он вызывает метод traverseAndAppendChildren для заполнения корневого элемента дочерними элементами, определенными в объекте приложения.

4. Метод traverseAndAppendChildren:

Метод traverseAndAppendChildren является важным компонентом класса DomRenderer, который упрощает рекурсивное построение элементов DOM на основе иерархической структуры, определенной в объекте приложения. Его роль заключается в навигации по объекту приложения и его вложенным сегментам, создании соответствующих элементов DOM и добавлении их к родительским элементам в структурированном виде.

Вот более подробное описание того, как работает метод traverseAndAppendChildren:

а) Параметры:

• сегмент: этот параметр представляет текущий сегмент обрабатываемого объекта приложения. Он инкапсулирует информацию о конкретном элементе DOM, который необходимо создать.

• ele: этот параметр представляет родительский элемент DOM, к которому должно быть добавлено содержимое текущего сегмента.

b) Создание элементов DOM. Для каждого сегмента метод начинается с создания нового элемента DOM с использованием свойства type сегмента. Этот элемент будет представлять определенный тип элемента HTML, определенный в объекте приложения (например, div, h1, span и т. д.).

c) Установка атрибутов:

• Если текущий сегмент имеет свойство id, метод устанавливает для атрибута id только что созданного элемента DOM значение свойства id. Это помогает однозначно идентифицировать элемент в документе.

• Если свойство id отсутствует, метод предполагает, что сегмент имеет только свойство класса, определенное в объекте props. В этом случае он устанавливает атрибут класса элемента DOM, используя значение свойства класса.

d) Обработка содержания:

• Объект props внутри каждого сегмента содержит дочернее свойство, которое может быть либо строкой, либо массивом дочерних сегментов.

• Если свойство Children имеет строковый тип, это означает, что содержимое текущего элемента DOM должно быть текстовым.

• Если свойство children является массивом, это означает, что текущий элемент DOM имеет дочерние элементы. Метод перебирает каждый дочерний сегмент в массиве и рекурсивно вызывает метод traverseAndAppendChildren для создания и добавления дочерних элементов DOM.

e) Добавление к родительскому элементу: после того, как createdEl (элемент DOM, представляющий текущий сегмент) правильно сконфигурирован с атрибутами и содержимым, он добавляется к параметру ele, который является родительским элементом DOM, передаваемым в метод.

5. Процесс рендеринга:

Метод рендеринга инициализирует корневой элемент, используя свойство type объекта приложения. Он вызывает traverseAndAppendChildren для заполнения корневого элемента иерархией дочерних элементов на основе свойства children объекта приложения. Процесс рендеринга рекурсивно проходит через иерархию, создавая элементы DOM и добавляя их к своим родительским элементам. Наконец, корневой элемент, содержащий всю иерархию, присоединяется к целевому элементу DOM (телу).

В результате структура объекта приложения преобразуется в реальные элементы DOM на веб-странице.

Это решение демонстрирует, как использовать JavaScript для управления DOM, динамического создания элементов, обхода иерархических структур данных и обработки различных атрибутов и дочерних элементов. Это практический пример того, как мы можем программно генерировать и отображать сложные пользовательские интерфейсы на веб-страницах.