TL;DR: В этой статье я хочу показать, почему распространённая фраза "не занимайтесь преждевременной оптимизацией" почти всегда используется неправильно, особенно в современных фронтенд-проектах. Я посмотрю на исторический контекст, разберу, что именно Кнут называл оптимизацией, и почему многие вещи, которые считаются "преждевременной оптимизацией", на деле — нормальная инженерная дисциплина.

Я фронтендер, пишу на React, регулярно делаю код-ревью в проекте, над которым работаю совместно с другими фронтендерами, и иногда заглядываю в другие проекты и в open source репозитории на GitHub. Нередко в пулл-реквестах я замечаю вещи, которые априори работают плохо — лишние ререндеры компонентов на React, неудачные алгоритмы в бизнес-логике, лишние преобразования и перекладывания данных из-за непродуманной структуры. И регулярно в ответ на замечание и варианты улучшения кода вижу одно и то же возражение "Кнут сказал — не занимайтесь преждевременной оптимизацией". Эту фразу часто повторяют, не задумываясь о том, как и когда она возникла, какой смысл вкладывал в неё автор, и насколько она уместна в данном конкретном случае.

Меня всегда это раздражало, и вот я собрался и написал статью в ответ на такие "отписки". Статья состоит из пяти частей:

  1. О статье, в которой Кнут написал своё знаменитое выражение.

  2. Что именно Кнут называет оптимизацией.

  3. Сравнение программ того времени и программ, которые пишут фронтендеры сейчас.

  4. Примеры "преждевременных оптимизаций", которые не являются ни преждевременными, ни оптимизациями.

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

1. О статье, в которой Кнут напи��ал своё знаменитое выражение

Выражение "не занимайтесь преждевременной оптимизацией" впервые появилось в статье Дональда Кнута "Structured Programming with go to Statements" в журнале Computing Surveys в декабре 1974 года. В статье Кнут рассматривает использование оператора goto и показывает, как от него можно отказаться без ущерба для скорости и читабельности в типичных случах передачи управления, таких как выход из цикла, сложные ветвления с пересекающимися вычислениями, рекурсивные вычисления. В современных языках программирования эти идеи развились в break и continue в циклах, break и проваливание в следующую ветку в switch case, хвостовую рекурсию и т.п. — в начале семидесятых годов почти ничего этого не было, эти концепции только придумывались и обсуждались.

Чтобы рассматривать разные варианты кода с goto и без него, в качестве подопытной задачи Кнут берёт поиск значения x в массиве A длиной m. Если значение отсутствует — его надо добавить в A. Дополнительно в массиве B для каждого индекса из A надо подсчитывать количество выполненных поисков.

Решение с goto выглядит так (я немного модернизировал синтаксис, чтобы код был понятнее современному читателю, работающему с C-подобными языками):

// Example 1
for (i = 1; i <= m; i++) { if (A[i] == x) goto found; }
notfound:
i = m + 1; m = i;
A[i] = x; B[i] = 0;
found:
B[i] = B[i] + 1;

Далее Кнут показывает, что вполне можно обойтись без goto:

// Example 1a
i = 1;
while (i <= m && A[i] != x) { i = i + 1; }
if (i > m) { m = i; A[i] = x; B[i] = 0; }
B[i] = B[i] + 1;

Но! По мнению Кнута, есть целых два "но":

  1. Здесь на одно сравнение i с m больше.

  2. Этот вариант менее читабелен.

Обратите внимание, здесь и дальше в статье Кнут скрупулёзно подсчитывает стоимость выполнения, количество сравнений, чтений и записей в память — ему недостаточно простой O-нотации.

Дальше Кнут пишет, что если немного подумать, то находится вот такой вариант решения:

// Example 2
A[m + 1] = x; i = 1;
while (A[i] != x) { i = i + 1; }
if (i > m) { m = i; B[i] = 1; }
else { B[i] = B[i] + 1; }

Этот вариант для Кнута лучше, чем предыдущие, потому что цикл быстрее. Example 1 и Example 1a на каждой итерации выполняют два сравнения — i с m и A[i] с x. Example 2 выполняет только одно сравнение A[i] != x (использованный для этого приём — маркерный элемент, он же sentinel — описан в главах 9.2 и 13.2 книги Джона Бентли "Жемчужины программирования").

Цитирую:

