Анализ погрешностей измерения уровня сигнала генератора СВЧ с помощью измерительного приемника

Данная статья посвящена рассмотрению погрешностей, возникающих при измерении уровня мощности генераторов сигналов с помощью измерительного приемника R&S®FSMR3000. Даны рекомендации по минимизации основных погрешностей.

Для измерения уровня сигнала генераторов сигналов СВЧ в качестве калибратора уровня часто используются измерительные приемники, обладающие высокой линейностью в широком диапазоне. Одним из таких приемников является измерительный приемник R&S®FSMR3000 (далее – FSMR3000) [1]. Данный прибор представляет собой универсальную систему калиб­ровки генераторов сигналов и аттенюаторов. Измерительный при­емник FSMR3000 сочетает в себе калибратор уровня, анализатор модуляции и аудиосигналов, измеритель мощности и анализатор спектра и фазового шума. Благодаря высокой линейности и широкому диапазону частот (от 100 кГц до 8/26,5/50 ГГц в зависимости от модели прибора) он идеально подходит для решения измерительных задач в калибровочных и испытательных лабораториях.

Аналоговые модули, чувствительные к возникновению компрессии, такие как смесители и усилители тракта ПЧ, работают со значительным запасом линейности, чтобы исключить эффекты компрессии. Конструкция смесителей и усилителей, используемых в аналоговом тракте прибора, сочетает в себе высокую линейность с низким уровнем собственного шума. В результате, несмотря на широкий динамический диапазон прибора (от -152 дБмВт до +30 дБмВт на частоте 1 ГГц) [2], FSMR3000 обеспечивает хорошее отношение сигнал/шум (SNR) без компрессии, используя только три диапазона измерений. Модули и компоненты, под­верженные дрейфу (ЖИГ-фильтр) или обладающие нелинейной харак­теристикой уровня (кварцевый фильтр), в режиме измерения уровня отключаются и не вносят искажений.

Источники погрешности измерения уровня сигнала генератора с помощью приемника FSMR3000

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

Наибольшую точность измерения абсолютной мощности выходного сигнала генератора можно полу­чить при использовании датчика поглощаемой мощности, принцип действия которого основан на термоэлектрическом эффекте. Такие датчики принято называть термодатчиками. Примером термодатчиков является серия датчиков R&S®NRPxxT (далее – NRPxxT), где xx обозначается верхняя граничная частота рабочего диапазона частот термодатчика. Решающими факторами здесь выступают технические характеристики датчика мощности [3] и рассогласование (КСВН) между источником (испытуемым генератором) и датчиком. Термодатчики имеют очень малые КСВН по сравнению с измеритель­ными приемниками, что позволяет достичь минимальных погрешностей измерения [4].

По сравнению с другими датчиками мощности термодатчики обладают высокой точностью измерения, но имеют ограниченную чувствительность. Для датчика NRPxxT она составляет -35 дБмВт.  Таким образом, с помощью датчика мощности NRPxxT, подключенного к испытуемому генератору, и измерительного приемника FSMR3000, можно выполнить измерение уровня сигнала генератора выше -35 дБмВт. Измерение уровня во всем диапазоне возможно с помощью измерительной функции приемника. В данном случае перед измерением проводится калибровка прибора. Процедура калибровки включает в себя два этапа: измерение уровня сигнала генератора с помощью датчика мощности и калибровка прибора с подключенным к ВЧ-входу приемника генератором сигналов.

При этом требуется механическое переподключение измерительных приборов (см. рисунок 1). После выполнения процедуры калибровки возможно измерение уровня сигнала генератора в пределах динамического диапазона приемника FSMR3000 [5].

Рис. 1. Функциональная схема установка для измерения уровня сигнала генератора с помощью измерительного приемника FSMR3000 и датчика мощности NRPxxT
Рис. 1. Функциональная схема установка для измерения уровня сигнала генератора с помощью измерительного приемника FSMR3000 и датчика мощности NRPxxT

Благодаря высокой линейности приемника FSMR3000 данный подход позволяет измерять уровень сигнала генератора в широком диапазоне с высокой абсолютной точностью.

При измерении уровня ВЧ-сигнала генератора с помощью измерительного приемника FSMR3000 и датчика мощности необходимо учитывать следующие источники погрешности:

  • погрешность измерения абсолютной мощности датчиком мощности;

  • погрешность нелинейности измерительного приемника;

  • погрешность от диапазона к диапазону приемника;

  • погрешность из-за рассогласования выхода генератора, кабельного соединения и ВЧ-входа измерительного приемника;

  • случайная погрешность измерения.

Погрешность измерения абсолютной мощности датчиком мощности складывается из следующих составляющих:

  • погрешность калибровки;

  • погрешность нелинейности;

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

  • погрешности установки и дрейфа нуля;

  • шума отображения;

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

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

Для расчета этой погрешности используется следующая формула:

Δ_рассоглас  [дБ]=20 log⁡(1+(S_Gen-1)/(S_Gen+1)×(S_Sens-1)/(S_Sens+1)*1/√2), (1)

где Δ арассоглас — стандартная погрешность рассогласования,SGen – КСВН генератора, SSens – КСВН датчика мощности.

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

При измерениях уровня сигнала генераторов СВЧ во всем диапазоне значений, ко­торый у лучших приборов составляет от -130 дБмВт до +30 дБмВт, в измерительном приемнике требуется переключение встроенного ВЧ-аттенюатора. Возможная погрешность измерения уровня, возни­кающая в результате такого переключения, (погрешность от диапазона к диапазону приемника) устраняется за счет калиб­ровки соседнего диапазона. При постоянном уровне сигнала результат измерения после переключения диапазонов корректируется под резуль­тат измерения до переключения. Графическая иллюстрация представления диапазонов уровней мощности приемника FSMR3000 показана на рисунке 2.

Рис. 2. Графическое представление диапазонов уровней мощности прибора FSMR3000
Рис. 2. Графическое представление диапазонов уровней мощности прибора FSMR3000

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

Погрешность измерения из-за низкого отношения сигнал/шум (SNR) (случайная погрешность измерения) имеет ключевое значение при низких уровнях сигнала. В FSMR3000 используется следующая формула для расчета стандартной погрешности на основе изме­ренного отношения сигнал/шум SNR и количества усреднений n:

Δ(SNR)  [дБ]=20/√n∙log⁡(1-10^(-SNR/20 ^), (2)

Отношение сигнал/шум зависит от эффективной шумовой полосы детектирования, которая ограничивает нижний предел измерения уровня сигнала генератора. Эффективная шумовая полоса в приборе определяется используемой оконной функцией быстрого преобразования Фурье и временем измерения. В FSMR3000 используется окно с плоской вершиной для достижения максимальной точности измерений. Эффективная шумовая полоса в свою очередь обратно пропорциональна времени измерений.

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

Заметный вклад в общую погрешность измерения уровня генератора вносит погрешность из-за рассогласования источника, кабельного соединения и ВЧ-входа измерительного приемника при изменении КСВН источника. КСВН генератора изменяется при снижении мощности генератора вследствие переключения ступенчатого аттенюатора на выходе генератора. В этом случае возникает погреш­ность, зависящая от КСВН FSMR3000 и генератора. Различные методы измерения КСВН генераторов сигналов рассмотрены в [6]. Поскольку при измерении уровня мощности генератора с помощью FSMR3000 обычно изменяется настройка его входного аттенюатора и, следовательно, КСВН, эти дополнительные изменения КСВН приводят к увеличению погрешности измерения, связанной с рассогласованием источника сигнала и измерительного приемника.

Чтобы уменьшить эту погрешность рекомендуется использовать внеш­ний прецизионный аттенюатор с ослаблением 10 дБ, либо модуль датчика мощности R&S®NRP-Z27 или R&S®NRP-Z37 (далее – NRP-Z27/Z37). Аттенюатор следует подсоединять непосредственно к испытуемому ге­нератору. При использовании высоко­качественного измерительного кабеля с КСВН менее 1,2 его можно не учиты­вать при расчетах, поскольку доминирующим фактором будет КСВН FSMR3000.

Отличительной особенностью датчика мощности NRP-Z27/Z37 является его низкий постоянный КСВН, который слабо зависит от диапазона измерения FSMR3000 из-за хорошей развязки модуля. Тем самым уменьшаются погрешности, которые обычно возникают при изменении импеданса нагрузки (КСВН датчика и FSMR3000 при переключении входного аттенюатора). Однако при этом вносимые потери модуля датчика мощности NRP-Z27/Z37 снижают входную чувствительность измерительного приемника, что накладывает определенное ограничение на их применение для измерения низких уровней сигнала генератора.

Рис. 3. Установка для измерения уровня сигнала генератора с помощью FSMR3000 и модуля датчика мощности NRP-Z27/37
Рис. 3. Установка для измерения уровня сигнала генератора с помощью FSMR3000 и модуля датчика мощности NRP-Z27/37

Модуль датчика мощности NRP-Z27/37 позволяет сочетать преимущес­тва термоэлектрических датчиков мощности с надежностью и воспроизводимос­тью прецизионного делителя мощности. Низкий постоянный КСВН и хо­рошая развязка входа FSMR3000 значительно снижают погрешности изме­рения, возникающие из-за КСВН испытуемого генератора. Данная конфигурация также устраняет необходимость механического переподключения датчика мощности и измерительного кабеля.

Анализ вклада источников погрешности в общую погрешность измерения уровня сигнала генератора с помощью измерительного приемника FSMR3000

В таблице 1 приведены результаты расчета погрешности измерения уровня мощности сигнала генератора прибором FSMR3008 с датчиком мощности NRP18T без аттенюатора и с подключенным аттенюатором, а также с датчиком мощности NRP-Z27 при нормальных условиях эксплуатации. Погрешности установки и дрейфа нуля, шум отображения датчика мощности и погрешность от диапазона к диапазону приемника пренебрежимо малы и не учитывались в расчете. Расчет проводился на основе технических данных приборов, приведенных в [2,3,7], для генератора с КСВН, равным 1,5. Использовалось значение КСВН аттенюатора, равное 1,1.

Табл. 1

Наименование погрешности

Значения погрешности, дБ

NRP18T без аттенюатора

NRP18T с аттенюатором

NRP-Z27

Погрешности измерения абсолютной мощности 0 дБмВт датчиком мощности на частоте 1 ГГц:

—  погрешности калибровки и нелинейности

— погрешность из-за рассогласования между испытуемым генератором и датчиком мощности

 

 

0,027

 

0,036

 

 

0,027

 

0,036

 

 

0,036

 

0,102

Погрешности измерения уровня -110 дБмВт измерительным приемником FSMR3000 с включенным предусилителем при времени измерения 400 мс и количестве усреднений 64:

— погрешность нелинейности измерительного приемника

— погрешность из-за рассогласования выхода генератора, кабельного соединения и ВЧ-входа измерительного приемника

— случайная погрешность измерения

 

 

 

 

 0,033

 

0,457

0,011

 

 

 

 

 0,033

 

0,100

0,037

 

 

 

 

 0,033

 

0,013

0,051

Общая погрешность при доверительной вероятности 0,95

0,921

0,241

0,249

Из результатов расчета следует, что основной вклад в общую погрешность измерения уровня сигнала генератора вносит погрешность рассогласования. Использование внешнего прецизионного аттенюатора позволяет уменьшить погрешность из-за рассогласования выхода генератора и ВЧ-входа измерительного приемника. В связи с тем, что аттенюатор вносит потери в измерительный тракт, отношение сигнал/шум ухудшается, что приводит к незначительному увеличению случайной погрешности измерения. Тем не менее, использование внешнего аттенюатора позволяет получить значительно меньшую общую погрешность измере­ния уровня сигнала генератора измерительным приемником FSMR3000. В приведенном примере она состав­ляет 0,24 дБ.

При использовании модуля датчика мощности NRP-Z27/Z37 прибор FSMR3000 обеспечивает такую же низкую погрешность измерения, что и с датчиком мощности NRPxxT и пре­цизионным аттенюатором. Особым преимуществом данной конфигура­ции является то, что измерения могут быть автоматизированы, поскольку устраняется трудоемкий процесс подключения и отключения измери­тельных кабелей, приводящий к износу ВЧ-разъемов оборудования.

Литература

  1. R&S®FSMR3000 Measuring Receiver // Product Brochure, PD 3608.5741.12, Version 02.00, October 2021/

  2. R&S®FSMR3000 Measuring Receiver // Specifications, PD 3608.5741.22, Version 01.01, October 2021.

  3. R&S®NRP Power Meter Family // Specifications, PD 3607.0852.22, Version 11.00, July 2021.

  4. Minihold R. RF Level Measurement Uncertainties with the Measuring Receiver R&S(R) FSMR // Application Note 1MA92, 03.2006.

  5. Signal Source Level Calibration using the FSMR3000 // Application Note 1SL377, Version 0e, 11.2021.

  6. Пивак А., Репин А. Измерение КСВН генераторов сигналов // ЭЛЕКТРОНИКА: Наука, Технология, Бизнес. 2021. № 1. С. 86–89.


ссылка на оригинал статьи https://habr.com/ru/post/653859/

Создание собственного React с нуля

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

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

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

Но прежде чем мы начнем, небольшое предупреждение. Хотя наша библиотека вполне рабочая (см. примеры приложений в конце), это, прежде всего, демонстрационный инструмент, а не легковесная альтернатива React. Весь проект можно найти на GitHub.

1. Архитектура библиотеки реактивного рендеринга

Главное в любой библиотеке реактивного рендеринга — это компоненты. Работа компонента заключается в управлении так называемым «реактивным пространством». Компонент содержит переменную («state») и отвечает за ее рендеринг в DOM. Здесь уже встречаются некоторые понятия, знакомые вам по написанию react-компонентов : this.state, this.setState и render.

Архитектура базового реактивного компонента. Каждая интеракция создает новое state (состояние), формирующее новую VDOM, обновляющую HTML приложения

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

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

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

2. Виртуальная DOM

По своей сути виртуальная DOM состоит из двух структур данных: дерева синтаксиса HTML и diff-дерева. Дерево HTML представляет состояние HTML в JavaScript-объекте . Это возвращаемый тип метода рендеринга компонентов и то, с чем будет взаимодействовать разработчик, использующий библиотеку. Diff-дерево описывает, какие шаги необходимо предпринять для обновления элемента HTML из состояния одного синтаксического дерева в другое.

Связь между VDOM-деревьями, diffing-алгоритмом и утилитой сравнения (diff) 
Связь между VDOM-деревьями, diffing-алгоритмом и утилитой сравнения (diff) 

2.1 Дерево синтаксиса HTML

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

export type VDOMAttributes = { [_: string]: string | number | boolean | Function }  export interface VDOMElement {   kind: 'element'   tagname: string   childeren?: VDomNode[]   props?: VDOMAttributes   key: string | number }  export interface VDOMText {   kind: 'text',   value: string   key: string | number }  export type VDomNode =  | VDOMText | VDOMElement

Определение типа VDOM.

Наша библиотека не будет поддерживать JSX, и поэтому нам нужны некоторые функции-хелперы для создании элементов VDOM удобным способом. Они будут использоваться в методах рендеринга наших компонентов и являются аналогом вызовов React.createElement, с которыми вы, возможно, сталкивались в своих пакетах JavaScript.

export const createElement = (tagname: string, props: VDOMAttributes & { key: string }, ...childeren: VDomNode[]): VDOMElement => {   const key = props.key   delete props.key   return ({ kind: 'element',     tagname, props,     childeren, key   }) }  export const createText = (value: string | number | boolean, key: string = '') : VDOMText => ({   key, kind: 'text', value: value.toString() })

Функции для создания элементов VDOM.

2.2 Структура данных diff

Начнем с механизма сравнения (diffing), который работает с одним элементом, а затем расширим его за счет поддержки дочерних. Сначала создадим тип со всеми операциями. Операция — это мутация DOM, которая будет произведена с одним элементом HTML. Основными операциями являются update и replace. Также есть skip, указывающая на отказ от изменений.

type AttributesUpdater = {   set: VDOMAttributes   remove: string[] }  interface UpdateOperation {   kind: 'update',   attributes: AttributesUpdater,   childeren: ChildUpdater[] }  interface ReplaceOperation {   kind: 'replace',   newNode: VDomNode }  interface SkipOperation {    kind: 'skip'  }  export type VDomNodeUpdater =    | UpdateOperation   | ReplaceOperation   | SkipOperation

Типы для операций, применяемых к одному элементу.

Операция update фактически представляет собой diff-дерево и состоит из небольших шагов, которые необходимо предпринять для обработки результата большинства вызовов setState. Также здесь содержится ссылка на обновления для дочерних элементов. Операции для дочерних элементов немного отличаются, потому что они будут применяться к коллекции элементов. Помимо тех операций, которые мы уже определили, они также включают insert и remove:

interface RemoveOperation {   kind: 'remove' }  interface InsertOperation {   kind: 'insert',    node: VDomNode }  export type ChildUpdater =   | UpdateOperation   | ReplaceOperation   | RemoveOperation   | SkipOperation   | InsertOperation

Типы для операций, которые могут быть применены к дочерним элементам.

3. Создание diff’ов

Теперь нам удалось построить виртуальную DOM и diff, далее создадим то, что делает VDOM стоящим усилий: diffing-алгоритм . Это позволит нашим компонентам эффективно переходить из одного состояния в другое, это важно, когда речь о плавной работе реактивного приложения. Медленный или даже прерывистый diffing-алгоритм полностью уничтожает пользовательский опыт любого приложения, независимо от того, насколько тщательно проработаны его компоненты.

Мы разложим данный фрагмент на две части: сначала diffing отдельного элемента, потом diffing коллекции (дочерних) элементов, так же, как это делалось для определений типов.

3.1 Создание diff’а элемента

При diffing’е VDOM мы имеем дело с двумя различными вещами, которые могут обновляться: текстовые узлы и HTML-элементы. Начнем с текстовых узлов, т.к. они довольно просты. Если оба элемента являются текстовыми узлами и их значение не изменилось, мы возвращаем операцию skip. В любом другом случае когда есть текстовый узел, нам придется заменить старый элемент новым:

export const createDiff = (oldNode: VDomNode, newNode: VDomNode): VDomNodeUpdater => {   // skip over text nodes with the same text   if (oldNode.kind == 'text' && newNode.kind == 'text' && oldNode.value == newNode.value) {     return skip()   }    // If a textnode is updated we need to replace it completly   if (oldNode.kind == 'text' || newNode.kind == 'text') {       return replace(newNode)   }   // rest of the diffing }

Diffing текстовых узлов.

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

export const createDiff = (oldNode: VDomNode, newNode: VDomNode): VDomNodeUpdater => {   // diff text nodes      // If the tagname of a node is changed we have to replace it completly   if (oldNode.tagname != oldNode.tagname) {     return replace(newNode)   }    // diff elements }

Обновленное tagname означает, что весь компонент должен быть смонтирован заново.

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

export const createDiff = (oldNode: VDomNode, newNode: VDomNode): VDomNodeUpdater => {   // diff text nodes and replaced elements      // get the updated and replaced attributes   const attUpdater: AttributesUpdater = {     remove: Object.keys(oldNode.props || {})       .filter(att => Object.keys(newNode).indexOf(att) == -1),     set: Object.keys(newNode.props || {})       .filter(att => oldNode.props[att] != newNode.props[att])       .reduce((upd, att) => ({ ...upd, [att]: newNode.props[att] }), {})   }    const childsUpdater: ChildUpdater[] = childsDiff((oldNode.childeren || []), (newNode.childeren || []))    return update(attUpdater, childsUpdater) }

Генерация всех данных для обновления.

Мы завершили логику diffing’а для отдельных элементов. Как и в случае с деревом синтаксиса HTML, позже добавим поддержку diffing’а и монтирования компонентов. Переходим к логике diffing’а для дочерних элементов.

3.2 Создание diff для дочерних элементов

Последней частью diffing-алгоритма является diffing для дочерних элементов. Это, самая сложная часть VDOM, потому что существует множество вариантов того, что может произойти с дочерними элементами. Можно вставить новый элемент в начало, в середину или в конец; удалить, обновить или пересортировать существующие элементы. Здесь в игру вступает key элемента: предполжим, что если ключи совпадают, то и элементы одинаковы (но могут быть изменены). С помощью ключей определим, что первый элемент в текущем дереве соответствует третьему в новом дереве, потому что добавилось два элемента впереди. Без ключей это практически невозможно узнать.

Пример с diff-операциями между двумя коллекциями элементов VDOM
Пример с diff-операциями между двумя коллекциями элементов VDOM

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

const childsDiff = (oldChilds: VDomNode[], newChilds: VDomNode[]): ChildUpdater[] => {   const remainingOldChilds: [string | number, VDomNode][] = oldChilds.map(c => [c.key, c])   const remainingNewChilds: [string | number, VDomNode][] = newChilds.map(c => [c.key, c])    const operations: ChildUpdater[] = []      return operations }

Базовая структура для функции childsDiff.

В строке 6 добавим логику, которая обрабатывает все дочерние элементы и заполняет operations массив операциями для преобразования старого дерева в новое. Этот алгоритм будет сосредоточен на поиске элементов (= ключей), которые присутствуют как в старой, так и в новой VDOM. Для них будем генерировать обновления, а все остальные элементы будут удалены или вставлены. Ниже показана эта часть логики. Пока в стеках остаются обновленные дочерние элементы, мы продолжаем генерировать для них update операции.

const childsDiff = (oldChilds: VDomNode[], newChilds: VDomNode[]): ChildUpdater[] => {   const remainingOldChilds: [string | number, VDomNode][] = oldChilds.map(c => [c.key, c])   const remainingNewChilds: [string | number, VDomNode][] = newChilds.map(c => [c.key, c])    const operations: ChildUpdater[] = []    // find the first element that got updated   let [ nextUpdateKey ] = remainingOldChilds.find(k => remainingNewChilds.map(k => k[0]).indexOf(k[0]) != -1) || [null]    while(nextUpdateKey) {           // process other children here          // create the update     operations.push(createDiff(remainingOldChilds.shift()[1], remainingNewChilds.shift()[1]))      // find the next update     ; [ nextUpdateKey ] = remainingOldChilds.find(k => remainingNewChilds.map(k => k[0]).indexOf(k[0]) != -1) || [null]   }      // process the remaining children here      return operations }

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

В этой функции важно отметить, что nextUpdatedKey не обязательно должен быть первым из элементов как в remainingOldChilds, так и в remainingNewChilds. Перед ними в стеках могут быть элементы, которые были удалены или вставлены и потому не содержатся одновременно в обеих коллекциях. Сначала надо удалить все старые элементы и вставить новые, прежде чем мы сможем создать обновление.

// create stacks and the first update   while(nextUpdateKey) {      // first remove all old childs before the update     removeUntilkey(operations, remainingOldChilds, nextUpdateKey)          // then insert all new childs before the update     insertUntilKey(operations, remainingNewChilds, nextUpdateKey)      // generate update }
const removeUntilkey = (operations: ChildUpdater[], elems: [string | number, VDomNode][], key: string | number) => {   while(elems[0] && elems[0][0] != key) {     operations.push(remove())     elems.shift()   } }  const insertUntilKey = (operations: ChildUpdater[], elems: [string | number, VDomNode][], key: string | number) => {   while(elems[0] && elems[0][0] != key) {     operations.push(insert(elems.shift()[1]))   } }

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

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

 while(nextUpdateKey) {     // generate updates here   }    // remove all remaing old childs after the last update   removeUntilkey(operations, remainingOldChilds, undefined)    // insert all remaing new childs after the last update   insertUntilKey(operations, remainingNewChilds, undefined)    return operations }

Обработаем оставшиеся операции remove и insert после завершения всех обновлений.

Теперь есть полноценный diffing-алгоритм, способный генерировать ключевые diffs сложных деревьев и позволит получить приемлемый эффективный diff. Возможны оптимизации, каждая пара remove и insert выполняется с помощью одной replace. Можно поддерживать сортировку элементов по ключам, что важно для некоторых приложений.

Пример пересортированных ключей. B будет удален и вставлен. Если B стейтфул-компонент, то B возвращается в исходное состояние 
Пример пересортированных ключей. B будет удален и вставлен. Если B стейтфул-компонент, то B возвращается в исходное состояние 

4. Соединение виртуальной и реальной DOM

Теперь есть готовая виртуальная DOM, пора соединить ее с реальной. Для этого понадобятся две отдельные части: функция, которая берет VDOM и рендерит ее в DOM, и функция, которая принимает diff и применяет его к элементу в DOM.

Связь между diff, элементом HTML и функцией apply 
Связь между diff, элементом HTML и функцией apply 

4.1 Рендеринг виртуальной DOM

Чтобы начать использовать новый виртуальный DOM, — надо осуществить его рендеринг в реальный. Это будет рендеринг первой версии компонента. После этого функция applyDiff станет отвечать за соединение виртуальной и реальной DOM друг с другом. По сути, она сводится к вызову document.createElement для каждого элемента в дереве:

const renderElement = (rootNode: VDomNode): HTMLElement | Text => {   if (rootNode.kind == 'text') {     return document.createTextNode(rootNode.value)   }    const elem = document.createElement(rootNode.tagname)    for (const att in (rootNode.props || {})) {     (elem as any)[att] = rootNode.props[att]   }    (rootNode.childeren || []).forEach(child =>     elem.appendChild(renderElement(child))   )    return elem }

Преобразование VDOM в HTML-элемент.

Мы присваиваем каждое свойство непосредственно элементу. Конечно, в React эта логика реализована не так. Правильной будет установка атрибутов с помощью setAttribute и использование синтетической системы для связи событий внутри DOM и VDOM. Здесь я сократил часть статьи, чтобы не увеличивать ее в размерах…

4.2 Применение diff к элементу HTML

Другая половина рендеринга — это применение diff. Для этого функция берет HTMLElement и diff и применяет его к элементу. Здесь вы видите те же операции, которые мы определили ранее. Для краткости я опустил все валидации, которые позволяет TypeScript, единственное, что мы проверяем, это то, что мы присваиваем атрибуты действительному элементу, а не текстовому узлу.

export const applyUpdate = (elem: HTMLElement | Text, diff: VDomNodeUpdater): HTMLElement | Text => {   if (diff.kind == 'skip') return elem     if (diff.kind == 'replace') {     const newElem = renderElement(diff.newNode)     elem.replaceWith(newElem)     return newElem   }    if('wholeText' in elem) throw new Error('invalid update for Text node')    for (const att in diff.attributes.remove) {     elem.removeAttribute(att)   }    for (const att in diff.attributes.set) {     (elem as any)[att] = diff.attributes.set[att]   }    applyChildrenDiff(elem, diff.childeren)    return elem }

Применение diff между двумя элементами VDOM к элементу DOM.

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

const applyChildrenDiff = (elem: HTMLElement, operations: ChildUpdater[]) => {   let offset = 0   for (let i = 0; i < operations.length; i++) {     const childUpdater = operations[i]      if (childUpdater.kind == 'skip') continue      if (childUpdater.kind == 'insert') {       if (elem.childNodes[i + offset - 1]) elem.childNodes[i + offset - 1].after(renderElement(childUpdater.node))       else elem.appendChild(renderElement(childUpdater.node))       continue     }          const childElem = elem.childNodes[i + offset]      if (childUpdater.kind == 'remove') {       childElem.remove()       offset -= 1       continue     }      applyUpdate(childElem, childUpdater)   } }

Применение набора операций к дочерним элементам HTML-элемента.

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

5. Компоненты и реактивные области применения

Знакомые с React, знают что компоненты — это то, что содержит состояние. Хотя с точки зрения пользователей это действительно важная часть компонентов, в исходном коде реактивного фреймворка основная задача компонента — управлять реактивной областью. Что такое реактивная область? Это часть дерева VDOM, которая отвечает за свои собственные обновления. То есть компонент имеет ссылку на html-элемент в DOM, где он установлен, и создает diffs для этого элемента на основе состояния и пропсов. Даже если компонент принадлежит родителю, он все равно будет отвечать за свой собственный рендеринг и diffing.

Хуки жизненного цикла для наших компонентов
Хуки жизненного цикла для наших компонентов

5.1 Создание класса компонента

Мы начнем с создания базового класса для всех компонентов. Этот класс будет хранить текущий пропс, состояние, корневой элемент и VDOM. Базовый набросок такого класса показан ниже. Многие атрибуты и методы знакомы по библиотекам типа React, например, props, state, setState, componentDidMount и render.

Существуют методы, которые выделены только для внутреннего использования. setProps будет вызываться во время обновления родителя, чтобы предоставить этому компоненту новые пропсы. Эта функция возвращает разницу между его VDOM и новой VDOM родителя. initProps вызывается во время процесса монтирования и возвращает начальную VDOM, которая будет рендериться в реальной DOM. После этого будет вызван notifyMounted, и компонент будет полностью смонтирован в DOM. Кроме того, у нас есть метод unmount, используемый при удалении компонента из DOM.

export abstract class Component<P, S> {          protected props: P     protected state: S          private currentRootNode: VDomNode     private mountedElement: HTMLElement | Text          protected setState(updater: (s:S) => S) { }          // called when the mounted element recieves new props     public setProps(props: P): VDomNodeUpdater { }          // called when mounting the element to generate the first VDOM state     public initProps(props: P): VDomNode { }          // this gets called when the component is mounted in the real DOM     public notifyMounted(elem: HTMLElement | Text) { }      // this gets called when the component will be unmounted in the real DOM     public unmount() { }          // hooks     public componentDidMount() {}     public componentWillRecieveProps(props: P, state: S): S { return state }     public componentDidUpdate() { }     public componentWillUnmount() { }          // the render function each component has to implement     public abstract render(): VDomNode }

Набросок для класса Component.

Еще вы видите пустые хук-методы, которые отдельные компоненты могут переопределять. Подключим их к внутренним компонентам нашей библиотеки по ходу работы.

5.2 Монтаж компонентов

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

export interface VDOMComponent {   kind: 'component'   instance?: Component<any, any>   props: object   component: { new(): Component<any, any> }   key: string | number }  export type VDomNode =    | VDOMText   | VDOMElement   | VDOMComponent

Расширение для определения VDOM для компонентов.

Свойство instance предназначено только для внутреннего использования. В нем будет храниться реальный экземпляр компонента, с состоянием и т.д. внутри. В функции diffing мы обязательно скопируем существующие инстансы компонентов из старого дерева в новое, чтобы они не потерялись между ними. Важно отметить, что функция рендеринга компонента не создаст никаких инстансов дочерних компонентов. Вместо этого она вернет узел со ссылкой на класс компонента и значением пропса. Создание компонента будет происходить «за кулисами».

export const createComponent = <P extends object>(component: { new(): Component<P, any> }, props: P & { key: string }): VDOMComponent => {   const key = props.key   delete props.key   return ({     component, props, key, kind: 'component'   })   }

Функция для построения узла компонента в рендер-функции .

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

Когда дело доходит до «монтирования» компонента, необходимо рассмотреть еще два сабкейса: компонент уже инстанцирован или его еще нужно создать. Если компонент уже имеет инстанс, процесс довольно прост: мы вызываем функцию render для получения текущего представления состояния VDOM, создаем из него HTML-элемент и вызываем notifyMounted для компонента.

Все, что происходит во время монтажа компонента в функции render, разделено между обязанностями функции и класса компонента. Стрелки вниз - это вызовы из функции, стрелки вверх - возвраты из методов
Все, что происходит во время монтажа компонента в функции render, разделено между обязанностями функции и класса компонента. Стрелки вниз — это вызовы из функции, стрелки вверх — возвраты из методов

Когда компонент все же нужно создать, необходимо выполнить еще несколько шагов. Сначала создадим инстанс компонента с помощью ключевого слова new. Он будет назначен пропсу instance VDOM. Таким образом, он сохраняется для использования при следующем рендеринге, поэтому нам не придется заново создавать стейтфул-компоненты. После этого мы инициализируем пропс компонента. Это вернет первоначальную VDOM компонента, которую можно отрендерить в DOM. Наконец, вызываем notifyMounted. Дополнительный код, имплементирующий данную логику для функции renderElement, показан ниже.

const renderElement = (rootNode: VDomNode): HTMLElement | Text => {   //  render text nodes    if(rootNode.kind == 'component') {     if(rootNode.instance) {       const elem = renderElement(rootNode.instance.render())       rootNode.instance.notifyMounted(elem as HTMLElement)       return elem     }      rootNode.instance = new rootNode.component()     const elem = renderElement( rootNode.instance.initProps(rootNode.props))     rootNode.instance.notifyMounted(elem as HTMLElement)     return elem   }    // render elements }

Монтаж компонента в функции рендеринга.

Чтобы это действительно работало, мы также должны имплементировать различные методы в классе компонента. Первый метод — initProps. Он отвечает за инициализацию props и выполнение первого render. Он сохранит и вернет результирующее дерево VDOM вызывающей стороне. Затем вызывающий будет отвечать за размещение его в DOM.

Инициализация пропсов в компоненте.

Другой метод, который нам нужен для завершения процесса монтирования, — это notifyMounted. Это обратный вызов (callback), который будет вызван render (потенциально через applyUpdate) в тот момент, когда мы создадим HTML-элемент для исходного дерева VDOM. Этот метод также вызовет componentDidMount, хук, который компоненты могут имплементировать, чтобы сделать что-нибудь после того, как произойдет рендеринг в DOM. Для этого используется setTimeout, чтобы гарантировать, что хук будет вызван после того, как текущая функция завершит монтаж компонента, а не во время него.

export abstract class Component<P, S> {    public notifyMounted(elem: HTMLElement) {     this.mountedElement = elem     setTimeout(() => this.componentDidMount())   }        public componentDidMount() {} }

Коллбэк для рендеринга и хук componentDidUpdate.

5.3 Обновление компонента

Теперь, когда можно встраивать компоненты в DOM, займемся их обновлением. Обновление происходит двумя способами: компонент может обновить свое state или получить новые props от своего родителя. В первом случае именно компонент отвечает за выполнение обновления в DOM, в последнем — он вернет diff своему родителю.

В обоих случаях процесс схож: мы рендерим компонент, создаем diff с этим новым деревом и сохраненным существующим, обновляем currentRootNode новым деревом VDOM и возвращаем diff. Реализация показана ниже. Метод getUpdateDiff будет использоваться как setState и setProps и выполнит тяжелую работу по управлению реактивной областью компонента. Именно здесь планируется вызов componentDidUpdate для выполнения, после того как обновление будет завершено.

export abstract class Component<P, S> {    private getUpdateDiff() : VDomNodeUpdater {     const newRootNode = this.render()     const diff = createDiff(this.currentRootNode, newRootNode)     if(diff.kind == 'replace') elem => this.mountedElement = elem     this.currentRootNode = newRootNode     setTimeout(() => this.componentDidUpdate())     return diff   } }

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

Здесь видно  callback для операций replace. Это необходимо для того, чтобы убедиться, что свойство mountedElement нашего компонента продолжает указывать на правильный HTML-элемент в случае замены корневого элемента. Для этого нам понадобится несколько дополнений к VDOM и рендерингу:

export const applyUpdate = (elem: HTMLElement | Text, diff: VDomNodeUpdater): HTMLElement | Text => {   if (diff.kind == 'skip') return elem     if (diff.kind == 'replace') {     const newElem = renderElement(diff.newNode)     elem.replaceWith(newElem)     if(diff.callback) diff.callback(newElem)     return newElem   }      // rest of the function }
interface ReplaceOperation {   kind: 'replace',   newNode: VDomNode   callback?: (elem: HTMLElement | Text) => void }

Добавление обратных вызовов для операции replace и функции applyUpdate.

5.4 Обновление компонентов с помощью setState

Когда компонент обновляется с помощью setState, нам нужно: установить свойство state; получить diff с помощью getUpdateDiff; применить этот diff к mountedElement. Кроме того, я добавил if-оператор, чтобы при попытке обновить не смонтированный компонент выкидывалась ошибка. Или можно обновить состояние и вернуться, тогда новое состояние будет использовано во время первого рендеринга в initProps. Можно распознать это как предупреждение от React. Поскольку мы уже реализовали всю логику обновления компонентов, этот метод стал довольно коротким и простым:

export abstract class Component<P, S> {    protected setState(updater: (s:S) => S) {     if(this.mountedElement == undefined) throw new Error("you are updating an unmounted component")     this.state = updater(this.state)     applyUpdate(this.mountedElement, this.getUpdateDiff())   } }

Метод setState для наших компонентов.

5.5 Обновление компонентов с помощью setProps

Другим способом обновления компонента является метод setProps. Он очень похож на setState. Его полное определение найдете ниже. Здесь мы также выкидываем ошибку, если компонент еще не смонтирован. Одно отличие от классических хуков жизненного цикла React в том, что мы позволяем componentWillRecieveProps возвращать новое состояние, которое будет использоваться до того, как мы заново отрендерим компонент с новыми пропсами.

export abstract class Component<P, S> {    public setProps(props: P): VDomNodeUpdater {     if(this.mountedElement == null)       throw new Error("You are setting the props of an inmounted component")      this.state = this.componentWillRecieveProps(props, this.state)     this.props = props     return this.getUpdateDiff()   } }

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

В отличие от setState, setProps вызывается в функции diffing, когда пропсы узла компонента изменились. Чтобы осуществить это на практике, добавим поддержку компонентов в diffing. В процессе работы с компонентом могут произойти 3 вещи: мы обновляем существующий компонент, демонтируем его или монтируем новый.

Обзор того, что происходит при обновлении компонента с дочерним. Родитель не делает diff VDOM дочернего компонента, но при этом несет ответственность за применение diff'ов, сгенерированных дочерним компонентом.
Обзор того, что происходит при обновлении компонента с дочерним. Родитель не делает diff VDOM дочернего компонента, но при этом несет ответственность за применение diff’ов, сгенерированных дочерним компонентом.

Сценарий, который мы будем реализовывать в первую очередь, — это обновление существующего компонента. Первым шагом будет копирование существующего инстанса из oldNode в newNode. После этого мы проверяем, изменились ли props, если да, то устанавливаем props компонента. Полученный в результате diff и есть тот diff, который мы непосредственно возвращаем из нашей функции. В случае, если props не изменился, мы возвращаем операцию skip .

export const createDiff = (oldNode: VDomNode, newNode: VDomNode): VDomNodeUpdater => {   // diff text nodes        if(oldNode.kind == 'component' && newNode.kind == 'component' && oldNode.component == newNode.component && oldNode.instance) {     newNode.instance = oldNode.instance     if(isEqual(oldNode.props, newNode.props)) return skip()     return newNode.instance.setProps(newNode.props)   }    // diff elements }

Обновление props для существующего компонента внутри diffing-кода.

Второй сценарий, который мы реализуем, — это замена любого узла компонентом. Это весьма похоже на уже рассмотренный нами монтаж, но только теперь он выполняется внутри функции diffing для генерации операции replace. Примечательно то, что мы используем callback операции replace, чтобы убедиться, что notifyMounted все еще вызывается внутри функции рендеринга.

export const createDiff = (oldNode: VDomNode, newNode: VDomNode): VDomNodeUpdater => {      // updating components    if(newNode.kind == 'component') {     newNode.instance = new newNode.component()     return {        kind: 'replace',        newNode: newNode.instance.initProps(newNode.props),       callback: e => newNode.instance.notifyMounted(e)     }   }      // diffing elements }

Монтаж компонента внутри diffing.

5.6 Демонтаж компонентов

Последний сценарий — это замена компонента на другой. Это приводит к «демонтажу» компонента. Должно произойти несколько вещей: мы вызываем unmount  на компонент, компонент вызывает хук componentWillUnmount, компонент обнуляет свою ссылку на DOM и мы удаляем HTML-элемент из DOM.

const removeUntilkey = (operations: ChildUpdater[], elems: [string | number, VDomNode][], key: string | number) => {   while(elems[0] && elems[0][0] != key) {     if(elems[0][1].kind == 'component') {       elems[0][1].instance.unmount()       elems[0][1].instance = null     }     operations.push(remove())     elems.shift()   } }
export const createDiff = (oldNode: VDomNode, newNode: VDomNode): VDomNodeUpdater => {    // update component props    if(oldNode.kind == 'component') {     oldNode.instance.unmount()     oldNode.instance = null     return replace(newNode)   }    // mount new components }

Обновление diffing-кода для демонтажа компонентов, которые будут удалены.

Последний метод, который необходимо реализовать в нашем компоненте, — это unmount. Этот метод вызывает хук componentWillUnmount, на этот раз без setTimeout, потому что хук запускается до начала фактического демонтажа. Далее мы устанавливаем для mountedElement значение null. Это делается по двум причинам: любые обновления этого компонента теперь будут вызывать ошибку, и обеспечивается освобождение HTML-элемента для сборки мусора.

export abstract class Component<P, S> {        public unmount() {     this.componentWillUnmount()     this.mountedElement = null   }        public componentWillUnmount() { } }

Демонтаж компонента.

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

6. Создание примера приложения

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

interface NewItemFormState {     name: string }  interface NewItemFormProps {     addItem: (name: string) => void }  class NewItemForm extends Component<NewItemFormProps, NewItemFormState> {      state = { name: '' }      render() {         return createElement(             'form',             { key: 'f',               onsubmit: (e: Event) => {                 e.preventDefault()                 this.props.addItem(this.state.name)                 this.setState(() => ({ name: '' }))               }             },             createElement('label', { key: 'l-n', 'for': 'i-n' }, createText('New item')),             createElement('input',                 { key: 'i-n', id: 'i-n',                    value: this.state.name,                   oninput: (e: any) => this.setState(s => ({...s, name: e.target.value})) }             ),         )     } }

Компонент управляемой формы с нашей библиотекой реактивного рендеринга.

class ToDoComponent extends Component<{}, ToDoState> {      state: ToDoState = { items: [] }      toggleItem(index: number) {         this.setState(s => ({items: s.items.map((item, i) => {             if(index == i) return { ...item, done: !item.done }             return item         })}))     }      render() {         return createElement('div', { key: 'root'},             createComponent(NewItemForm, {                 key: 'form',                 addItem: n => this.setState(s => ({ items: s.items.concat([{name: n, done: false}])}))             }),             createElement('ul', { key: 'items' },                  ...this.state.items.map((item: ToDoItem, i) =>                      createElement( 'li', { key: i.toString()},                         createElement('button', {                                  key: 'btn',                                 onclick: () => this.toggleItem(i)                             },                              createText(item.done ? 'done' : '-')),                         createText(item.name, 'label')                 ))             )         )     }

Простое todo-приложение с нашей библиотекой реактивного рендеринга.

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

7. Заключение

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


React-hooks появились в React с версии 16.8, сегодня они используются уже повсеместно. Приглашаем всех заинтересованных на интенсив, на котором разберемся, как работать с React-hooks, создадим компонент с использованием hooks, а также научимся делать кастомные hooks. Поработаем с react-testing-library и научимся тестировать компоненты и кастомные hooks. Регистрация по ссылке.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/653861/

Байт, байт, кладбище, юзер, или Смерть в цифровую эпоху


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

По данным Good Trust — американского сервиса по управлению цифровыми архивами — на одном  только Facebook насчитывается около 30 миллионов аккаунтов умерших людей. По мере роста цифрового имущества (аккаунты, подписки, домены, фотоархивы, электронные кошельки), проблема их наследования становится все более важной: на эту тему выпускают книги, а СМИ пишут подробные инструкции, как подготовиться к загробной цифровой жизни.

Во многих компаниях есть политики, касающееся смерти пользователя. Вслед за Google и Facebook, в конце 2021 года Apple запустила функцию Digital Legacy. Пользователь может назначить до пяти «контактов-наследников», которые смогут унаследовать его Apple ID и все данные в случае смерти. Наследникам нужно будет принять приглашение и предоставить доказательства, и как только они будут одобрены, у них будет полный доступ к паролям, фотографиям, документам, сообщениям и прочим данным Apple ID.

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


В России


Если вдруг вы внезапно умерли и не оставили под клавиатурой бумажку с паролями и просьбой почистить ваши закладки и историю браузера, то доступ к вашим российским почтовым аккаунтам умрет вместе с вами. Здесь ваше цифровое наследие защищает 23-я статья Конституции РФ — «Каждый имеет право на тайну переписки». Компании не выдадут ваши цифровые секреты (если, конечно, их настоятельно не попросят люди в сером).

Яндекс, ссылаясь на 23-ю статью, не имеет права передавать доступ к аккаунту в случае смерти. А вот что касается YooMoney, то родственник может унаследовать деньги с аккаунта, но опять же не доступ к нему.

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

Mail.ru удаляет или блокирует ящик по запросу родственников, предоставивших все необходимые документы.

Для сравнения: в Google есть функция «На всякий случай» — она позволяет человеку назначить до 10 доверенных лиц, которые в случае его смерти получат доступ к аккаунту умершего пользователя.

Что касается Outlook, то родственники могут запросить доступ к учетной записи Microsoft. Компания предоставит часть какой-то информации и закроет аккаунт. 

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

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

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

Соцсеть Одноклассники по запросу от родственников может удалить страницу умершего человека или установить на аватарку траурную рамку.

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

В России цифровое наследование плохо регулируется. И если на Западе, например в США и Германии, закон позволяет передать цифровые архивы по наследству, то в России все пока находится на зачаточном уровне и решается в судебном порядке.

Ст. 1112 ГК определяет наследство, как вещи, имущественные права и обязанности. При этом электронная почта имуществом не является, как не является и интеллектуальной собственностью. 

Но даже если в законе и есть четкие формулировки, то от путаницы не спасают даже они. Например, закон «О цифровых финансовых активах» определяет криптовалюту как имущество. Но несмотря на это, механизм ее наследования не до конца ясен. 

Так что вывод: чтобы подготовиться к цифровой загробной жизни в России, важно позаботиться об оформлении завещания. В нем можно прописать, что именно вы хотите передать по наследству, а также возложить на родственников какие-либо обязанности (например, удалить или сохранить аккаунт в соцсети).

В мире


Определить, что произойдет с аккаунтом GitHub после смерти владельца может предварительно назначенный преемник или другое уполномоченное лицо (сотрудник или деловой партнер). Преемник может:

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

Управлять общедоступными репозиториями преемник сможет спустя 7 дней  после предъявления свидетельства о смерти. При этом в сам аккаунт войти он не сможет.

Юзер «Смерть» на GitHub сделал лишь один коммит

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

С 2020 года Twitter работает над тем, как увековечить память умерших пользователей, но до сих пор пока никаких плашек или надписей, как в инстаграме или линкедине, не появилось.

Заблокированная на территории РФ LinkedIn, где в основном ищут работу российские айтишники, позволяет по запросу либо удалить, либо увековечить аккаунт умершего человека.

Цифровые поверенные


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

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

Все описанные ниже сервисы — зарубежные (и это только малая их часть). В США, например, достаточно развитое законодательство в плане наследования виртуальной собственности. В России же такого нет из-за юридических сложностей. Единственный подобный сервис в РФ пока в стадии разработки — это «Капсула времени» на базе блокчейна. 

В начале статьи мы говорили о сервисе Good Trust, позволяющим управлять своим цифровым наследием. Good Trust был основан в начале пандемии Рикардом Штайбером, потерявшим своего отца и друга.

Создатели сервиса популяризируют тему подготовки к смерти в контексте цифровых активов. На сайте есть множество статей — что делать с накопленными милями авиакомпаний и плейлистами Spotify в случае смерти родственника, как написать впечатляющий некролог, как не стать жертвой мошенников и т. д.

В Средневековье были популярны тексты Ars Moriendi («Искусство умирать»), где рассказывалось, как «умереть хорошо». 

В наше время людям тоже нужны такие инструкции. Поэтому Штайбер издал книгу Digital Legacy: Take Control of Your Online Afterlife, где подробно рассказывает про юридические аспекты, как разработать правильную стратегию для цифровых активов (фотографий, электронных писем, банковских счетов), как защитить контент в соцсетях и как обсудить и решить все проблемы наследования с семьей и друзьями.

Демоны соцсетей и ангел LastPass

Сервис Everplans позволяет систематизировать документы — завещания, страховые полисы, медицинские документы, информацию о домашних животных, цифровые учетные записи и многое другое. На сайте есть формы предварительных распоряжений, законы о цифровом имуществе, реестры донорства органов, завещание и многое другое для разных штатов США. 

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

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

Британский сервис Farewill позиционирует себя как высокорейтингового эксперта по смерти. Это онлайн-сервис по составлению завещаний — буквально за 15 минут, не отходя от монитора человек может написать завещание либо надиктовать его по телефону и заказать доставку. Компания предоставляет юридическую информацию и помогает организовать похороны или кремацию. Человек может спланировать онлайн как свои собственные похороны, так и похороны умершего родственника.

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

Подобным образом работает и сервис Bank of Memories, построенный на блокчейне. Он позволяет передавать информацию в будущее, отправлять сообщения потомкам и передавать права на свои данные.

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

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

Поэтому Opera запустила первое в мире виртуальное кладбище геймеров — там можно похоронить аватары людей, которые бросили игру из-за дел в реальной жизни.

Двумерное кладбище построено в 8-битном стиле. Там есть конструктор надгробий, с помощью которого люди могут выбрать скин памятника, выбить ник своего друга, добавить панегирик и указать дату, когда человека последний раз видели в сети. Затем надгробие оказывается на кладбище — им можно поделиться в соцсетях или отправить «погребенному» другу.

Кликните на могилку и нажмите F

Резюме


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

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

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

Следуйте за белым кроликом, кликнув на картинку ниже😉


ссылка на оригинал статьи https://habr.com/ru/company/getmeit/blog/653865/

12 вредных советов для пользователей услуг Colocation в Западной Европе. Часть II

Напоминаем: если вам нужно разместить несколько серверов в Западной Европе, даже и не думайте читать договор — они все равно типовые… А наши вредные советы помогут гарантированно угробить иностранный бизнес или навредить карьере. Вот и вторая их часть подоспела.

Первая часть вредных советов по Colocation в западном ЦОДе

7. Планируйте работы с оборудованием на вечернее время и выходные

Ожидания

Клиенты российских ЦОДов привыкли проводить серьезные модификации размещенной на арендуемых площадях инфраструктуры в неурочное время. В Западной Европе наверняка можно делать то же самое с привлечением местного персонала.

Реальность  

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

Как правильно?

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

8. Если поддержка отказывается решить вопрос, смело обращайтесь к руководству площадки

Ожидания

Если что-то сломается ночью или на выходных, а поддержка откажется оперативно решить вопрос, можно связаться с руководством дата-центра, и нам оперативно все починят. Лучше всего написать в «Фейсбук» или «Твиттер».

Реальность  

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

Как правильно?

Даже не пробуйте решить вопросы неформально. 

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

Ожидания

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

Реальность  

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

Кейс HOSTKEY

Однажды мы подписали не глядя контракт на доступ в Интернет с сетевой компанией, а качество услуг оказалось неудовлетворительным — связанность с Восточной Европой не подошла клиенту, и он не смог использовать канал. Формально провайдер условий SLA не нарушил, все пингуется. В итоге нам пришлось платить в течение года, хотя фактически услугой мы не пользовались.

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

Как правильно?

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

10. Не задумывайтесь о логистических проблемах

Ожидания

Можно купить серверы и привезти их в западный дата-центр, заплатив относительно небольшую сумму за установку в стойки. Со старым «железом» проблем тем более не возникнет: его легко продать на eBay.

Реальность  

На практике в западном дата-центре вам предложат самостоятельно разгружать фуру (которую еще нужно найти и как-то загрузить), потому что их специалисты этим не занимаются даже за отдельную плату. Руководство площадки не хочет нести ответственность за сохранность оборудования и боится обвинений в соучастии, если оно было получено незаконным путем. Еще и поэтому в Западной Европе предпочитают брендовое «железо» и сервисные контракты с вендором на его установку. 

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

Как правильно?

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

11. Прочитав и подписав договор, не бойтесь сюрпризов

Ожидания

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

Реальность

В основном это так, но есть нюансы. Установка какой-нибудь премиум-кроссировки может стоить 500 евро, а вы об этом даже не задумывались. Обычно информация есть в прайс-листах, но изучить их во всех деталях нереально. Или площадка может отказаться что-то делать, потому что она так не работает: этим обычно грешат крупные компании. В России тоже такое случается, но у мелких заказчиков есть какие-то рычаги воздействия на поставщика услуг. В Западной Европе все иначе: если не устраивает, то делайте сами, или вот цена за эту услугу. Не нравится? Не берите.

Кейс HOSTKEY

Мы однажды так потеряли 30 000 евро: в договоре было написано, что перерасход трафика свыше квоты стоит 5 евро за 1 ГБ (а не за 1 ТБ, как мы ожидали). Не обратили внимания, а потом стало поздно — так часто бывает с сетевым трафиком в западноевропейских дата-центрах, особенно на длительных контрактах. Есть проблемы и с арендой стоек: даже профессионалу может быть не до конца понятно, сколько именно он заплатит за стойку с 5 кВт электричества — электропитание могут посчитать отдельно. 

Как правильно?

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

12. Не связывайтесь с мелкими хостерами

Ожидания

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

Реальность  

Мелким хостер-посредник может показаться только в сравнении с лидерами рынка, а по масштабам его целевой аудитории это крупный бизнес. Такие компании имеют собственный обученный персонал, арендуют большие площади на длительный срок и получают недоступные розничным клиентам скидки (мы уже писали, что минимальный объем для больших ЦОДов — это 30–50 стоек). К тому же небольшие хостеры создают добавочную ценность для клиентов за счет знания местных условий и услуг, которые не оказывают крупные западноевропейские дата-центры. 

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

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

Как правильно?

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

А если без шуток, то надеемся, что наши советы помогут вам быстро и без потерь разобраться в тонкостях услуг Colocation в Западной Европе.


ссылка на оригинал статьи https://habr.com/ru/company/hostkey/blog/653875/

Поговорим о фичах в предварительной версии C# 11

К старту курса по разработке на C# рассказываем о новых конструкциях в предварительной версии языка C# 11 Preview 7. Среди них шаблоны списка, проверка Parameter на null и возможность переноса строки при интерполяции строк. За подробностями приглашаем под кат.


Visual Studio 17.1 (Visual Studio 2022 Update 1) и .NET SDK 6.0.200 содержат предварительный функционал C# 11. Чтобы поработать с ним, обновите Visual Studio или загрузите последний SDK .NET. Также посмотрите пост о выходе Visual Studio 2022 17.1, чтобы узнать о нововведениях в Visual Studio, а также пост об анонсе .NET 7 Preview 7.

Проектирование C# 11

Мы любим проектировать и разрабатывать открыто. Предложения по функциональности C# и заметки о проектировании языка вы найдёте в репозитории CSharpLang. Главная страница объясняет процесс проектирования, а ещё можно послушать Мэдса Торгерсена на .NET Community Runtime and Languages Standup, где мы говорим о процессе проектирования.

Как только работа над функцией спланирована, она вместе с отслеживанием прогресса переносится в репозиторий Roslyn. Статус ближайших функций вы найдёте на странице Feature Status. Там вы увидите, над чем мы работаем и что добавляем во все предварительные версии, а ещё можно посмотреть, что вы могли пропустить.

Из этого поста я убрала сложные технические детали и технические дискуссии о значении каждой новой конструкции языка в вашем коде. Надеемся, что вы попробуете эти предварительные функции и дадите нам знать, что думаете о них. Чтобы попробовать новые конструкции C# 11, создайте проект и установите значение тега LangVersion в Preview.

Файл .csproj будет выглядеть так:

<Project Sdk="Microsoft.NET.Sdk">     <PropertyGroup>         <OutputType>Exe</OutputType>         <TargetFramework>net6.0</TargetFramework>         <ImplicitUsings>enable</ImplicitUsings>         <Nullable>enable</Nullable>         <LangVersion>preview</LangVersion>     </PropertyGroup> </Project>

Символ новой строки в элементах формата интерполированных строк

Прочитать об этом изменении больше можно в предложении Remove restriction that interpolations within a non-verbatim interpolated string cannot contain new-lines. #4935

C# поддерживает два стиля интерполированных строк: буквальный и небуквальный, $@»» и $»» соответственно. Ключевое различие между ними заключается в том, что небуквальные интерполированные строки не могут содержать символ новой строки в сегментах текста, вместо этого должен использоваться символ экранирования (например, \r\n).

Буквальные интерполированные строки в своих сегментах могут содержать символы новых строк без экранирования. Символы "" экранируют кавычки. Всё это поведение остаётся неизменным.

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

Элементы формата сами по себе — не текст, они не должны следовать правилам экранирования и переноса строк для интерполированных сегментов строчного текста. Например, код ниже может привести к ошибкам в C# 10, но он корректен в предварительной версии C# 11:

var v = $"Count ist: { this.Is.Really.Something()                             .That.I.Should(                                 be + able)[                                     to.Wrap()] }.";

Шаблоны списка

Больше об этом изменении читайте в предложении о списке шаблонов.

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

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

В коде ниже шаблон [1, 2, .., 10] сопоставляет всё:

int[] arr1 = { 1, 2, 10 }; int[] arr1 = { 1, 2, 5, 10 }; int[] arr1 = { 1, 2, 5, 6, 7, 8, 9, 10 };

Чтобы разобраться с шаблоном списка, посмотрите этот код:

public static int CheckSwitch(int[] values)     => values switch     {         [1, 2, .., 10] => 1,         [1, 2] => 2,         [1, _] => 3,         [1, ..] => 4,         [..] => 50     };

Вот результат передачи нескольких массивов этому коду:

WriteLine(CheckSwitch(new[] { 1, 2, 10 }));          // prints 1 WriteLine(CheckSwitch(new[] { 1, 2, 7, 3, 3, 10 })); // prints 1 WriteLine(CheckSwitch(new[] { 1, 2 }));              // prints 2 WriteLine(CheckSwitch(new[] { 1, 3 }));              // prints 3 WriteLine(CheckSwitch(new[] { 1, 3, 5 }));           // prints 4 WriteLine(CheckSwitch(new[] { 2, 5, 6, 7 }));        // prints 50

Также можно захватить результаты шаблона слайса:

public static string CaptureSlice(int[] values)     => values switch     {         [1, .. var middle, _] => $"Middle {String.Join(", ", middle)}",         [.. var all] => $"All {String.Join(", ", all)}"     };
  • Шаблоны списка работают с любым счётным и индексируемым типом. Это означает, что шаблонам доступны свойства Length или Count, а у индексатора — параметр int или System.Index. 

  • Шаблоны слайса работают с любым нарезаемым типом. Это означает, что шаблону доступен индексатор, аргументом принимающий Range или метод Slice с двумя параметрами int.

Мы рассматриваем добавление поддержки шаблона списков типов IEnumerable. Если у вас есть возможность поэкспериментировать с этой фичей, дайте нам знать, что вы думаете о ней.

Проверка Parameter на null

Узнать об этом изменении больше можно в предложении Parameter null checking. Мы поместили эту фичу в предварительную версию C# 11, чтобы у нас было время получить отзывы. По поводу крайне лаконичного и более подробного синтаксиса хочется получить отзывы клиентов и пользователей, которые успели поэкспериментировать с этой функцией.

Довольно часто для проверки аргумента на null используются такие вариации бойлерплейта:

public static void M(string s) {     if (s is null)     {         throw new ArgumentNullException(nameof(s));     }     // Body of the method }

С помощью проверки Parameter на null можно сократить код, добавив к имени параметра !!:

public static void M(string s!!) {     // Body of the method }

Здесь генерируется код проверки на null. Выполняться он будет перед выполнением любого кода внутри метода. В конструкторах проверка на null происходит перед инициализацией поля, вызовом базовых конструкторов и вызовом конструкторов через this.

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

Сама проверка — эквивалент кода if (param is null) throw new ArgumentNullException(...). Когда !! содержат несколько параметров, операторы !! выполняются в порядке объявления этих параметров.

Вот несколько рекомендаций, ограничивающих применение !!:

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

 Другие случаи, где проверку использовать нельзя, включают:

  • параметры метода с extern;

  • параметры делегата;

  • параметры интерфейсного метода, когда нет интерфейсного метода по умолчанию;

Проверку можно применить только к параметрам, которые возможно проверить.

Пример ситуаций, которые исключаются на основании второго правила, — переменные _ и параметры out. Проверка на null возможна для параметров ref и in. Она разрешена для параметров индексатора и добавляет в акцессоры get и set:

public string this[string key!!] { get { ... } set { ... } }

Применима проверка и к параметрам лямбда-выражений, со скобками или без — не важно:

// An identity lambda which throws on a null input Func<string, string> s = x!! => x;

Асинхронные методы также могут иметь проверяемые на null параметры. Параметр проверяется при вызове метода.

Новый синтаксис проверки на null допустим в методах итератора. Параметр проверяется при вызове метода итератора, но не при проходе нижележащего перечислителя. Это верно для традиционных асинхронных итераторов:

class Iterators {     IEnumerable<char> GetCharacters(string s!!) {         foreach (var c in s) {             yield return c;         }     }      void Use() {         // The invocation of GetCharacters will throw         IEnumerable<char> e = GetCharacters(null);     } }

Итераторы с обнуляемыми ссылочными типами

Любой имеющий в начале имени !! параметр вначале будет иметь отличное от null значение. Это верно, даже если тип параметра сам по себе потенциально null. Такое может случиться с явно обнуляемым типом, скажем, string, или с параметром типа без ограничений. Если синтаксис !! в параметре комбинируется с явно обнуляемым типом этого параметра, компилятор выдаст предупреждение:

void WarnCase<T>(     string? name!!,     // CS8995   Nullable type 'string?' is null-checked and will throw if null.      T value1!!        // Okay )

Конструкторы

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

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

Заметки о дизайне

Может быть, вы слышали Джареда Парсонса на .NET Languages and Runtime Community Standup 9 февраля 2022 года. Ролик начинается примерно на 45-й минуте стрима, когда Джаред присоединяется к нам, чтобы рассказать подробности о решениях по функциональности предварительной версии C# 11 и ответить на некоторые распространённые отзывы.

Кто-то узнал об этой особенности, увидев PR, использующие эту функцию в .NET Runtime. Другие команды Microsoft оставляют важные отзывы, используя собственные разработки. Удивительно, что при помощи нового синтаксиса проверки на null мы смогли удалить из .NET Runtime около 20 000 строк.

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

Мы рассмотрели и отклонили глобальную установку проверки на null для всех обнуляемых параметров. Проверка параметров на null заставляет выбирать, как будет обрабатываться null.

Есть много методов, где аргумент null — допустимое значение, а значит, выполнять такую проверку везде, где тип не null, чрезмерно и это скажется на производительности. Ограничить только уязвимые null методы (например, public interface) крайне сложно.

По работе с .NET Runtime мы знаем, что во многих местах проверка не происходит, а значит, потребуется механизм отказа от использования каждого параметра. Пока мы не думаем, что глобальная проверка на null во время выполнения может быть уместной, и если когда-нибудь рассмотрим такую проверку, то это будет другая функция языка.

Резюме

Visual Studio 17.1 и .NET SDK 6.0.200 предлагают взглянуть на ранний C# 11. Поэкспериментировать можно с проверкой параметров на null, шаблонами списка и символами новых строк в фигурных скобках интерполированных строк. Мы будем рады услышать ваше мнение здесь [в комментариях к оригинальной статье] или в дискуссии репозитория CSharpLang на Github.

Погрузиться в IT впервые или прокачать ваши навыки вы сможете на наших курсах:

Выбрать другую востребованную профессию:

Краткий каталог курсов и профессий


ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/653835/