Русский
Русский
English
Статистика
Реклама

Быстрый vds

Перевод Немного о том, как работает виртуальный DOM в React

22.05.2021 14:05:19 | Автор: admin

image


Настоящий или реальный (real) DOM


DOM расшифровывается как Document Object Model (объектная модель документа). Проще говоря, DOM это представление пользовательского интерфейса (user interface, UI) в приложении. При каждом изменении UI, DOM также обновляется для отображения этих изменений. Частые манипуляции с DOM негативно влияют на производительность.


Что делает манипуляции с DOM медленными?


DOM представляет собой древовидной структуру данных. Поэтому изменения и обновления самого DOM являются достаточно быстрыми. Но после изменения обновленный элемент и все его потомки (дочерние элементы) должны быть повторно отрисованы (отрендерены) для обновления UI приложения. Повторный рендеринг очень медленный процесс. Таким образом, чем больше у нас компонентов UI, тем более дорогими с точки зрения производительности являются обновления DOM.


Манипуляции с DOM являются сердцем современного интерактивного веба. К сожалению, они намного медленнее большинства JavaScript-операций. Ситуация усугубляется тем, что многие JavaScript-фреймворки обновляют DOM чаще, чем необходимо.


Допустим, у нас имеется список из 10 элементов. Мы изменяем первый элемент. Большинство фреймворков перестроят весь список. Это в 10 раз больше работы, чем требуется! Только 1 элемент изменился, остальные 9 остались прежними.


Перестроение списка это легкая задача для браузера, но современные веб-сайты могут осуществлять огромное количество манипуляций с DOM. Поэтому неэффективное обновление часто становится серьезной проблемой. Для решения данной проблемы команда React популяризовала нечто под названием виртуальный (virtual) DOM (VDOM).


Виртуальный DOM


В React для каждого объекта настоящего DOM (далее RDOM) существует соответствующий объект VDOM. VDOM это объектное представление RDOM, его легковесная копия. VDOM содержит те же свойства, что и RDOM, но не может напрямую влиять на то, что отображается на экране.


Виртуальный DOM (VDOM) это концепция программирования, где идеальное или виртуальное представление UI хранится в памяти и синхронизируется с реальным DOM, используемая такими библиотеками, как ReactDOM. Данный процесс называется согласованием (reconcilation).

Манипуляции с RDOM являются медленными. Манипуляции с VDOM намного быстрее, поскольку они не отображаются (отрисовываются) на экране. Манипуляции с VDOM похожи на работу с проектом (или планом) здания перед началом его возведения.


Почему VDOM является более быстрым?


Когда в UI добавляются новые элементы, создается VDOM в виде дерева. Каждый элемент является узлом этого дерева. При изменении состояния любого элемента, создается новое дерево. Затем это новое дерево сравнивается (diffed) со старым.


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


На изображениях ниже представлено виртуальное DOM-дерево и процесс согласования.



Красным цветом обозначены узлы, которые были обновлены. Эти узлы представляют элементы UI, состояние которых изменилось. После этого вычисляется разница между предыдущей и текущей версиями виртуального DOM-дерева. Затем все родительское поддерево подвергается повторному рендерингу для представления обновленного UI. Наконец, это обновленное дерево используется для обновления RDOM.



Как React использует VDOM?


После того, как мы рассмотрели, что такое VDOM, настало время поговорить о том, как он используется в React.


1. React использует паттерн проектирования Наблюдатель (observer) и реагирует на изменения состояния


В React каждая часть UI является компонентом и почти каждый компонент имеет состояние (state). При изменении состояния компонента, React обновляет VDOM. После обновления VDOM, React сравнивает его текущую версию с предыдущей. Этот процесс называется поиском различий (diffing).


После обнаружения объектов, изменившихся в VDOM, React обновляет соответствующие объекты в RDOM. Это существенно повышает производительность по сравнению с прямыми манипуляциями DOM. Именно это делает React высокопроизводительной библиотекой JavaScript.


2. React использует механизм пакетного (batch) обновления RDOM


Это также положительно влияет на производительность. Названный механизм предполагает отправку обновлений в виде пакетов (набора, серии) вместо отправки отдельного обновления при каждом изменении состояния.


Повторная отрисовка UI самая затратная часть, React обеспечивает точечную и групповую перерисовку RDOM.


3. React использует эффективный алгоритм поиска различий


React использует эвристический O(n) (линейный) алгоритм, основываясь на двух предположениях:


  1. Два элемента разных типов приводят к построению разных деревьев


  2. Разработчик может обеспечить стабильность элементов между рендерингами посредством пропа key (ключ)



На практике эти предположения являются верными почти во всех случаях.


При сравнении двух деревьев, React начинает с корневых элементов. Дальнейшие операции зависят от типов этих элементов.


Элементы разных типов


  • Если корневые элементы имеют разные типы, React уничтожает старое дерево и строит новое с нуля


  • Вместе со старым деревом уничтожаются все старые узлы DOM. Экземпляры компонента получают componentWillUnmount(). При построении нового дерева, новые узлы DOM встраиваются в DOM. Экземпляры компонента получают сначала UNSAFE_componentWillMount(), затем componentDidMount(). Любое состояние, связанное со старым деревом, утрачивается


  • Любые компоненты, являющиеся дочерними по отношению к корневому, размонтируются, их состояние уничтожается. Например, при сравнении:



<div><Counter /></div><span><Counter /></span>

Старый Counter будет уничтожен и создан заново.


Элементы одинакового типа


При сравнении двух элементов одинакового типа, React смотрит на атрибуты этих элементов. Узлы DOM сохраняются, изменяются только их атрибуты. Например:


<div className="before" title="stuff" /><div className="after" title="stuff" />

После сравнения этих элементов будет обновлен только атрибут className.


После обработки узла DOM, React рекурсивно перебирает всех его потомков.


Рекурсивный перебор дочерних элементов


По умолчанию React перебирает два списка дочерних элементов DOM-узла и генерирует мутацию при обнаружении различий.


Например, при добавлении элемента в конец списка дочерних элементов, преобразование одного дерева в другое работает хорошо:


<ul><li>первый</li><li>второй</li></ul><ul><li>первый</li><li>второй</li><li>третий</li></ul>

React "видит", что в обоих деревьях имеются <li>первый</li> и <li>второй</li>, пропускает их и вставляет в конец <li>третий</li>.


Обычно, вставка элемента в начало списка плохо влияет на производительность. Например, преобразование одного дерева в другое в данном случае будет работать плохо:


<ul><li>первый</li><li>второй</li></ul><ul><li>нулевой</li><li>первый</li><li>второй</li></ul>

React не сможет понять, что <li>первый</li> и <li>второй</li> остались прежними и мутирует каждый элемент.


Использование ключей


Для решения данной проблемы React предоставляет атрибут (проп) key. Когда дочерние элементы имеют ключи, React использует их для сравнения потомков текущего и предыдущего узлов. Например, добавление ключей к элементам из последнего примера сделает преобразование деревьев намного более эффективным:


<ul><li key="1">первый</li><li key="2">второй</li></ul><ul><li key="0">нулевой</li><li key="1">первый</li><li key="2">второй</li></ul>

Теперь React знает, что элемент с ключом 0 является новым, а элементы с ключами 1 и 2 старыми.


На практике в качестве ключей, как правило, используются идентификаторы:


<li key={item.id}>{item.name}</li>

При отсутствии идентификаторов, их всегда можно добавить в модель данных или создать хэш на основе какой-либо части данных. Ключи должны быть уникальными среди соседних элементов, а не глобально.


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


Изменение порядка элементов при использовании индексов в качестве ключей также может привести к проблемам с состоянием элементов. Экземпляры компонента обновляются и повторно используются на основе ключей. Если ключом является индекс, перемещение элемента приведет к изменению ключа. Как результат, состояние компонента для таких вещей, как неуправляемое поле для ввода данных, может смешаться и обновиться неожиданным образом.




Простыми словами: Вы говорите React, в каком состоянии должен находиться UI, и он обеспечивает соответствие DOM этому состоянию. Преимущество такого подхода состоит в том, что вам, как разработчику, не нужно знать, как именно происходит изменение атрибутов, обработка событий и обновление DOM.


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


