Те из нас, кто занимается веб-разработкой более нескольких лет, вероятно, писали код с использованием более чем одного JavaScript-фреймворка. Со всеми возможными вариантами — React, Svelte, Vue, Angular, Solid — это практически неизбежно. Одна из самых неприятных вещей, с которой нам приходится сталкиваться при работе с разными фреймворками, — это воссоздание всех этих низкоуровневых компонентов пользовательского интерфейса: кнопок, вкладок, раскрывающихся списков и т. д. Что особенно расстраивает, так это то, что мы обычно определяем их в одном фреймворке. , скажем, React, но тогда нужно их переписать, если мы хотим построить что-то в Svelte. Или Вью. Или Солид. И так далее.
Не было бы лучше, если бы мы могли определить эти низкоуровневые компоненты пользовательского интерфейса один раз, независимо от фреймворка, а затем повторно использовать их между фреймворками? Конечно, было бы! И мы можем; веб-компоненты - это путь. Этот пост покажет вам, как это сделать.
На данный момент истории SSR для веб-компонентов немного не хватает. Декларативное теневое DOM (DSD) — это то, как веб-компонент визуализируется на стороне сервера, но на момент написания этой статьи он не интегрирован с вашими любимыми платформами приложений, такими как Next, Remix или SvelteKit. Если это требование для вас, обязательно проверьте последний статус DSD. Но в остальном, если вы не используете SSR, читайте дальше.
Сначала немного контекста
Веб-компоненты — это, по сути, HTML-элементы, которые вы определяете сами, например <yummy-pizza>
или что-то еще, с нуля. Они описаны повсюду здесь, в CSS-Tricks (включая обширная серия Калеба Уильямса и один от Джона Рея), но мы кратко рассмотрим этот процесс. По сути, вы определяете класс JavaScript, наследуете его от HTMLElement
, а затем определите все свойства, атрибуты и стили веб-компонента и, конечно же, разметку, которую он в конечном итоге будет отображать для ваших пользователей.
Возможность определять пользовательские элементы HTML, которые не привязаны к какому-либо конкретному компоненту, очень интересна. Но эта свобода есть и ограничение. Существование независимо от какой-либо среды JavaScript означает, что вы не можете взаимодействовать с этими средами JavaScript. Подумайте о компоненте React, который извлекает некоторые данные, а затем отображает некоторые из них. другие Компонент React, передавая данные. На самом деле это не будет работать как веб-компонент, поскольку веб-компонент не знает, как отображать компонент React.
Веб-компоненты особенно хороши в качестве листовые компоненты. Компоненты листа это последнее, что нужно визуализировать в дереве компонентов. Это компоненты, которые получают некоторые реквизиты и отображают некоторые UI, Эти не компоненты, находящиеся в середине вашего дерева компонентов, передающие данные, устанавливающие контекст и т. д. — просто кусочки чистой воды. UI это будет выглядеть одинаково, независимо от того, какой JavaScript-фреймворк поддерживает остальную часть приложения.
Веб-компонент, который мы создаем
Вместо того, чтобы создавать что-то скучное (и обычное), например, кнопку, давайте создадим что-то немного другое. В моем загрузка сообщение мы рассмотрели использование размытых предварительных просмотров изображений, чтобы предотвратить перекомпоновку контента и предоставить пользователям достойный пользовательский интерфейс во время загрузки наших изображений. Мы посмотрели, как base64 кодирует размытые, ухудшенные версии наших изображений и показывает это в нашем пользовательском интерфейсе, пока загружается реальное изображение. Мы также рассмотрели создание невероятно компактных размытых превью с помощью инструмента под названием Блурхэш.
В этом посте показано, как создавать эти превью и использовать их в проекте React. Этот пост покажет вам, как использовать эти предварительные просмотры из веб-компонента, чтобы их могли использовать любой Фреймворк JavaScript.
Но нам нужно пройтись, прежде чем мы сможем бежать, поэтому сначала мы пройдемся по чему-нибудь тривиальному и глупому, чтобы увидеть, как именно работают веб-компоненты.
Все в этом посте будет создавать ванильные веб-компоненты без каких-либо инструментов. Это означает, что код будет немного шаблонным, но его будет относительно легко понять. Такие инструменты, как Lit or трафарет предназначены для создания веб-компонентов и могут быть использованы для удаления большей части этого шаблонного кода. Я призываю вас проверить их! Но для этого поста я предпочитаю немного больше шаблонного кода в обмен на то, что мне не нужно будет вводить и учить еще одну зависимость.
Простой компонент счетчика
Давайте создадим классический «Hello World» из компонентов JavaScript: счетчик. Мы отобразим значение и кнопку, которая увеличивает это значение. Просто и скучно, но это позволит нам взглянуть на самый простой веб-компонент.
Чтобы создать веб-компонент, первым шагом является создание класса JavaScript, который наследуется от HTMLElement
:
class Counter extends HTMLElement {}
Последний шаг — зарегистрировать веб-компонент, но только если мы его еще не зарегистрировали:
if (!customElements.get("counter-wc")) { customElements.define("counter-wc", Counter);
}
И, конечно же, визуализировать:
<counter-wc></counter-wc>
И все, что между ними, это то, что мы заставляем веб-компонент делать то, что мы хотим. Одним из распространенных методов жизненного цикла является connectedCallback
, который срабатывает, когда наш веб-компонент добавляется в DOM. Мы могли бы использовать этот метод для рендеринга любого контента, который нам нужен. Помните, что это класс JS, наследуемый от HTMLElement
, значит наш this
value — это сам элемент веб-компонента со всеми обычными методами манипулирования DOM, которые вы уже знаете и любите.
В самом простом случае мы могли бы сделать это:
class Counter extends HTMLElement { connectedCallback() { this.innerHTML = "<div style='color: green'>Hey</div>"; }
} if (!customElements.get("counter-wc")) { customElements.define("counter-wc", Counter);
}
…который будет работать просто отлично.
Добавление реального контента
Давайте добавим полезный интерактивный контент. Нам нужно <span>
для хранения текущего числового значения и <button>
для увеличения счетчика. На данный момент мы создадим этот контент в нашем конструкторе и добавим его, когда веб-компонент фактически находится в DOM:
constructor() { super(); const container = document.createElement('div'); this.valSpan = document.createElement('span'); const increment = document.createElement('button'); increment.innerText = 'Increment'; increment.addEventListener('click', () => { this.#value = this.#currentValue + 1; }); container.appendChild(this.valSpan); container.appendChild(document.createElement('br')); container.appendChild(increment); this.container = container;
} connectedCallback() { this.appendChild(this.container); this.update();
}
Если вам действительно не нравится ручное создание DOM, помните, что вы можете установить innerHTML
или даже один раз создать элемент шаблона в качестве статического свойства класса веб-компонента, клонировать его и вставить содержимое для новых экземпляров веб-компонента. Возможно, есть какие-то другие варианты, о которых я не думаю, или вы всегда можете использовать структуру веб-компонентов, например Lit or трафарет. Но в этом посте мы продолжим упрощать.
Двигаясь дальше, нам нужно устанавливаемое свойство класса JavaScript с именем value
#currentValue = 0; set #value(val) { this.#currentValue = val; this.update();
}
Это просто стандартное свойство класса с установщиком, а также второе свойство для хранения значения. Один забавный момент заключается в том, что для этих значений я использую синтаксис свойства частного класса JavaScript. Это означает, что никто за пределами нашего веб-компонента не может касаться этих значений. Это стандартный JavaScript это поддерживается во всех современных браузерах, так что не бойтесь использовать его.
Или не стесняйтесь называть это _value
Если вы предпочитаете. И, наконец, наш update
Метод:
update() { this.valSpan.innerText = this.#currentValue;
}
Оно работает!
Очевидно, что это не тот код, который вы хотели бы поддерживать в масштабе. Вот полный рабочий пример если вы хотите посмотреть поближе. Как я уже говорил, такие инструменты, как Lit и Stencil, призваны упростить эту задачу.
Добавляем еще немного функционала
Этот пост не является глубоким погружением в веб-компоненты. Мы не будем охватывать все API и жизненные циклы; мы даже не будем покрывать теневые корни или слоты. На эти темы есть бесконечный контент. Моя цель здесь состоит в том, чтобы предоставить достаточно приличное введение, чтобы вызвать некоторый интерес, наряду с некоторыми полезными указаниями на самом деле. через веб-компоненты с популярными платформами JavaScript, которые вы уже знаете и любите.
С этой целью давайте немного улучшим наш веб-компонент счетчика. Пусть он примет color
атрибут, чтобы управлять цветом отображаемого значения. И пусть он также принимает increment
свойство, поэтому потребители этого веб-компонента могут увеличивать его на 2, 3, 4 за раз. И чтобы управлять этими изменениями состояния, давайте воспользуемся нашим новым счетчиком в песочнице Svelte — мы немного перейдем к React.
Мы начнем с того же веб-компонента, что и раньше, и добавим атрибут цвета. Чтобы настроить наш веб-компонент для приема атрибута и ответа на него, мы добавляем статический observedAttributes
свойство, которое возвращает атрибуты, которые прослушивает наш веб-компонент.
static observedAttributes = ["color"];
Имея это на месте, мы можем добавить attributeChangedCallback
метод жизненного цикла, который будет запускаться всякий раз, когда любой из атрибутов, перечисленных в observedAttributes
устанавливаются или обновляются.
attributeChangedCallback(name, oldValue, newValue) { if (name === "color") { this.update(); }
}
Теперь мы обновляем наш update
способ его фактического использования:
update() { this.valSpan.innerText = this._currentValue; this.valSpan.style.color = this.getAttribute("color") || "black";
}
Наконец, давайте добавим наш increment
имущество:
increment = 1;
Простой и скромный.
Использование компонента счетчика в Svelte
Давайте воспользуемся тем, что мы только что сделали. Мы перейдем к нашему компоненту приложения Svelte и добавим что-то вроде этого:
<script> let color = "red";
</script> <style> main { text-align: center; }
</style> <main> <select bind:value={color}> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </select> <counter-wc color={color}></counter-wc>
</main>
И это работает! Наш счетчик отображает, увеличивает, а раскрывающийся список обновляет цвет. Как видите, мы визуализируем атрибут цвета в нашем шаблоне Svelte, и при изменении значения Svelte выполняет всю работу по вызову setAttribute
в нашем базовом экземпляре веб-компонента. Здесь нет ничего особенного: то же самое уже сделано для атрибутов любой HTML-элемент.
Все становится немного интереснее с increment
опора Это не атрибут нашего веб-компонента; это реквизит класса веб-компонента. Это означает, что его необходимо установить в экземпляре веб-компонента. Потерпите меня, так как через некоторое время все станет намного проще.
Во-первых, мы добавим несколько переменных в наш компонент Svelte:
let increment = 1;
let wcInstance;
Наш мощный компонент счетчика позволит вам увеличить значение на 1 или на 2:
<button on:click={() => increment = 1}>Increment 1</button>
<button on:click={() => increment = 2}>Increment 2</button>
Но, в теории, нам нужно получить фактический экземпляр нашего веб-компонента. Это то же самое, что мы всегда делаем каждый раз, когда добавляем ref
с Реагировать. Со Svelte это просто bind:this
директива:
<counter-wc bind:this={wcInstance} color={color}></counter-wc>
Теперь в нашем шаблоне Svelte мы прослушиваем изменения в переменной приращения нашего компонента и устанавливаем базовое свойство веб-компонента.
$: { if (wcInstance) { wcInstance.increment = increment; }
}
Вы можете проверить это на этой живой демонстрации.
Очевидно, мы не хотим делать это для каждого веб-компонента или объекта, которым нам нужно управлять. Было бы неплохо, если бы мы могли просто установить increment
прямо в нашем веб-компоненте, в разметке, как мы обычно делаем для свойств компонента, и, знаете, просто работай? Другими словами, было бы неплохо, если бы мы могли удалить все случаи использования wcInstance
и вместо этого используйте этот более простой код:
<counter-wc increment={increment} color={color}></counter-wc>
Оказывается, можем. Этот код работает; Svelte берет на себя всю эту работу за нас. Проверьте это в этой демонстрации. Это стандартное поведение практически для всех фреймворков JavaScript.
Так почему же я показал вам ручной способ настройки реквизита веб-компонента? Две причины: полезно понимать, как эти вещи работают, и минуту назад я сказал, что это работает «почти» для всех фреймворков JavaScript. Но есть один фреймворк, который, как ни странно, не поддерживает настройку реквизита веб-компонента, как мы только что видели.
React — другой зверь
Реагировать. Самый популярный фреймворк JavaScript на планете не поддерживает базовое взаимодействие с веб-компонентами. Это известная проблема, уникальная для React. Интересно, что это на самом деле исправлено в экспериментальной ветке React, но по какой-то причине не было объединено в версию 18. Тем не менее, мы все еще можем отслеживать его ход. И вы можете попробовать это сами с помощью демо.
Решение, конечно, состоит в том, чтобы использовать ref
, возьмите экземпляр веб-компонента и вручную установите increment
когда это значение изменится. Это выглядит так:
import React, { useState, useRef, useEffect } from 'react';
import './counter-wc'; export default function App() { const [increment, setIncrement] = useState(1); const [color, setColor] = useState('red'); const wcRef = useRef(null); useEffect(() => { wcRef.current.increment = increment; }, [increment]); return ( <div> <div className="increment-container"> <button onClick={() => setIncrement(1)}>Increment by 1</button> <button onClick={() => setIncrement(2)}>Increment by 2</button> </div> <select value={color} onChange={(e) => setColor(e.target.value)}> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </select> <counter-wc ref={wcRef} increment={increment} color={color}></counter-wc> </div> );
}
Как мы уже говорили, кодирование вручную для каждого свойства веб-компонента просто не масштабируется. Но не все потеряно, потому что у нас есть пара вариантов.
Вариант 1. Используйте атрибуты везде
У нас есть атрибуты. Если вы щелкнули демонстрацию React выше, increment
реквизит не работал, но цвет изменился правильно. Разве мы не можем кодировать все атрибутами? К сожалению нет. Значения атрибутов могут быть только строками. Здесь этого достаточно, и с таким подходом мы сможем продвинуться довольно далеко. Цифры как increment
могут быть преобразованы в и из строк. Мы могли бы даже JSON преобразовать/разобрать объекты. Но в конечном итоге нам нужно будет передать функцию веб-компоненту, и в этот момент у нас не останется вариантов.
Вариант 2: завернуть
Есть старая поговорка, что любую проблему в информатике можно решить, добавив уровень косвенности (за исключением проблемы слишком большого количества уровней косвенности). Код для установки этих реквизитов довольно предсказуем и прост. Что, если мы спрячем его в библиотеке? Умные люди, стоящие за Lit есть одно решение. Эта библиотека создает для вас новый компонент React после того, как вы предоставите ему веб-компонент, и перечисляет необходимые ему свойства. Хотя я умный, я не поклонник этого подхода.
Вместо однозначного сопоставления веб-компонентов с компонентами React, созданными вручную, я предпочитаю просто one Компонент React, который мы передаем нашему веб-компоненту название тэга к (counter-wc
в нашем случае) — вместе со всеми атрибутами и свойствами — и чтобы этот компонент отображал наш веб-компонент, добавьте ref
, затем выясните, что такое свойство и что такое атрибут. Это идеальное решение, на мой взгляд. Я не знаю библиотеки, которая делает это, но ее создание должно быть простым. Давайте попробуем!
Это пользования мы ищем:
<WcWrapper wcTag="counter-wc" increment={increment} color={color} />
wcTag
имя тега веб-компонента; остальные — это свойства и атрибуты, которые мы хотим передать.
Вот как выглядит моя реализация:
import React, { createElement, useRef, useLayoutEffect, memo } from 'react'; const _WcWrapper = (props) => { const { wcTag, children, ...restProps } = props; const wcRef = useRef(null); useLayoutEffect(() => { const wc = wcRef.current; for (const [key, value] of Object.entries(restProps)) { if (key in wc) { if (wc[key] !== value) { wc[key] = value; } } else { if (wc.getAttribute(key) !== value) { wc.setAttribute(key, value); } } } }); return createElement(wcTag, { ref: wcRef });
}; export const WcWrapper = memo(_WcWrapper);
Самая интересная строка в конце:
return createElement(wcTag, { ref: wcRef });
Вот как мы создаем элемент в React с динамическим именем. На самом деле это то, во что React обычно транспилирует JSX. Все наши элементы div преобразуются в createElement("div")
звонки. Обычно нам не нужно вызывать этот API напрямую, но он всегда рядом, когда нам это нужно.
Кроме того, мы хотим запустить эффект макета и пройтись по каждому свойству, которое мы передали нашему компоненту. Мы перебираем их все и проверяем, является ли это свойством с in
check, который проверяет объект экземпляра веб-компонента, а также его цепочку прототипов, которая перехватывает любые геттеры/сеттеры, попадающие в прототип класса. Если такого свойства не существует, предполагается, что оно является атрибутом. В любом случае мы устанавливаем его только в том случае, если значение действительно изменилось.
Если вам интересно, почему мы используем useLayoutEffect
вместо useEffect
, это потому, что мы хотим немедленно запустить эти обновления до того, как наш контент будет отображен. Также обратите внимание, что у нас нет массива зависимостей для нашего useLayoutEffect
; это означает, что мы хотим запустить это обновление на каждый рендер. Это может быть рискованно, поскольку React имеет тенденцию к повторному рендерингу. много. Я улучшаю это, завернув все это в React.memo
. По сути это современная версия React.PureComponent
, что означает, что компонент будет перерисовываться только в том случае, если какой-либо из его фактических реквизитов изменился — и он проверяет, произошло ли это, с помощью простой проверки на равенство.
Единственный риск здесь заключается в том, что если вы передаете свойство объекта, которое вы мутируете напрямую, без повторного назначения, вы не увидите обновлений. Но это крайне не рекомендуется, особенно в сообществе React, так что я бы не стал об этом беспокоиться.
Прежде чем двигаться дальше, я хотел бы высказать одну последнюю вещь. Вы можете быть недовольны тем, как выглядит использование. Опять же, этот компонент используется следующим образом:
<WcWrapper wcTag="counter-wc" increment={increment} color={color} />
В частности, вам может не понравиться передача имени тега веб-компонента в <WcWrapper>
компонент и предпочитают вместо этого @lit-labs/react
выше, который создает новый отдельный компонент React для каждого веб-компонента. Это совершенно справедливо, и я бы посоветовал вам использовать то, что вам наиболее удобно. Но для меня одно преимущество этого подхода заключается в том, что его легко удалять. Если каким-то чудом React объединит правильную обработку веб-компонентов из своей экспериментальной ветки в main
завтра вы сможете изменить приведенный выше код:
<WcWrapper wcTag="counter-wc" increment={increment} color={color} />
…к этому:
<counter-wc ref={wcRef} increment={increment} color={color} />
Возможно, вы могли бы даже написать единственный кодмод, чтобы сделать это везде, а затем удалить <WcWrapper>
вообще. На самом деле, зачеркните это: глобальный поиск и замена с помощью RegEx, вероятно, сработают.
Реализация
Я знаю, кажется, что нужно было проделать путь, чтобы добраться сюда. Если вы помните, наша первоначальная цель состояла в том, чтобы взять код предварительного просмотра изображения, который мы рассматривали в моем загрузка сообщениеи переместите его в веб-компонент, чтобы его можно было использовать в любой среде JavaScript. Отсутствие в React надлежащего взаимодействия добавило много деталей. Но теперь, когда у нас есть приличное представление о том, как создать веб-компонент и использовать его, реализация будет почти антиклиматической.
Я оставлю здесь весь веб-компонент и выделю некоторые интересные фрагменты. Если вы хотите увидеть его в действии, вот рабочая демонстрация. Он будет переключаться между тремя моими любимыми книгами по трем моим любимым языкам программирования. URL-адрес для каждой книги будет каждый раз уникальным, поэтому вы можете увидеть предварительный просмотр, хотя вы, вероятно, захотите ограничить вещи на вкладке «Сеть DevTools», чтобы действительно увидеть, что происходит.
Посмотреть весь код
class BookCover extends HTMLElement { static observedAttributes = ['url']; attributeChangedCallback(name, oldValue, newValue) { if (name === 'url') { this.createMainImage(newValue); } } set preview(val) { this.previewEl = this.createPreview(val); this.render(); } createPreview(val) { if (typeof val === 'string') { return base64Preview(val); } else { return blurHashPreview(val); } } createMainImage(url) { this.loaded = false; const img = document.createElement('img'); img.alt = 'Book cover'; img.addEventListener('load', () => { if (img === this.imageEl) { this.loaded = true; this.render(); } }); img.src = url; this.imageEl = img; } connectedCallback() { this.render(); } render() { const elementMaybe = this.loaded ? this.imageEl : this.previewEl; syncSingleChild(this, elementMaybe); }
}
Во-первых, мы регистрируем интересующий нас атрибут и реагируем на его изменение:
static observedAttributes = ['url']; attributeChangedCallback(name, oldValue, newValue) { if (name === 'url') { this.createMainImage(newValue); }
}
Это приводит к созданию нашего компонента изображения, который будет отображаться только при загрузке:
createMainImage(url) { this.loaded = false; const img = document.createElement('img'); img.alt = 'Book cover'; img.addEventListener('load', () => { if (img === this.imageEl) { this.loaded = true; this.render(); } }); img.src = url; this.imageEl = img;
}
Затем у нас есть наше свойство предварительного просмотра, которое может быть либо нашей строкой предварительного просмотра base64, либо нашим blurhash
пакет:
set preview(val) { this.previewEl = this.createPreview(val); this.render();
} createPreview(val) { if (typeof val === 'string') { return base64Preview(val); } else { return blurHashPreview(val); }
}
Это относится к любой вспомогательной функции, которая нам нужна:
function base64Preview(val) { const img = document.createElement('img'); img.src = val; return img;
} function blurHashPreview(preview) { const canvasEl = document.createElement('canvas'); const { w: width, h: height } = preview; canvasEl.width = width; canvasEl.height = height; const pixels = decode(preview.blurhash, width, height); const ctx = canvasEl.getContext('2d'); const imageData = ctx.createImageData(width, height); imageData.data.set(pixels); ctx.putImageData(imageData, 0, 0); return canvasEl;
}
И, наконец, наш render
Метод:
connectedCallback() { this.render();
} render() { const elementMaybe = this.loaded ? this.imageEl : this.previewEl; syncSingleChild(this, elementMaybe);
}
И несколько вспомогательных методов, чтобы связать все воедино:
export function syncSingleChild(container, child) { const currentChild = container.firstElementChild; if (currentChild !== child) { clearContainer(container); if (child) { container.appendChild(child); } }
} export function clearContainer(el) { let child; while ((child = el.firstElementChild)) { el.removeChild(child); }
}
Это немного более шаблонно, чем нам нужно, если мы создадим это во фреймворке, но преимущество в том, что мы можем повторно использовать это в любом фреймворке, который нам нужен — хотя React пока понадобится обертка, как мы обсуждали .
Шансы и заканчивается
Я уже упоминал обертку Lit React. Но если вы обнаружите, что используете Stencil, он на самом деле поддерживает отдельный выходной конвейер только для React. И хорошие ребята из Microsoft тоже создал что-то похожее на обертку Лита, прикрепленный к библиотеке веб-компонентов Fast.
Как я уже упоминал, все фреймворки, не названные React, будут обрабатывать настройку свойств веб-компонента за вас. Просто обратите внимание, что некоторые из них имеют некоторые особенности синтаксиса. Например, с Solid.js, <your-wc value={12}>
всегда предполагает, что value
это свойство, которое вы можете переопределить с помощью attr
префикс, например <your-wc attr:value={12}>
.
Подведение итогов
Веб-компоненты — это интересная, часто недостаточно используемая часть ландшафта веб-разработки. Они могут помочь уменьшить вашу зависимость от какой-либо отдельной среды JavaScript, управляя вашим пользовательским интерфейсом или «конечными» компонентами. Хотя создание их в виде веб-компонентов — в отличие от компонентов Svelte или React — будет не таким эргономичным, преимуществом является то, что их можно будет широко использовать повторно.
Создание взаимодействующих веб-компонентов, которые работают даже с React первоначально опубликовано CSS-хитрости, Вам следует получить информационный бюллетень.
- '
- "
- 116
- 2D
- a
- О нас
- через
- Действие
- добавленный
- плюс
- Все
- уже
- Несмотря на то, что
- всегда
- Другой
- API
- API
- приложение
- Применение
- подхода
- Атрибуты
- , так как:
- до
- между
- Немного
- Черный
- Книги
- строить
- Строительство
- призывают
- холст
- Привлекайте
- Причины
- цепь
- изменение
- Проверки
- ребенок
- Дети
- выбор
- класс
- классический
- ближе
- код
- Кодирование
- Общий
- сообщество
- компонент
- компоненты
- компьютер
- Информатика
- Потребители
- Container
- содержание
- содержание
- продолжать
- контроль
- может
- Пара
- чехол для варгана
- Создайте
- создали
- создает
- Создающий
- создание
- Текущий
- изготовленный на заказ
- данным
- сделка
- глубоко
- предназначенный
- подробность
- Дев
- застройщиков
- Развитие
- DID
- различный
- непосредственно
- управлять
- Падение
- динамический
- эффект
- элементы
- поощрять
- равенство
- особенно
- по существу
- и т.д
- со временем
- многое
- точно,
- пример
- Excel
- Кроме
- обмена
- захватывающий
- существующий
- обширный
- ярмарка
- вентилятор
- БЫСТРО
- фигура
- конец
- First
- фиксированной
- следовать
- Рамки
- каркасы
- Бесплатно
- Freedom
- от
- полный
- fun
- функция
- порождать
- порождающий
- Глобальный
- цель
- хорошо
- захват
- Зелёная
- обрабатывать
- Управляемость
- произошло
- счастливый
- имеющий
- высота
- помощь
- здесь
- Спрятать
- очень
- держать
- Как
- How To
- HTTPS
- идеальный
- изображение
- изображений
- немедленно
- реализация
- В других
- В том числе
- невероятно
- самостоятельно
- individual
- пример
- интегрированный
- интерактивный
- интерес
- заинтересованный
- совместимость
- IT
- саму трезвость
- JavaScript
- путешествие
- Сохранить
- Основные
- Знать
- пейзаж
- Языки
- последний
- уровень
- уровни
- Библиотека
- жизненные циклы
- Вероятно
- ограничение
- линия
- Список
- Включенный в список
- мало
- жить
- загрузка
- посмотреть
- смотрел
- искать
- любят
- сделанный
- поддерживать
- сделать
- Создание
- управлять
- управления
- руководство
- вручную
- отображение
- Вопрос
- означает
- упомянутый
- методы
- Microsoft
- может быть
- БОЛЕЕ
- самых
- Самые популярные
- двигаться
- перемещение
- потребности
- сеть
- "обычные"
- нормально
- номер
- номера
- Обзор
- Опция
- Опции
- заказ
- Другое
- в противном случае
- пакет
- часть
- особый
- особенно
- Прохождение
- штук
- планета
- Точка
- Популярное
- возможное
- довольно
- предварительный просмотр
- Превью
- частная
- Проблема
- процесс
- Программирование
- языки программирования
- Проект
- свойства
- собственность
- обеспечивать
- RE
- реагировать
- причины
- Получать
- уменьшить
- зарегистрироваться
- зарегистрированный
- оказывает
- ОТДЫХ
- возвращают
- Возвращает
- Снижение
- рискованный
- Run
- Сказал
- то же
- песочница
- масштабируемые
- Шкала
- Наука
- Поиск
- Серии
- набор
- установка
- Shadow
- аналогичный
- просто
- с
- одинарной
- умный
- So
- твердый
- Решение
- РЕШАТЬ
- некоторые
- удалось
- особый
- стандарт
- Начало
- Область
- Статус:
- По-прежнему
- стиль
- поддержка
- Поддержанный
- Поддержка
- Коммутатор
- с
- цель
- тестXNUMX
- Ассоциация
- задача
- вещи
- мышление
- три
- Через
- TIE
- время
- вместе
- завтра
- инструментом
- инструменты
- Темы
- трогать
- поворот
- типично
- ui
- понимать
- созданного
- Обновление ПО
- Updates
- us
- использование
- пользователей
- ценностное
- версия
- Вид
- W
- Web
- Что
- Что такое
- будь то
- в то время как
- ветер
- без
- слова
- Работа
- работает
- работает
- бы
- письмо
- лет
- ВАШЕ