Example 2 beats Example 1 because it makes the inner loop considerably faster. If we assume that the programs have been handcoded in assembly language, so that the values of i, m, and x are kept in registers, and if we let n be the final value of i at the end of the program, Example 1 will make 6n + 10 (+3 if not found) references to memory for data and instructions on a typical computer, while the second program will make only 4n + 14 (+6 if not found). ... we save about 33% of the run-time.

То есть второй вариант (Example 2) быстрее первого на 33% в случае ручного ассемблирования, и на 21% в случае использования компилятора (цитату не привожу, чтобы не раздувать текст статьи).

Разобрав эти варианты, Кнут приходит к выводу, что вполне возможно отказаться от использования goto без потерь в читабельности и эффективности. И дальше он полностью переключается на тему эфф��ктивности и оптимизации кода. Следующая глава в статье так и называется — "Efficiency".

2. Что именно Кнут называет оптимизацией

В главе "Efficiency" Кнут показывает, как можно оптимизировать код уже найденного удачного варианта решения — Example 2, сократить стоимость с 4n + 14 до 3.5n + 14.5 и получить ещё 12% прироста производительности:

// Example 2a
       A[m + 1] = x; i = 1; goto test;
loop:  i = i + 2;
test:  if (A[i] == x) goto found;
       if (A[i + 1] != x) goto loop;
       i = i + 1;
found: if (i > m) { m = i; B[i] = 1; }
       else { B[i] = B[i] + 1; }

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

После этого он замечает, что эти 12% прироста производительности не стоят того, ч��о оптимизированный таким образом код теперь сложно отлаживать и сопровождать.

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

  • Изменение направления цикла чтения массива с for (i = 1; i <= m; i++) на обратное for (i = m; i > 0; i--), так как сравнение с константой 0 дешевле, чем сравнение с переменной m. Сейчас обратное направление чаще всего менее эффективно из-за предвыборки данных, которая хорошо работает при увеличении индексов в массиве и плохо работает при уменьшении индексов. И это заметно даже в JavaScript.

  • Точный подсчёт количества инструкций и стремление сократить его хоть на одну. Тогда это было возможно благодаря прозрачной модели вычислений; сегодня в высокоуровневых языках чаще всего можно только приблизительно оценить сложность кода в O-нотации.

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

  • Использование языков низкого уровня и ассемблера, ручное ассемблирование, ручное размещение переменных в регистрах. Сейчас в JavaScript и других языках высокого уровня это сложно, но аналогом можно считать вынесение тяжёлых участков в WASM, а для Node.js — использование аддонов на C++.

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

  • Компиляция без проверок на выход за границы массива. В C++ это можно сделать и сегодня (но это неточно — я не настоящий сварщик и могу ошибаться), а в Java и JavaScript — нет.

  • Использование goto, когда оно позволяет сократить количество команд, даже если переход осуществляется снаружи внутрь цикла.

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

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

Не менее интересно, что Кнут НЕ считает оптимизацией вообще и преждевременной оптимизацией в частности. На протяжении десятка страниц он придумывает семь вариантов решения одной задачи поиска элемента в массиве (Example 1, Example 1a, Example 2, Example 2a, Example 3, Example 3a, Example 3b) и отбирает самые читабельные и эффективные.

Ключевой момент: перебор алгоритмов — не оптимизация. Поиск более удобного, более читаемого или просто более корректного решения — не оптимизация. Это обычная инженерная работа, которую, по мнению Кнута, делать нужно. Для Кнута "оптимизация" начинается только там, где вы идёте против читаемости ради выигрыша в инструкциях.

3. Сравнение программ того времени и программ, которые пишут фронтендеры сейчас

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

Практически все программы, которые и о которых писал Кнут — реализация алгоритма, в котором присутствует "горячий код", он же bottle neck или узкое место (по опыту Кнута чаще всего это самый глубоко вложенный цикл в расчётах):

Experience has shown that most of the running time in non-IO-bound programs is concentrated in about 3% of the source text.

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

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

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

В реальных приложениях на React, которые мне доводилось профилировать, до 70% времени может уходить на перекладывания пропсов из одного объекта в другой и на ререндеры компонентов — зачастую бесполезные, когда после ререндера reconciliation не находит отличий и DOM в итоге не меняется.

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

Следующее отличие — проблема идентификации узких мест:

A good programmer ... will be wise to look carefully at the critical code; but only after that code has been identified.

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

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

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

these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered

И ещё в одном месте он повторяет, что оптимизация жертвует ясностью кода:

[...] when it is desirable to sacrifice clarity for efficiency, [...]

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

Если подытожить сказанное выше, то можно выделить три ключевых момента:

  1. Узкое место сейчас может появиться буквально в любом месте фронтенд-приложения.

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

  3. В абсолютном большинстве случаев неоптимальностей хороший программист может найти варианты, которые не ухудшают читаемость и сопровождаемость кода (как и делает Кнут в своей статье).

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

4. Примеры "преждевременных оптимизаций", которые не являются ни преждевременными, ни оптимизациями

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

4.1. Работа с DOM

Код в пулл-реквесте:

Array
    .from(document.querySelectorAll('li'))
    .forEach(e => {
        if (e.querySelectorAll('img').length) {
            const span = e.querySelector('span');
            // Делаем что-то с текстом
        }
    });

Здесь автор вручную с помощью if и нескольких querySelector делает то, что механизм селекторов уже давно умеет "из коробки". Пример кода можно сильно упростить (если нужны более старые браузеры, которые не понимают псевдокласс :has — всё равно можно упростить, но не так сильно):

document.querySelectorAll('li:has(img) span')
    .forEach(span => {
        // Делаем что-то с текстом
    });

У современных фронтендеров слабое владение браузерными API и "ванильным" JavaScript (то есть без использования библиотек и фреймворков) встречается всё чаще. Шаг в сторону от любимого фреймворка — и начинается неизведанная территория. Ещё один пример неоптимального использования селекторов:

document.querySelectorAll('.main p')[document.querySelectorAll('.main p').length - 1].style;

Очень обидно показать автору такого кода, как сделать его быстрее без утраты читабельности и получить в ответ "не занимайтесь преждевременной оптимизацией, как писал — так и буду писать". Изучение и использование возможностей среды выполнения (браузеры и Node.js) делает код яснее и часто быстрее — это не оптимизация, а нормальное, идиоматическое использование API.

4.2. Boolean short circuit evaluation

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

async function getData() {
    const fromStore = getDataFromReduxStore();
    const fromLS = getDataFromLocalStorage();
    const fromBack = await fetchDataFromBackend();
    return fromStore || fromLS || fromBack;
}

Надеюсь, читателям очевидно, что при наличии данных в сторе совсем не нужно читать их из local storage и тем более делать запрос в бэкенд. Используя short circuit evaluation, пример можно переписать, не теряя ясности и компактности:

async function getData() {
    return getDataFromReduxStore() ||
        getDataFromLocalStorage() ||
        (await fetchDataFromBackend());
}

Аналогичных не столь очевидных примеров я видел предостаточно в самых разных проектах. Short circuit evaluation — часть семантики логических операторов. Игнорирование этого механизма не делает код чище и читабельнее, а заставляет выполнять ненужную работу. И если систематически писать всю логику в таком "игнорирующем" стиле, то можно заметно просадить производительность приложения, не создав ни одного узкого места — тормозить будет весь код, в котором есть вычисления и логические операторы.

4.3. Повторяющиеся вычисления

Один фронтендер начал внедрять формат webp, когда ещё не все браузеры клиентов его поддерживали. Фронтендер не стал "заниматься преждевременной оптимизацией" и написал вот такой код:

function checkWebPSupport(): boolean {
    const e = document.createElement('canvas');
    if (e.getContext && e.getContext('2d')) {
        return e.toDataURL('image/webp').indexOf('data:image/webp') === 0;
    }
    return false;
}

function getImageUrl(imageName: string): string {
    return `/images/${imageName}.${checkWebPSupport() ? 'webp' : 'png'}`;
}

На локальном dev-сервере с несколькими картинками оно работало приемлемо (особенно если учесть, что у фронтенд-разработчиков обычно очень производительные компьютеры), но даже без профилирования кода можно построить несложную цепочку умозаключений:

  • в продакшене могут быть (и по закону Мёрфи обязательно будут) десятки и сотни картинок

  • десятки и сотни вызовов checkWebPSupport() будут тормозить в браузере

  • результат проверки checkWebPSupport() в одном отдельно взятом браузере не меняется

Вывод — надо определять подержку webp только один раз при открытии страницы и потом переиспользовать:

let hasWebPSupport: boolean | undefined = undefined;

function checkWebPSupport(): boolean {
    if (hasWebPSupport !== undefined) {
        return hasWebPSupport;
    }

    const e = document.createElement('canvas');
    if (e.getContext && e.getContext('2d')) {
        hasWebPSupport = e.toDataURL('image/webp').indexOf('data:image/webp') === 0;
    } else {
        hasWebPSupport = false;
    }

    return hasWebPSupport;
}

При желании можно убрать проверку переменной, используя самомодифицирующийся код:

let checkWebPSupport: () => boolean = () => {
    let hasWebPSupport = false;
    const e = document.createElement('canvas');

    if (e.getContext && e.getContext('2d')) {
        hasWebPSupport = e.toDataURL('image/webp').indexOf('data:image/webp') === 0;
    }

    checkWebPSupport = hasWebPSupport ? () => true : () => false;

    return hasWebPSupport;
};

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

4.4. Ненужная мемоизация в React

Иногда разработчиков заносит в противоположную сторону, они увлекаются мемоизацией и заворачивают в useMemo даже очевидно простые выражения:

const firstScreen = useMemo(
    () => (props.firstScreen ? 1 : 0),
    [props.firstScreen],
);
const image = useMemo(() => `/images/${imageName}.png`, [imageName]);
const number = useMemo(() => index + 1, [index]);

Этот код можно сделать и короче и быстрее:

const firstScreen = props.firstScreen ? 1 : 0;
const image = `/images/${imageName}.png`;
const number = index + 1;

Если результат выражения — примитивный тип (boolean, string, number и так далее), то useMemo надо использовать только для тяжёлых вычислений.

4.5. Accidentally quadratic

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

Фронтендеры не отстают и тоже периодически выкатывают в продакшен код, который без нужды имеет завышенную сложность. Частый виновник — оператор spread:

const tagIds: Record<string, string> = tags.reduce(
    (acc, tag) => ({ ...acc, [tag.id]: tag.name }),
    {},
);

const tagNames: string[] = tags.reduce((acc, tag) => [...acc, tag.name], []);

Простота оператора spread вырабатывает привычку не задумываясь использовать его везде, даже в обработке коллекций. Но spread каждый раз создаёт новый объект/массив, копируя все предыдущие элементы. В результате на коллекции из N элементов получаем N копирований по (N-1)/2 элементов в среднем — отсюда квадратичная сложность. Ожидаемое поведение на код-ревью: ревьюер пишет про квадратичную сложность, автор исправляет, запоминает и дальше так не делает. Реальное поведение: "не занимайтесь преждевременной оптимизацией", релиз, пользователи жалуются на тормоза приложения, кто-то другой чинит баг, автор продолжает писать в том же духе.

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

Я пытался сам написа��ь заключение, но @fillpackart уже всё прекрасно сформулировал, так что закончу цитатой из его статьи:

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

Все эти понятия — "преждевременные оптимизации", "экономия на спичках" — создают иллюзию, что оптимизации — штука неважная, и поэтому разработчики могут их не изучать. Такая иллюзия опасна, ведь изучать тонкости рантаймов, устройства языков программирования, алгоритмы и структуры данных — долго и сложно. И если ты выращиваешь поколение разработчиков, которые это не делали — ты получаешь отвратительную инженерную культуру, ужасные, супермедленные инструменты и приложения. И людей, которые в сто раз опаснее спичечников. За примерами далеко ходить не надо — достаточно просто посмотреть на современных фронтендеров. Они делают самые тормозные вещи в мире, у них в коде всего два вида коллекций — они про другие и знать не знают. Их инструменты для билдов и управления пакетами работают так медленно, что после ввода "yarn start" можно смело идти смотреть сериал. Итоговые файлы, которые получаются после компиляции, весят раз в сто больше, чем должны были. Создается эффект снежного ��ома. Как теперь ни переучивай их, какую культуру ни прививай — с большинством проблем фронтенд-инфраструктуры особо ничего и не сделаешь — если только не переписать её с нуля (да здравствуют bun, swc, esbuild и oxlint — прим. моё).

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

Комментарии (74)


  1. nin-jin
    30.11.2025 17:02

    Я фронтендер, пишу на React

    А потом ещё рассказывает нам что-то про efficiency...


    1. gassner
      30.11.2025 17:02

      Если человек пишет на Реакте, и у него получаются быстрые решения, то он как раз понимает в efficiency.


      1. nin-jin
        30.11.2025 17:02

        Если человек хоть на 3% понимает в efficiency, то использование React не пройдёт у него код-ревью как ни крути.


        1. nin-jin
          30.11.2025 17:02

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

          const FeatureList = React.memo( function FeatureList( props: {
          	features: Feature[]
          	activated: string[]
          	limit: number
          	doActivate: ( featureId: string )=> void
          	doDeactivate: ( featureId: string )=> void
          } ) {
          	
          	const onActivate = React.useCallback( function onFeatureActivate( featureId: string ) {
          		if( props.activated.includes( featureId ) ) return
          		if( props.activated.length >= props.limit ) return alert( 'Deactivate some feature first' )
          		props.doActivate( featureId )
          	}, [ props.activated, props.limit, props.doActivate ] )
          	
          	return <div>{ props.features.map( feature => (<FeatureCard
          		key={ feature.id }
          		feature={ feature }
          		active={ props.activated.includes( feature.id ) }
          		doActivate={ onActivate.bind( feature.id ) }
          		doDeactivate={ props.doDeactivate }
          	/>) ) }</div>
          	
          } )


          1. nihil-pro
            30.11.2025 17:02

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


          1. dominus_augustus
            30.11.2025 17:02

            С чего вы взяли, что если человек пишет на реакте, он обязательно пишет так, как вы привели в примере. Реакт классно сочетается с mvvm паттерном, да и с чистой архитектурой неплохо его использовать. Главное не трогать говностейт реакта, ну разве что только для хранения уж совсем примитивных значений, и будет тебе счастье. А вместе с этим не тащить в проект такую гадость как редакс, эффектор и им подобные. Берете mobx, либо cellX либо ваш мол атом, только без остального вашего зверинца, только реактивность (cellX и мол в бою не использовал, но авансом ставлю их в один ряд с mobx, руководствуясь вашими в том числе статьями о реактивности, думаю это все годные инструменты). Берем DI контейнер (typedi, inversify, либо пишем сами, так как в названных мной нет некоторых нужных фичей, либо они как-то костыльно там реализованы) чтобы иметь транспортный слой для доставки наших зависимостей, без ручного прокидывания и в отрыве от реактовского дерева, ну точнее как в отрыве, di контейнер/-ы в контексте, а уже из него тащим. И будет код выглядеть ни как это говно, что в примере у вас, а как-то так

            @Service([CommutationsProvider, DirectionsProvider, CommutationBlockAttributesCreator, ModalRegistry])
            export class CommutationsViewModel extends ViewModel {
                constructor(
                    private readonly commutationAggregateProvider: ICommutationsProvider,
                    private readonly directionsProvider: DirectionsProvider,
                    private readonly attributesCreator: CommutationBlockAttributesCreator,
                    private readonly modalRegistry: ModalRegistry
                ) {
                    super();
                    makeObservable(this);
                }
            
                @computed
                public get commutationList() {
                    return this.commutationAggregateProvider.value.list;
                }
            
                @autoAction.bound
                public addCommutationBlock() {
                    const useCase = new AddCommutationBlockUseCase(this.commutationAggregateProvider, this.attributesCreator);
                    useCase.execute();
                }
            
                @autoAction.bound
                public async deleteCommutationTable() {
                    const result = await this.modalRegistry.deleteCommutationTableModal.show();
            
                    if (result === ConfirmResult.YES) {
                        this.commutationAggregateProvider.value.clear();
                    }
                }
            
                @autoAction.bound
                public async generateCommutationTable() {
                    const result = await this.modalRegistry.generateCommutationTableModal.show();
            
                    if (result === ConfirmResult.YES) {
                        const useCase = new GenerateCommutationTableUseCase(
                            this.commutationAggregateProvider,
                            this.directionsProvider,
                            this.attributesCreator
                        );
            
                        useCase.execute();
                    }
                }
            
                @autoAction.bound
                public addCommutation() {
                    this.commutationAggregateProvider.value.addOne();
                }
            
                @autoAction.bound
                public deleteCommutation(index: number) {
                    this.commutationAggregateProvider.value.delete(index);
                }
            }

            И где-нибудь в компоненте дергаем

            export const DeleteCommutationCell = observer(function DeleteCommutationCell({
                commutation,
            }: {
                commutation: Commutation;
            }) {
                const vm = useInjected(CommutationsViewModel);
            
                return (
                    <Container>
                        <Button
                            size="s"
                            priority="quaternary"
                            content={ButtonContent.Icon}
                            icon={<CloseIcon />}
                            onClick={() => vm.deleteCommutation(commutation.index)}
                        />
                    </Container>
                );
            });


            1. nin-jin
              30.11.2025 17:02

              А ещё более сложного способа вывести одну единственную кнопочку вы придумать не смогли?


              1. dominus_augustus
                30.11.2025 17:02

                А к чему этот мув и при чем здесь кнопка? Я пример кода скинул, чтобы продемоснтрировать, что эти фразы про «все пишут плохо на реакте» - это бредятина.


                1. nin-jin
                  30.11.2025 17:02

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