Поскольку виртуальный DOM это в большей степени паттерн, нежели конкретная технология, данное понятие может означать разные вещи. В мире React виртуальный DOM, обычно, ассоциируется с React-элементами, которые являются объектами, представляющими пользовательский интерфейс. Тем не менее, React также использует внутренние объекты, которые называются волокнами (fibers). В этих объектах хранится дополнительная информация о дереве компонентов. Fiber это новый движок согласования, появившийся в React 16. Его основная цель заключается в обеспечении инкрементального рендеринга VDOM.


Как выглядит VDOM?


Название виртуальный DOM делает концепцию немного магической (мистической). На самом деле, VDOM это обычный JavaScript-объект.


Представим, что у нас имеется такое DOM-дерево:



Это дерево может быть представлено в виде такого объекта:


const vdom = {tagName: 'html',children: [{ tagName: 'head' },{tagName: 'body',children: [{tagName: 'ul',attributes: { class: 'list' },children: [{tagName: 'li',attributes: { class: 'list_item' },textContent: 'Элемент списка',}, // конец li],}, // конец ul],}, // конец body],} // конец html

Это наш VDOM. Как и RDOM, он является объектным представлением HTML-документа (разметки). Однако, поскольку он представляет собой всего лишь объект, мы можем свободно и часто им манипулировать, не прикасаясь к RDOM без крайней необходимости.


Вместо использования одного объекта для всего документа, удобнее разделить его на небольшие секции. Например, мы можем выделить из нашего объекта компонент list, соответствующий неупорядоченному списку:


const list = {tagName: 'ul',attributes: { class: 'list' },children: [{tagName: 'li',attributes: { class: 'list_item' },textContent: 'Элемент списка',},],}

VDOM под капотом


Теперь давайте поговорим о том, как VDOM решает проблему производительности и повторного использования.


Как мы выяснили ранее, VDOM может использоваться для обнаружения конкретных изменений, которые необходимо произвести в DOM. Вернемся к примеру с неупорядоченным списком и внесем в него те же изменения, которые мы делали с помощью DOM API.


Первым делом, нам нужна копия VDOM с изменениями, которые мы хотим осуществить. Поскольку нам не нужно использовать DOM API, мы можем просто создать новый объект.


const copy = {tagName: 'ul',attributes: { class: 'list' },children: [{tagName: 'li',attributes: { class: 'list_item' },textContent: 'Первый элемент списка',},{tagName: 'li',attributes: { class: 'list_item' },textContent: 'Второй элемент списка',},],}

Данная копия используется для создания различия (diff) между оригинальным VDOM (list) и его обновленной версией. Diff может выглядеть следующим образом:


const diffs = [{newNode: {/* новая версия первого элемента списка */},oldNode: {/* оригинальная версия первого элемента списка */},index: {/* индекс элемента в родительском списке */},},{newNode: {/* второй элемент списка */},index: {/* ... */},},]

Данный diff содержит инструкции по обновлению RDOM. После определения всех различий мы можем отправить их в DOM для выполнения необходимых обновлений.


Например, мы можем перебрать все различия и либо добавить нового потомка, либо обновить существующего в зависимости от различия:


const domElement = document.quesrySelector('list')diffs.forEach((diff) => {const newElement = document.createElement(diff.newNode.tagName)/* Добавляем атрибуты ... */if (diff.oldNode) {// Если имеется старая версия, заменяем ее новойdomElement.replaceChild(diff.newNode, diff.oldNode)} else {// Если старая версия отсутствует, создаем новый узелdomElement.append(diff.newNode)}})

Обратите внимание, что это очень упрощенная версия того, как может работать VDOM.


VDOM и фреймворки


Обычно, мы имеем дело с VDOM при использовании фреймворков.


Копцепция VDOM используется такими фреймворками, как React и Vue для повышения производительности обновления DOM. Например, с помощью React наш компонент list может быть реализован следующим образом:


import React from 'react'import ReactDOM from 'react-dom'const list = React.createElement('ul',{ className: 'list' },React.createElement('li', { className: 'list_item' }, 'Элемент списка'))// для создания элементов в React обычно используется специальный синтаксис под названием JSX// const list = <ul className="list"><li className="list_item">Элемент списка</li></ul>ReactDOM.render(list, document.body)

Для обновления списка достаточно создать новый шаблон и снова передать его ReactDOM.render():


const newList = React.createElement('ul',{ className: 'list' },React.createElement('li',{ className: 'list_item' },'Первый элемент списка'),React.createElement('li', { className: 'list_item' }, 'Второй элемент списка'))const timerId = setTimeout(() => {ReactDOM.render(newList, document.body)clearTimeout(timerId)}, 5000)

Поскольку React использует VDOM, даже несмотря на то, что мы повторно рендерим весь список, обновляются только фактически изменившиеся части.



Заключение


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


Подход, используемый Angular, который является фреймворком, благодаря которому одностраничные приложения (single page applications, SPA) обрели столь широкую известность, называется Dirty Model Checking (грязной проверкой моделей). Следует отметить, что DMC и VDOM не исключают друг друга. MVC-фреймворк вполне может использовать оба подхода. В случае с React это не имеет особого смысла, поскольку React это, в конце концов, всего лишь библиотека для слоя представления (view).




Облачные VDS от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Перевод Отслеживание и визуализация положения МКС с помощью 30 строк JavaScript-кода

15.05.2021 14:20:40 | Автор: admin


Предлагаю вашему вниманию перевод этой замечательной статьи.

В этом туториале мы создадим веб-приложение, визуализирующее положение любого спутника, например, Международной Космической Станции (далее МКС), в режиме реального времени (ну, почти).

Мы создадим приложение с нуля и примерим на себя роль настоящего ученого в области ракетостроения.

  • Мы узнаем, где найти данные для отдельного спутника, известные как двухстрочный набор элементов (two-line element set, TLE) (далее ДНЭ)
  • Мы используем библиотеку satellite-js для предсказания орбиты спутника по ДНЭ (это часть напрямую связана с ракетостроением)
  • Мы используем библиотеку CesiumJS для визуализации результата, однако, вы можете использовать любую библиотеку/движок, которые умеют работать с долготой, широтой и высотой

Превью конечного результата:



Здесь мы видим движение МКС по орбите со скоростью, увеличенной в 40 раз. Для того, чтобы увидеть текущее положение МКС, нажмите на иконку часов в верхнем левом углу панели управления.

1. Получение ДНЭ


ДНЭ это формат данных, описывающий движение объекта, вращающегося по орбите вокруг Земли. Он был создан Командованием воздушно-космической обороны Северной Америки (North American Aerospace Defense Command, NORAD). Подробнее об истории его создания можно прочитать здесь.

Имея описание орбиты, мы можем предсказывать локацию, в которой будет находиться спутник, в любой момент времени (см. ниже).

Это означает, что большинство устройств, отслеживающих положение спутников, делают это не совсем в режиме реального времени. Вместо того, чтобы получать непрерывно обновляющиеся данные, как, например, при отслеживании движения автомобиля, они используют последние доступные ДНЭ для предсказания положения объекта в определенный момент времени.

Где нам взять ДНЭ? Глобального реестра с такими данными не существует. За публикацию и обновление этих данных для космического сообщества отвечает тот, кому принадлежит тот или иной спутник (разумеется, если речь не идет о спутнике-шпионе).

Мы можем найти ДНЭ на сайте Space Track, который является реестром Космического командования Вооруженных сил США.

Другой ресурс этот список на CeleStrak (прим. пер.: для доступа к сайту требуется VPN), поддерживаемый доктором T.S. Kelso.

Мы будем использовать последний, поскольку он не требует регистрации. Для того, чтобы найти ДНЭ для МКС, нажмите на ссылку Space Stations.

Первой в списке будет МКС:

ISS (ZARYA)
1 25544U 98067A 21122.75616700 .00027980 00000-0 51432-3 0 9994
2 25544 51.6442 207.4449 0002769 310.1189 193.6568 15.48993527281553


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

На указанном ресурсе можно найти ДНЭ для метеорологических спутников, спутников GPS и даже для глобальной спутниковой системы Starlink, разворачиваемой SpaceX.

2. Предсказание орбиты спутника


Следующим нашим шагом является преобразование ДНЭ в определенную позицию во времени.

Для этого мы будем использовать satellite-js.

Подключаем библиотеку из CDN:




Затем передаем ей ДНЭ и время:

const ISS_TLE =
`1 25544U 98067A 21122.75616700 .00027980 00000-0 51432-3 0 9994
2 25544 51.6442 207.4449 0002769 310.1189 193.6568 15.48993527281553`;
// Инициализируем запись о спутнике с помощью ДНЭ
const satrec = satellite.twoline2satrec(
ISS_TLE.split('\n')[0].trim(),
ISS_TLE.split('\n')[1].trim()
);
// Получаем текущую позицию спутника
const date = new Date();
const positionAndVelocity = satellite.propagate(satrec, date);
const gmst = satellite.gstime(date);
const position = satellite.eciToGeodetic(positionAndVelocity.position, gmst);

console.log(position.longitude); // в радианах
console.log(position.latitude); // в радианах
console.log(position.height); // в км


Теперь у нас имеется текущее положение спутника (new Date()).

Данное положение является результатом построения определенной модели движения спутника. Эта модель называется SGP4/SDP4. Все ДНЭ следуют этой модели.

Если вас интересует, насколько точной является указанная модель, то короткий ответ звучит так: это зависит от нескольких факторов.

Точность ДНЭ зависит от нескольких факторов. Эти факторы включают в себя сенсоры, которые использовались для сбора данных, количество собранных данных о данном типе орбит, условия космической среды и т.д. К сожалению, поскольку названные факторы являются очень разными для каждого элемента набора, точность является не очень высокой. Несмотря на то, что NORAD проводит эксперименты по повышению точности предсказания движения спутников, ни один из методов не является идеальным.


3. Визуализация результата


Теперь у нас имеется возможность получать позицию спутника в заданный момент времени. Мы можем использовать это для анимирования пути спутника.

Но сначала давайте посмотрим, как анимировать отдельную точку в космосе с помощью CesiumJS.

Подключаем библиотеку вместе со стилями:

<script src="http://personeltest.ru/aways/cesium.com/downloads/cesiumjs/releases/1.81/Build/Cesium/Cesium.js"></script><link href="http://personeltest.ru/aways/cesium.com/downloads/cesiumjs/releases/1.81/Build/Cesium/Widgets/widgets.css" rel="stylesheet">


Создаем контейнер:

<div id="cesiumContainer"></div>


Дальше нам нужно инициализировать так называемого обозревателя (viewer). Мы передаем ему несколько дополнительных настроек для отключения функциональности, которая требует наличия токена доступа:

const viewer = new Cesium.Viewer('cesiumContainer', {  imageryProvider: new Cesium.TileMapServiceImageryProvider({    url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),  }),  baseLayerPicker: false, geocoder: false, homeButton: false, infoBox: false,  navigationHelpButton: false, sceneModePicker: false});viewer.scene.globe.enableLighting = true;


Наконец, мы можем визуализировать положение спутника в виде красной точки в космосе:

const satellitePoint = viewer.entities.add({  position: Cesium.Cartesian3.fromRadians(    position.longitude, position.latitude, position.height * 1000  ),  point: { pixelSize: 5, color: Cesium.Color.RED }});


Вот полный код данного шага на Glitch.

4. Анимируем путь


Для анимирования пути нам всего лишь нужно получить еще несколько будущих позиций спутника. CesiumJS поддерживает интерполяцию (переход) между позициями в течение времени из коробки.

Реализация анимации несколько многословна. Вот соответствующий код на Glitch. Ниже описаны самые важные концепции.

Мы создаем SampledPositionProperty. Это объект, содержащий позиции во времени, между которыми осуществляется переход:

const positionsOverTime = new Cesium.SampledPositionProperty();


Мы перебираем позиции в любом количестве, и для каждой позиции создаем объект со временем, который называется JulianDate в CesiumJS, а также саму позицию и добавляем их в качестве образца (sample):

for (let i = 0; i < totalSeconds; i+= timestepInSeconds) {  const time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());  // Получаем позицию с помощью satellite-js  const position = Cesium.Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);  positionsOverTime.addSample(time, position);}


Наконец, мы передаем positionsOverTime в нашу точку:

const satellitePoint = viewer.entities.add({  position: positionsOverTime,  point: { pixelSize: 5, color: Cesium.Color.RED }});


Точка будет двигаться вместе с временной шкалой. Для прикрепления камеры к движущейся точке делаем следующее:

viewer.trackedEntity = satellitePoint;


Заключение


Надеюсь, вам было интересно немного узнать о том, как создаются программы для отслеживания спутников. Конечно, многие вопросы остались без ответа, например, что означает каждый параметр ДНЭ? Как часто они обновляются? Каким именно образом происходит их обновление?

Лично мне было очень интересно узнать о существования таких данных, о том, как их получить и использовать прямо в браузере с помощью JavaScript.

Вот парочка идей о том, что еще можно с этим сделать:

  • Визуализация нескольких спутников, например, спутниковой системы Starlink. В качестве источника для вдохновения можно использовать Celestrak viewer, показывающий каждый спутник из каталога. Можно, например, визуализировать рост количества спутников Starlink в течение времени
  • Отслеживание и визуализация положения спутника на уровне городской улицы. Можно добавить поиск здания или высоты с наилучшими условиями для наблюдения за спутником

Вот прототип второй идеи на Glitch. Демо: .

Также советую взглянуть на приложение See a satellite tonight, разработанное James Darpinian, в котором используется комбинация CesiumJS и Google улиц.

Кроме того, те, кто разбирается/увлекается 3D-моделированием, могут представить спутники не в виде точек, а в реальном масштабе для демонстрации того, насколько близко друг к другу они находятся в космосе.

Прим. пер.: мой вариант приложения выглядит так:



Благодарю за внимание и хорошего дня!



Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Перевод Практическое руководство по TypeScript для разработчиков

18.05.2021 14:13:42 | Автор: admin

Представляю вашему вниманию перевод статьи "Working With TypeScript: A Practical Guide for Developers".


Что такое TypeScript?


TypeScript это популярный статический типизатор (static type checker) или типизированное надмножество (typed superset) для JavaScript, инструмент, разработанный Microsoft и добавляющий систему типов к гибкости и динамическим возможностям JavaScript.


TypeScript развивается как проект с открытым исходным кодом, распространяется под лицензией Apache 2.0, имеет очень активное и высокопрофессиональное сообщество, а также огромное влияние на экосистему JavaScript.


Установка TypeScript


Для того, чтобы начать работу с TypeScript, нужно либо установить специальный интерфейс командной строки (command line interface, CLI), либо воспользоваться официальной онлайн-песочницей или другим похожим инструментом.


Для выполнения кода мы будем использовать Node.js. Устанавливаем его, если он еще не установлен на вашей машине, инициализируем новый Node.js-проект и устанавливаем транспилятор TypeScript:


# Создаем новую директорию для проектаmkdir typescript-intro# Делаем созданную директорию текущейcd typescript-intro# Инициализируем Node.js-проектnpm init -y# Устанавливаем компилятор TypeScriptnpm i typescript

Это установит tsc (компилятор TypeScript) для текущего проекта. Для того, чтобы проверить установку, в директории проекта создаем файл index.ts следующего содержания:


console.log(1)

Затем используем транспилятор для преобразования кода, содержащегося в этом файле, в JavaScript:


# Преобразуем index.ts в index.jsnpx tsc index.ts

Наконец, выполняем скомпилированный код с помощью команды node:


# Вы должны увидеть `1` в терминалеnode index.js

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


Обратите внимание: версии TypeScript могут сильно отличаться друг от друга, даже если речь идет о минорных релизах. Поэтому TypeScript лучше устанавливать локально и выполнять с помощью npx, вместо того, чтобы полагаться на глобальную версию.


Определение TypeScript-проекта


Для определения TypeScript-проекта внутри Node.js-проекта, необходимо создать файл tsconfig.json. Присутствие данного файла в директории свидетельствует о том, что мы имеем дело с TypeScript-проектом.


tsconfig.json содержит определенное количество настроек, которые влияют на поведение транспилятора, например, на то, какие файлы следует игнорировать, какой файл является целью компиляции, какие типы импортируются и т.д.


Вы легко можете настроить TypeScript с помощью следующей команды:


# Создаем стандартный tsconfig.jsonnpx tsc --init

Сгенерированный tsconfig.json содержит почти все возможные настройки с кратким описанием каждой из них. К счастью, данный файл содержит хорошие настройки по умолчанию, так что вы можете удалить большую часть закомментированных опций.


Мы еще вернемся к настройкам TypeScript, а сейчас давайте писать код.


Возможности TypeScript


Каждая возможность TypeScript подробно рассматривается в "Карманной книге по TypeScript". Мы сосредоточимся на практической составляющей некоторых из них. Я постараюсь пролить свет на некоторые возможности, которые часто упускаются из вида в литературе, посвященной TypeScript.


Основы типизации


Ключевая идея TypeScript заключается в контроле за динамической природой и гибкостью JavaScript с помощью типов. Давайте рассмотрим эту идею на практике.


В директории проекта создаем файл test.js следующего содержания:


function addOne(age) { return age + 1}const age = 'thirty two'console.log(addOne(age))

Выполняем данный код:


node test.js

  1. Что мы увидим в терминале?
  2. Как вы думаете, правильным ли будет вывод?

В терминале мы увидим thirty two1 без каких-либо предупреждений об очевидной некорректности вывода. Ничего нового: обычное поведение JavaScript.


Но что если мы хотим обеспечить, чтобы функция addOne() принимала только числа? Вы можете добавить в код проверку типа переданного значения с помощью оператора typeof или же вы можете использовать TypeScript, который привнесет в процесс компиляции кода некоторые ограничения.


Заменим содержимое созданного нами ранее index.ts следующим кодом:


function addOne(age: number): number { return age + 1}console.log(addOne(32))console.log(addOne('thirty two'))

Обратите внимание, что мы ограничили принимаемый функцией аргумент и возвращаемое функцией значение типом number.


Преобразуем файл:


npx tsc index.ts

Попытка преобразования проваливается:


index.ts:6:20 - error TS2345: Argument of type 'string' is notassignable to parameter of type 'number'. Аргумент типа 'строка' не может быть присвоен параметру с типом 'число'.

Таким образом, определение типов помогает избежать совершения ошибок, таких как передача в функцию аругментов неправильного типа.


string и number это лишь два из основных типов, поддерживаемых TypeScript. TypeScript поддерживает все примитивные значения JavaScript, включая boolean и symbol.


Кроме того, TypeScript определяет несколько собственных типов, которые не имеют соответствия в JavaScript, но являются очень полезными с точки зрения используемой в данной экосистеме методологии:


  • enum ограниченный набор значений
  • any указывает на то, что переменная/параметр могут быть чем угодно, что, по сути, нивелирует типизацию
  • unknown типобезопасная альтернатива any
  • void указывает на то, что функция ничего не возвращает
  • never указывает на то, что функция выбрасывает исключение или на то, что ее выполнение никогда не заканчивается
  • литеральные типы, конкретизирующие типы number, string или boolean. Это означает, например, что 'Hello World' это string, но string это не 'Hello World' в контексте системы типов. Тоже самое справедливо в отношении false в случае с логическими значениями или для 3 в случае с числами:

// Данная функция принимает не любое число, а только 3 или 4declare function processNumber(s: 3 | 4)declare function processAnyNumber(n: number)const n: number = 10const n2: 3 = 3processNumber(n) // Ошибка: `number` - это не `3 | 4`processAnyNumber(n2) // Работает. 3 - это `number`

Множества


TypeScript поддерживает несколько типов множеств (обычные массивы, ассоциативные массивы карты или мапы, кортежи), обеспечивая первоклассную поддержку композиции.


Карты (maps)


Карты, как правило, используются для определения связи между ключами и значениями для представления специфичных для приложения данных:


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем объект `user`, соответствующий ассоциативному типуconst user: User = { id: 1, username: 'Superman', name: 'Clark Kent',}

Векторы (vectors)


Векторы это последовательная индексированная структура данных, содержащая фиксированные типы для всех элементов. JavaScript не поддерживает данную возможность, но TypeScript позволяет разработчикам эмулировать эту концепцию:


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем несколько объектов `user`, соответствующих ассоциативному типуconst user1: User = { id: 1, username: 'Superman', name: 'Clark Kent',}const user2: User = { id: 2, username: 'WonderWoman', name: 'Diana Prince',}const user3: User = { id: 3, username: 'Spiderman', name: 'Peter Parker',}// Создаем вектор пользователейconst userVector: User[] = [user1, user2, user3]

Кортежи (tuples)


Кортежи также являются индексированной структорой данных, но типы элементов могут различаться в зависимости от их позиции:


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем объект `user`, соответствующий ассоциативному типуconst user1: User = { id: 1, username: 'Superman', name: 'Clark Kent',}// Создаем кортежconst userTuple: [User, number] = [user1, 10]

Объединения (unions)


Другим способом создания композиции типов являются объединения, которые бывают полезны в случаях, когда принимаемый функцией аргумент может иметь один из нескольких типов.


Предположим, что мы хотим создать функцию, возвращающую адрес пользователя на основе переданного объекта или строки.


Прежде всего, давайте установим node-fetch, чтобы иметь возможность использовать функцию fetch в Node.js:


npm i node-fetch @types/node-fetch

Затем с помощью typeof осуществляем разделение типов:


type User = { id: number username: string name: string email: string}async function fetchFromEmail(email: string) { const res = await fetch('https://jsonplaceholder.typicode.com/users') const parsed: User[] = await res.json() const user = parsed.find((u: User) => u.email === email) if (user) {   return fetchFromId(user.id) } return undefined}function fetchFromId(id: number) { return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)   .then((res) => res.json())   .then((user) => user.address)}function getUserAddress(user: User | string) { if (typeof user === 'string') {   return fetchFromEmail(user) } return fetchFromId(user.id)}getUserAddress('Rey.Padberg@karina.biz').then(console.log).catch(console.error)

Здесь мы в явном виде реализовали предохранитель типов.


К слову, кортежи и объединения можно использовать совместно:


const userTuple: Array<User | number> = [u, 10, 20, u, 30]// Любой элемент может быть либо `User`, либо `number`

Можно определять размер и тип каждого элемента массива:


const userTuple: [User, number] = [u, 10, 20, u, 30]// Ошибка: массив должен состоять из двух элементов с типами `User` и `number`const anotherUserTuple: [User, number] = [u, 10] // Все верно

Предохранители типов (type guards)


Предохранители типов это выражения, выполняющие проверки во время выполнения кода, результат которых может быть использован системой типов для сужения (narrow) области (scope) проверяемого аргумента.


Одним из предохранителей является оператор typeof, который мы использовали в предыдущем примере для сужения области аргумента user.


Существуют и другие предохранители, такие как instanceof, !== и in, полный список можно найти в документации.


Для случаев, когда система типов не может сделать правильный вывод о типе в текущем контексте, мы можем определить пользовательский предохранитель типа через предикат (типизированная функция, возвращающая логическое значение):


// Определяем предохранитель для `user`function isUser(u: unknown): u is User { if (u && typeof u === 'object') {   return 'username' in u && 'currentToken' in u } return false}function getUserAddress(user: User | string) { if (isUser(user)) {   return fetchFromEmail(user) } return fetchFromId(user.id)}

Пользовательские предохранители находятся под полным контролем разработчика, TypeScript не имеет возможности убедиться в их корректности.


Весьма распространенным случаем использования пользовательских предохранителей является влидация внешних данных с помощью JSON-схемы, предоставляемой сторонней библиотекой, такой как Ajv. Обычно, это происходит в веб-приложениях, где тело запроса имеет тип unknown (или any в зависимости от используемого фреймворка), и мы хотим проверить его перед использованием:


import Ajv from 'ajv'const ajv = new Ajv()const validate = ajv.compile({ type: 'object', properties: {   username: { type: 'string' },   currentToken: { type: 'string' }, },})function validateUser(data: unknown): data is User { return validate(data)}

В основе данного механизма лежит синхронизация JSON-схемы с типом. Если мы изменим тип, но не изменим схему, то вполне можем получить неожиданное сужение типа.


В следующем разделе мы узнаем, как обеспечить автоматическую синхронизацию между схемой и типом.


Исключающие объединения (discriminated unions)


Объединения с общим литеральным полем называются исключающими. При работе с такими типами TypeScript предоставляет неявный предохраитель, позволяя избежать его создания в явном виде:


type Member = { type: 'member' currentProject: string}type Admin = { type: 'admin' projects: string[]}type User = Member | Adminfunction getFirstProject(u: User) { if (u.type === 'member') {   return u.currentProject } return u.projects[0]}

В функции getFirstProject() TypeScript сужает область аргумента без помощи предиката. Попытка получить доступ к массиву projects в первой ветке (блоке if) закончится ошибкой типа.


Валидация во время выполнения


Как было отмечено ранее, в случае с пользовательскими предохранителями, корректность возвращаемого результата обеспечивается разработчиком.


При наличии ошибки в предикате, система типов может получить неверную информацию. Рассмотрим пример:


function validateUser(data: unknown): data is User { return true}

Данный предикат всегда возвращает true, позволяя типизатору сузить тип к тому, чем он на самом деле не является:


const invalidUser = undefinedif (validateUser(invalidUser)) { // Предыдущая инструкция всегда возвращает `true` console.log(invalidUser.name) // Ошибка, возникающая во время выполнения}

Существует несколько библиотек, которые позволяют обеспечить автоматическую синхронизацию между валидацией во время выполнения и соответствующим типом. Одним из самых популярных решений является runtypes, однако мы будем использовать io-ts и fp-ts.


Суть данного подхода состоит в том, что мы определяем форму (или фигуру) типа с помощью примитивов, предоставляемых io-ts; эта форма называется декодером (decoder); мы используем ее для проверки данных, которым мы по какой-либо причине не доверяем:


import * as D from 'io-ts/Decoder';import * as E from 'io-ts/Either';import { pipe } from 'fp-ts/function';// Определяем декодер, представляющий `user`const UserDecoder = D.type({   id: D.number,   username: D.string,   name: D.string   email: D.string});// и используем его в отношении потенциально опасных данныхpipe(   UserDecoder.decode(data),   E.fold(       error => console.log(D.draw(error)),       decodedData => {           // типом `decodedData` является `User`           console.log(decodedData.username)       }   ));

Настройка TypeScript


Поведение транспилятора можно настраивать с помощью файла tsconfig.json, находящегося в корне проекта.


Данный файл содержит набор ключей и значений, отвечающих за 3 вещи:


  1. Структура проекта: какие файлы включаются/исключаются из процесса компиляции, зависимости разных TypeScript-проектов, связь между этими проектами через синонимы (aliases).
  2. Поведение типизатора: выполнять ли проверку на наличие null и undefined в кодовой базе, сохранение const enums и т.п.
  3. Процесс транспиляции.

Пресеты TSConfig


TypeScript может преобразовывать код в ES3 и поддерживает несколько форматов модулей (CommonJS, SystemJS и др.).


Точные настройки зависят от среды выполнения кода. Например, если вашей целью является Node.js 10, вы можете транспилировать код в ES2015 и использовать CommonJS в качестве стратегии разрешения модулей.


Если вы используете последнюю версию Node.js, например, 14 или 15, тогда можете указать в качестве цели ESNext или ES2020 и использовать модульную стратегию ESNext.


Наконец, если вашей целью является браузер и вы не используете сборщик модулей, такой как webpack или parcel, то можете использовать UMD.


К счастью, команда TypeScript разработала хороший набор пресетов, которые вы можете просто импортировать в свой tsconfig.json:


{ "extends": "@tsconfig/node12/tsconfig.json", "include": ["src"]}

Среди наиболее важных настроек, можно отметить следующее:


  • declaration: определяет, должен ли TypeScript генерировать файлы определений (.d.ts) во время транспиляции. Данные файлы, как правило, используются при разработке библиотек
  • noEmitOnError: определяет, должен ли TypeScript прерывать процесс компиляции при возникновении ошибок, связанных с неправильными типами. Рекомендуемым значением данной нстройки является true
  • removeComments: true
  • suppressImplicitAnyIndexErrors: true
  • strict: дополнительные проверки. До тех пор, пока у вас не появится веской причины для отключения данной настройки, она должна иметь значение true
  • noEmitHelpers: при необходимости, TypeScript предоставляет утилиты и полифилы для поддержки возможностей, которых не было в ES3 и ES5. Если значение данной настройки является false, утилиты будут помещены в начало кода, в противном случае, они будут опущены (tslib можно устанавливать отдельно)

Заключение


Надеюсь, данная статья позволила вам получить общее предствления о возможностях, предоставляемых TypeScript, а также о том, почему использование TypeScript в дополнение к JavaScript в настоящее время фактически является стандартом веб-разработки.


Система типов TypeScript не является идеальной, но это лучшее, что мы имеет на сегодняшний день.




Облачные серверы от Маклауд отлично подходят для сайтов с JavaScript.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Перевод 5 приемов по разделению бандла и ленивой загрузке компонентов в React

25.05.2021 16:13:10 | Автор: admin

image


Разделение Javascript-кода на несколько файлов называется разделением бандла или сборки (bundle splitting). Это позволяет загружать только тот код, который который используется приложением в данный момент, другие части загружаются по необходимости (по запросу пользователя).


Распространенные случаи разделения сборки и ленивой или отложенной загрузки (lazy loading) включают в себя следующее:


  • Загрузка дополнительного кода при переходе пользователя к новому представлению (view слой, отвечающий за визуальное отображение)


  • Загрузка такого кода может быть связана с определенным действием, таким как прокрутка или нажатие кнопки


  • Также можно реализовать предварительную загрузку определенных ресурсов, которые представляют потенциальный интерес для пользователя


  • Это приводит к тому, что когда пользователь захочет получить доступ к определенной функциональности, она уже будет готова



    1. Динамический импорт с помощью Webpack



Webpack позволяет загружать модули (компоненты) динамически во время выполнения кода. Рассмотрим пример:


import { useState } from 'react'function MainComponent() {const [isModalDisplayed, setModalDisplayed] = useState(false)const [ModalComponent, setModalComponent] = useState(null)const loadModalComponent = async () => {const loadResult = await import('./components/Modal.js')setModalComponent(() => loadResult.default)}return (<div>{isModalDisplayed && ModalComponent ? <ModalComponent /> : null}<buttononClick={() => {setModalDisplayed(true)loadModalComponent()}}>Load Modal Component</button></div>)}

Динамический импорт может использоваться в любом месте кода. Такой импорт сообщает Webpack о необходимости создания отдельной сборки для конкретного компонента. Поскольку модальное окно отображается только когда пользователь нажимает кнопку, мы можем отложить загрузку соответствующего кода.


Динамический импорт позволяет каждому компоненту выступать в роли микрофронтенда (microfrontend).


2. Split API для загрузки React-компонентов


Пакет fusion-react предоставляет интерфейс split, компонент-обертку для отображения различных компонентов во время загрузки сборки:


  • Резервного компонента при возникновении ошибки


  • Настоящего компонента после загрузки сборки



Рассматриваемый интерфейс облегчает задачу разделения React-компонентов, позволяя избежать повторения шаблонного кода.


import { Link, Switch, Route } from 'react-router-dom'import { split } from 'fusion-react'const Loading = () => <div>Loading...</div>const Error = () => <div>Error</div>const Hello = split({load: () => import('./components/hello.js'),Loading,Error,})const Root = () => (<><div><ul><li><Link to='/'>Home</Link></li><li><Link to='/hello'>Hello</Link></li></ul></div><Switch><Route path='/' exact component={Home} /><Route path='/hello' component={Hello} /></Switch></>)

Разделение компонентов верхнего уровня на основе маршрутов еще одна техника оптимизации, способ уменьшения времени загрузки приложения.


В целом, отложенная загрузка кода до запроса пользователем страницы по конкретному маршруту это хорошая идея.


Интерфейс split в приведенном примере откладывает загрузку компонента Hello до того момента, когда пользователь перейдет по соответствующему маршруту. Загружаемый компонент указывается в свойстве load.


В данном случае мы также имеем возможность использовать динамический импорт.


Прим. пер.: существуют более специализированные и популярные решения для ленивой загрузки React-компонентов, например, react-loadable или react-lazyload.


3. Создание вендорного бандла (vendor bundle)


Прежде всего, вы можете спросить, для чего нам это нужно? Основная идея заключается в том, что в вендор помещается (выносится) код, который изменяется намного реже, чем код разрабатываемого приложения. Это позволяет использовать эффективные техники по кэшированию кода для повышения скорости загрузки страниц.


Вот как можно извлечь вендорный бандл из директории node_modules:


const path = require('path')module.exports = {entry: path.resolve(__dirname, 'src/index.js'),output: {path: path.resolve(__dirname, 'dist'),filename: '[name].[contenthash].js',},}

Если после этого вы запустите сборку (yarn build или npm run build), то увидите что-то вроде этого:


 webpack: Build Finished webpack: assets by status 128 KiB [emitted]asset 935.js 124 KiB [emitted] [minimized] (id hint: vendors) 2 related assetsasset main.js 3.24 KiB [emitted] [minimized] (name: main) 1 related assetasset index.html 267 bytes [emitted]assets by status 7.9 KiB [compared for emit]asset main.css 7.72 KiB [compared for emit] (name: main) 1 related assetasset 34.js 187 bytes [compared for emit] [minimized] 1 related assetEntrypoint main 135 KiB (326 KiB) = 935.js 124 KiB main.css 7.72 KiB main.js 3.34 KiB 3 auxiliary assets...webpack 5.5.0 compiled successfully in 4856 ms

4. Создание нескольких вендорных бандлов


Обычно, все модули объединяются в один вендорный бандл.


Знаете ли вы, что мы можем создать несколько таких бандлов?


Например, поскольку React не будет меняться между сборками, имеет смысл закэшировать его в виде отдельного пакета в случаях, когда вопрос производительности является критически важным.


Файл с настройками Webpack принимает свойство optimization, позволяющее разделять вендорный бандл:


module.exports = {splitChunks: {chunks: 'async',cacheGroups: {default: {minChunks: 2,reuseExistingChunk: true,},vendor_react: {test: /.*\/node_modules\/react\/index\.js/,name: 'vendor-react',chunks: 'initial',enforce: true,},},},}

После этого вендорный бандл будет разделен на client-vendor.js и clietn-vendor-react.js.


5. Ленивая загрузка компонентов с помощью React.lazy()


React.lazy() это функция, позволяющая рендерить динамически импортируемые компоненты как обычные компоненты.


Обычный импорт:


import MyComponent from './MyComponent'

Динамический импорт с помощью React.lazy():


const OtherComponent = React.lazy(() => import('./OtherComponent')

Компоненты, загружаемые с помощью React.lazy(), должны быть обернуты в компонент Suspense, который позволяет отображать резервный контент (например, индикатор загрузки) до полной загрузки импортируемого компонента:


import { lazy, Suspense } from 'react'const OtherComponent = lazy(() => import('./OtherComponent'))function MyComponent() {return (<><Suspense fallback={<div>Loading...</div>}><OtherComponent /></Suspense></>)}

Проп fallback принимает любой элемент (компонент). Компонент Suspense может быть помещен на любом родительском по отношению к ленивому компоненту уровне.


Suspense может оборачивать как отдельный компонент, так и группу компонентов:


import { lazy, Suspense } from 'react'const OtherComponent = lazy(() => import('./OtherComponent'))const AnotherComponent = lazy(() => import('./AnotherComponent'))function MyComponent() {return (<><Suspense fallback={<div>Loading...</div>}><section><OtherComponent /><AnotherComponent /></section></Suspense></>)}

Заключение


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


Это, безусловно, положительно повлияет на пользовательский опыт работы с приложением, поскольку оно станет более быстрым и отзывчивым.


Также не стоит забывать о том, что загруженный JavaScript-код, должен быть разобран и выполнен, что также требует некоторого времени и вычислительной мощности.




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

История нефтеперевозок. От танкеров с бакинской нефтью до современных монстров

25.05.2021 10:11:06 | Автор: admin


В 1847 году английский химик Джеймс Янг был занят перегонкой нефти, которая, в очень скромных количествах, выделялась в угольных месторождениях. Янг хотел получить легкое ламповое масло (сразу скажем, что до керосина дело у него так и не дошло), но получил масло тяжелое и вязкое.

Для лампового масла эта субстанция не очень годилась, зато она отлично подходила для смазывания деталей механизмов, которых к тому времени в Англии было довольно много, и спрос на нефтяной воск, предотвращающую порчу металлических деталей, вдруг скакнул до небес.

Правда, новое производство Янга и его компаньонов Мелдрама и Бинни испытывало постоянные проблемы с поставками сырья, потому что естественного просачивания нефти было явно недостаточно для удовлетворения спроса на парафин. Янг проявлял чудеса изобретательности (например, догадался получать нефть из горючего сланца), но в конце концов новые фабриканты неизбежно встали перед проблемой завоза нефти.



Джеймс Янг, замечательный химик. Нет, он не был первооткрывателем парафина, способ его получения придумал Карл Райхенсбах в 1830 году. А до него, в 1820 году, француз Мишель Шеврель изобрел стеарин. Это была замечательная эпоха, когда человечество нуждалось в свечах и оба материала, и стеарин и парафин, в первую очередь были прекрасными заменителями дорогому воску.
Янг стал использовать парафин для смазки механизмов и запатентовал его способ получения из нефти. Благодаря этому патенту он много лет (и весьма успешно) оспаривал право прочих повторять его технологию. Злые языки утверждают, что именно на патентных спорах, а вовсе не на смазочных материалах, он и сколотил состояние.


Вариантов в начале 60-х г.г. XIX века было немного: нефть активно добывали в районе Баку, на Каспии, в австро-венгерской Галиции и в Пенсильвании, на другом берегу Атлантики. Понятно, именно морской маршрут доставки нефти оказался предпочтительнее перевозки её через континент, он был надежнее, быстрее и дешевле любого другого.

Весной 1861 года бриг Elizabeth Watts прибыл из Филадельфии в Англию, имея на борту груз в 180 тонн нефти, разлитой в бочки, и это была только первая ласточка к берегам Британии, позже и всей Западной Европы, устремились суда, загруженные емкостями с нефтью. К этому времени мир уже охватила керосиновая лихорадка, и керосин оказался востребован в сотни раз больше, чем парафин.

Правда, перевозки в бочках (так нефть будут перевозить еще четверть века) не казались самим перевозчикам особо удобными: емкости были тяжелы, занимали слишком много места и были неудобны для перемещения.

Вообще перевозка нефти (и измерения добычи) велись на разных месторождениях по-разному, например, в Баку добычу считали в ведрах (подразумевая, понятно, не оцинкованные разнокалиберные сосуды, а принятую в России в то время меру объема), а перевозили на верблюдах, заливая нефть в бурдюки, а американцы нефть считали бочками. Правда, бочки у всех были разные, и только в 1866 году собрание нефтепромышленников Пенсильвании договорилось, что базовым значением станет баррель, бочка вместимостью в 42 галлона (159 литров), ввиду популярности этой тары (стандарт, принятый еще в XV веке в Голландии для засолки и перевозки селедки).


Хотите увидеть, как выглядит баррель нефти? Вот он, во всей красе. Можно сказать красе первозданной. Сейчас увидеть нефтяную бочку объемом в 1 баррель сложно...

Но, как мы уже и говорили выше, бочки были тарой тяжелой, дорогой, неудобной для перемещения, невозвратной и портящейся. Поэтому мысль о заливе нефти непосредственно в трюм кораблей, с последующей выгрузкой её при помощи помп в порту назначения, что называется, витала в воздухе.

Первыми рискнули это сделать братья Артемьевы, Николай и Дмитрий, переоборудовавшие парусную яхту (они называли её курсовая лодка) Александр для вывоза нефти из Баку в Астрахань.

Кстати, эта идея поражала людей, далеких от проблем логистики, своей кажущейся несуразностью, а у коллег вызывала истерический смех из-за кажущейся же неосуществимости, говорят, в Баку на братьев рисовали даже карикатуры в местных газетах, вот только уже после первой же навигации насмешники были посрамлены: Александр сделал 8 рейсов при том, что перевозившие нефть в бочках суда смогли сделать не более 6 ходок, хотя бы потому, что качать нефть помпами было быстрее, чем загружать и разгружать кадушки. Александр, в итоге, оказался еще и рентабельнее в эксплуатации, потому что вез чистый груз, а не тару (вес бочек составлял от 20 до 25% от веса содержимого), плюс они сильно сэкономили на стоимости бочек а еще они смогли доставлять груз без потерь, а расчеты потерь тогда были минус 20% из-за ломки и протечки бочек.

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


Здесь очень уместно было бы разместить фотографию братьев Артемьевых. Или хотя бы изображение их первых судов, которые они приспособили для налива нефти. Увы, ни того, ни другого история не сохранила. Мы даже не знаем, например, дату и место смерти младшего из братьев, да и об их компании сведения сохранились весьма скудные.

Артемьевы, собственно, подали пример всем нефтеперевозчикам всего мира, ну а попутно, по сути, спасли бакинские нефтепромыслы, так как к 1873 году, году первого плавания Александра, ценовая конъюнктура складывалась не в пользу бакинцев: доставка одного пуда нефти из Баку в Нижний Новгород составляла около 40 копеек за пуд, тогда как доставка того же пуда из Пенсильвании в Санкт-Петербург стоила примерно 30 копеек.

Вдохновенные успехом Артемьевы быстро увеличили размер и оборот собственного флота, добавив к парусникам еще и баржи, и у них быстро появились последователи например, французский консул Бовэ объявил о закладке собственного танкера на Черном море по замечательному примеру братьев Артемьевых.
Справедливости ради заметим, что опыты, чем-то похожие на опыты братьев Артемьевых, велись примерно в те же годы и на берегах Темзы, но именно на бакинских промыслах они оказались наиболее востребованы.

Тоньше всех прочувствовали перемены в конъюнктуре братья Нобели, и настоящей революцией в области перевозки нефти стал построенный по их заказу в Швеции, уже в 1876 году, британским инженер-полковником Генри Ф. Своном, танкер Зороастр. Не будет преувеличением сказать, что именно с этого судна начинается история мирового танкерного флота.

Зороастр работал на нефти (позже на мазуте) и перевозил из Баку в Царицын и Астрахань 15 тысяч пудов (242 тонны) нефти. За этим чудом техники были построены и другие танкеры, и уже через 7 лет по Волге ходили более 30 танкеров, а в 1885 году в той же Швеции для тех же братьев Нобелей был спущен на воду и первый морской танкер, Свет, предназначенный для экспорта бакинской нефти по Черному морю.


Зороастр, родоначальник всего танкерного флота в мире. Его конструкция предполагала две соединенные между собой наливные цистерны, разделенные рубкой. Позже вид танкеров существенно изменится.

Уже в следующем году, в 1886-м, в Англии строится океанский нефтеналивной пароход Glckauf, разработанный всё тем же Своном, способный перевозить уже более 3000 тонн нефти. Понадобилось всего три года для того, чтобы судов класса Glckauf стало 50 все они работали на трансатлантических маршрутах, перевозя североамериканскую нефть в европейские порты.

Впрочем, говоря о морских перевозках нефти совершенно невозможно не сказать о Маркусе Самюэле, основателе компании Shell (названной так потому, что до перевозки нефти Самуэль занимался продажей весьма скромной сувенирки из ракушек), который, получив контракт от Рокфеллеров на перевозку нефти, собственно, и создал мировую (или всемирную?) торговлю нефтью и нефтепродуктами.

Для того, чтобы его всемирные планы осуществились, надо было организовать пропуск танкеров через Суэцкий канал, в котором перемещение нефти тогда, из соображений безопасности, запрещалось. Самуэль задал администрации канала простой вопрос: какими должны быть танкеры, чтобы разрешение на их проход было бы получено?

Руководство канала тянуло с ответом, и Самуэль, на свой страх и риск, заложил восемь танкеров. Как это бывает иногда, в определенных обстоятельствах, когда, наконец, администрация канала выдвинула свои требования, они странным образом сошлись с характеристиками строящихся для Шелла судов.


Маркус Самуэль, основатель Шелл. С 1907 года Самуэль проводит удачное слияние с голландской компанией, и отныне полное название его детища звучит как Royal Dutch Shell.
Между прочим, первая разработка нефти этим объединенным предприятием (Royal Dutch добывала нефть и раньше) стала концессия в городе Грозный, в 1910 году.


Возможно, Самуэль обладал даром гипнотического внушения, возможно, инженер, строящий эти танкеры, обладал даром предвидения, а может быть, Самуэль умел делать ожидаемые подарки, но, так или иначе, а в 1892 году первый танкер из флотилии Шелл прошел через Суэцкий канал.

К 1907 году у Шелл был танкерный флот из 34 больших пароходов, и его заказчик, рокфеллеровская Стандарт Ойл с их 4 пароходами и 16 парусными танкерами попала в серьезную зависимость от Самуэля, который, заметим, расширял свою империю, создавая пункты приема нефти на азиатском континенте в Кобе, Гонконге, Шанхае, Джакарте, Сайгоне, Сингапуре практически любой значимый порт Азии был в его распоряжении.

Наверное, пусть мимоходом, на заслуживает упоминания то, что именно стараниями Самуэля британский флот (а после и все флоты мира) был подсажен на мазут, а зарождавшийся авиапром на керосин.

Уже в начале ХХ века подавляющее большинство танкеров оснащены дизельным двигателем, самым современным по тем временам. Ведется настоящая гонка по строительству танкеров все большей и большей вместимости, и суда с дедвейтом более 10 тысяч тонн уже не выглядят монстрами.

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


Американский танкер Гарри Синклер, торпедированный немецкой подводной лодкой

После войны на нефтяной игле сидел уже весь мир, потребность в нефтепродуктах росла чуть ли не в геометрической прогрессии, и вместе с ростом этой потребности рос и танкерный флот. В 1950 году объем потребления нефти уже вдвое превышал объем потребления во время войны, а к 1970 году вырос еще в пять раз от 1950-го. Только в США после войны построили 525 танкеров типа Т2 (проект был разработан под нужды второй мировой) с дедвейтом в 16500 тонн, но все они не в состоянии были удовлетворить растущие потребности.

На рост потребностей в перевозке сказывалось и то, что мир захватывали новые технологии оказалось, что перевозить сырую нефть и перерабатывать её на месте выгоднее, чем перевозить нефтепродукты. Кроме того, во второй половине ХХ века США, а следом за ней и все другие страны, переходят к политике обеспечения внутри страны так называемых стратегических запасов нефти.

Под эти задачи быстро растут и возможности танкеров (чем больше танкер, тем ниже себестоимость перевозки) от стандартных 16500 тонн во время войны до 331800 тонн (Univers Ireland) в 1968 году.

Кстати, в конце 60-х закладываются танкеры дедвейтом в 600-800 тысяч тонн, но экономический кризис не дал тогда осуществиться этим планам.


Эссо Нортумбрия, британский танкер, его спуску на воду в 1969 году придавалось огромное значение (принцесса Анна лично разбивала традиционное шампанское о его борт), и ожидания он, одновременно, и оправдал так как на его примере целые поколения кораблестроителей будут изучать конструкторские удачи и, увы, конструкторские огрехи, и, одновременно не оправдал, так как, ввиду упомянутых огрехов, он больше ремонтировался, чем работал.
Зато этот корабль послужил темой для написания замечательной песни (в стиле и духе матросских английских песен) Roll Northumbria, в которой, собственно, рассказывается история корабля.


Тем не менее тот же экономический кризис и повсеместная национализация нефтяных отраслей по всему миру приводит к строительству супертанкеров к этой категории относят суда, перевозящие более 2 млн баррелей (318 тысяч тонн). Самым крупным судном в истории стал танкер Knock Nevis, способный перевозить 564753 тонн нефти. Длинна этого судна составляла более полукилометра. Этому монстру был недоступен не только любой из каналов в мире, но и многие проливы, так как его осадка (25 метров) не позволяла ему проходить, например, Ла-Манш.


Танкер и супертанкер



Облачные VDS серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

В поисках упорядоченного множества в Python разбираемся с теорией и выбираем лучшую реализацию

24.05.2021 10:16:39 | Автор: admin


Множество (Set) структура данных, которая позволяет достаточно быстро (в зависимости от реализации) применить операции add, erase и is_in_set. Но иногда этого не достаточно: например, невозможно перебрать все элементы в порядке возрастания, получить следующий / предыдущий по величине или быстро узнать, сколько элементов меньше данного есть в множестве. В таких случаях приходится использовать Упорядоченное множество (ordered_set). О том, как оно работает, и какие реализации есть для питона далее.


Стандартный Set


В языке Python есть стандартная стукрура set, реализованная с помощью хэш-таблиц. Такую структуру обычно называют unordered_set. Данный метод работает так: каждый элемент присваивается какому-то классу элементов (например, класс элементов, имеющих одинаковый остаток от деления на модуль). Все элементы каждого класса хранятся в одтельном списке. В таком случае мы заранее знаем, в каком списке должен находиться элемент, и можем за короткое время выполнить необходимые операции. Равновероятность каждого остатка от деления случайного числа на модуль позволяет сказать, что к каждому классу элементов будет относиться в среднем size / modulo элементов.


Но хэш-таблица не позволяет выполнить операцию count_lower или подобные, поэтому придётся использовать другие структуры данных.


Что есть в других языках


В языке c++ есть структура std::set, которая поддерживает операции изменения, проверку на наличие, следующий / предыдущий по величине элемент, а также for по всем элементам. Но тут нет операций получения элемента по индексу и индекса по значению, так что надо искать дальше (индекс элемента количество элементов, строго меньших данного)


И решение находится достаточно быстро: tree из pb_ds. Эта структура в дополнение к возможностям std::set имеет быстрые операции find_by_order и order_of_key, так что эта структура именно то, что мы ищем.


Она реализована с помощью красно-чёрных деревьев. Смысл этой струкруты в том, что все элементы образуют собой двоичное дерево поиска, которое балансируется так, чтобы высота не превышала логарифм. Нам это даёт возможность с помощью одного спуска по дереву выполнить необходимые операции. Также с этой задачей может справиться Декартово дерево (Дерамида) по неявному ключу или AVL дерево.


Таким образом, целью этой статьи станет поиск аналога этой структуры в Python.


Как будем тестировать скорость работы структур данных


Для оценки времени работы я написал программу, которая будет выполнять последовательно несколько типов операций:


  1. Добавление в множество миллиона случайных чисел (при данном сиде среди них будет 999'936 различных)
  2. Проверка миллиона случайных чисел на присутствие в множестве
  3. Прохождение циклом по всем элементам в порядке возрастания
  4. В случайном порядке для каждого элемента массива узнать его индекс (а, соответственно, и количество элементов, меньше данного)
  5. Получение значения i-того по возрастанию элемента для миллиона случайных индексов
  6. Удаление всех элементов множества в случайном порядке

from SomePackage import ordered_setimport randomimport timerandom.seed(12345678)numbers = ordered_set()# adding 10 ** 6 random elements - 999936 uniquelast_time = time.time()for _ in range(10 ** 6):    numbers.add(random.randint(1, 10 ** 10))print("Addition time:", round(time.time() - last_time, 3))# checking is element in set for 10 ** 6 random numberslast_time = time.time()for _ in range(10 ** 6):    is_element_in_set = random.randint(1, 10 ** 10) in numbersprint("Checking time:", round(time.time() - last_time, 3))# for all elementslast_time = time.time()for elem in numbers:    now_elem = elemprint("Cycle time:", round(time.time() - last_time, 3))# getting index for all elementslast_time = time.time()requests = list(numbers)random.shuffle(requests)for elem in requests:    answer = numbers.index(elem)print("Getting indexes time:", round(time.time() - last_time, 3))# getting elements by indexes 10 ** 6 timesrequests = list(numbers)random.shuffle(requests)last_time = time.time()for _ in range(10 ** 6):    answer = numbers[random.randint(0, len(numbers) - 1)]print("Getting elements time:", round(time.time() - last_time, 3))# deleting all elements one by onerandom.shuffle(requests)last_time = time.time()for elem in requests:    numbers.discard(elem)print("Deleting time:", round(time.time() - last_time, 3))

SortedSet.sorted_set.SortedSet


Пакет с многообещающим названием. Используем pip install sortedset


К сожалению, автор не приготовил нам функцию add и erase в каком-либо варианте, поэтому будем использовать объединение и вычитание множеств


Использование:


from SortedSet.sorted_set import SortedSet as ordered_setnumbers = ordered_set()numbers |= ordered_set([random.randint(1, 10 ** 10)])  # добавлениеnumbers -= ordered_set([elem])  # удаление

Протестируем пока на множествах размера 10'000:


Задача Время работы
Добавление 16.413
Проверка на наличие 0.018
Цикл по всем элементам 0.001
Получение индексов 0.008
Получение значений по индексам 0.015
Удаление 30.548

Как так получилось? Давайте загляем в исходный код:


def __init__(self, items=None):    self._items = sorted(set(items)) if items is not None else []def __contains__(self, item):    index = bisect_left(self._items, item)

Как оказалось, это обычный массив, в котором наличие элемента определяется бинпоиском. Это действительно отсортированное множество, но очень ленивое.


Вывод: почти бесполезно, несколько строчек кода завернули в класс


sortedcontainers.SortedSet


Внеший пакет, для установки можно использовать pip install sortedcontainers. Посмотрим же, что он нам покажет


Задача Время работы
Добавление 3.924
Проверка на наличие 1.198
Цикл по всем элементам 0.162
Получение индексов 3.959
Получение значений по индексам 4.909
Удаление 2.933

Но, не смотря на это, кажется мы нашли то, что искали! Все операции выполняются за приличное время. По сравнению с ordered_set некоторые операции выполняются дольше, но за то операция discard выполняется не за o(n), что очень важно для возможности использования этой структуры.


Также пакет нам предлагает SortedList и SortedDict, что тоже может быть полезно.


И как же оно работает?


На странице пакета мы можем прочитать, что реализована структура не так, как мы предполагали в начале статьи.


Из-за особенностей реализации языка Python, в нём быстро работают list, а также bisect.insort (найти бинарным поиском за o(log n) место, куда нужно вставить элемент, а потом вставить его туда за o(n)). Insert работает достаточно быстро на современных процессорах. Но всё-таки в какой-то момент такой оптимизации не хватает, поэтому структуры реализованы как список списков. Создание или удаление списков происходит достаточно редко, а внутри одного списка можно выполнять операции даже за быструю линию.


Если говорить кратко, то принцип действия похож на корневую оптимизацию.


Проблема с ordered_set


Что вообще такое упорядоченное множество? Это множество, в котором мы можем сравнить любые 2 элемента и найти среди них больший / меньший. В течение всей статьи под операцией сравнения воспринималась операция сравнения двух элеметнов по своему значению. Но все пакеты называющиеся ordered_set считают что один элемент больше другого, если он был добавлен раньше в множество. Так что с формулировкой ordered_set нужно быть аккуратнее и уточнять, имеется ввиду ordered set или sorted set.


Bintrees



Так есть же модуль bintrees! Это же то, что нам нужно? И да, и нет. Его разработка была приостановлена в 2020 году со словами Use sortedcontainers instead.


Пакет предлагает нам несколько структур. К сожалению, ни одна из них не поддерживает операции find_by_order и подобные, так что эти струкруты являются аналогами std::set. Посмотрим же, на что они способны:


pip install bintrees


Название AVLTree говорит само за себя, RBTree красно-чёрное дерево, BinaryTree несбалансированное двоичное дерево, префикс Fast означает реализацию на Cython (соответственно, необходимо наличие Visual C++, если используется на Windows).


Задача AVLTree FastAVLTree RBTree FastRBTree BinaryTree FastBinaryTree
Добавление 21.946 2.285 20.486 2.373 11.054 2.266
Проверка на наличие 5.86 2.821 6.172 2.802 6.775 3.018
Цикл по всем элементам 0.935 0.297 0.972 0.302 0.985 0.295
Удаление 12.835 1.509 25.803 1.895 7.903 1.588

Результаты тестирования отчётливо показывают нам, почему использовать деревья поиска на Python плохая идея в плане производительности. А вот в интеграции с Cython всё становится намного лучше.


Оказывается, эта структура и SortedSet очень похожи по производительности. Все 3 Fast версии структур bintrees достаточно близки, поэтому будем считать, что оттуда мы используем FastAVLTree.


Задача SortedSet FastAVLTree
Добавление 3.924 2.285
Проверка на наличие 1.198 2.821
Цикл по всем элементам 0.162 0.297
Получение индексов 3.959 n/a
Получение значений по индексам 4.909 n/a
Удаление 2.933 1.509

Как мы видим, AVL в полтора раза быстрее в скорости добавления элементов и почти в 2 раза быстрее в операциях удаления. Но он в те же 2 раза медленнее в проверке на наличие и цикле по всем элементам. К тому же не стоит забывать, что 2 операции он выполнять не умеет, то есть не является тем ordered_set, что мы ищем.


Использование:


import bintreesnumbers = bintrees.FastAVLTree()numbers.insert(value, None)  # второй параметр - значение, как в словаре

Что же выбрать


Мои рекомендации звучат так: если вам нужны операции find_by_order и order_of_key, то ваш единственный вариант sortedcontainers.SortedSet. Если вам нужен только аналог std::map, то выбирайте на своё усмотрение между SortedSet и любым из fast контейнеров из bintrees, опираясь на то, каких операций ожидается больше.


Можно ли сделать что-то быстрее


Скорее нет, чем да. Использование Cython один из самых мощных способов оптимизации, а AVL считается очень быстрым решением исходной задачи. Про остальные операции ordered_set можно сказать, что модификация красно-чёрного дерева так, чтобы оно поддерживало эти операции, вряд ли будет быстрее SortedContainers, так что смысла изобретать велосипед я не вижу.




Облачные VPS серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru