All streams
Search
Write a publication
Pull to refresh
46
0
Дмитрий Казаков @DmitryKazakov8

Front-end архитектор

Send message

Есть еще https://github.com/gristlabs/ts-interface-builder, если я правильно понял задачу как генерацию валидационных функций из ts-типов

В целом да, задачи, где требуется математика, современному фронтендеру встречаются очень редко. В основном это либо рисование анимаций на канвасе (траектории), либо высчитываемые по формулам линии на графиках. А делать браузерные игры — это уже совсем другая профессия. Так что в целом достаточно и математики 7 класса + знания особенностей работы с числами в JS (округление, bigInt, погрешности при расчетах, конвертация относительных и абсолютных величин, операции с датами и временем в миллисекундах, дельты для определения направления движения мыши / прокрутки, разбиение области на взаимно влияющие зоны для увеличения / уменьшения размеров элемента).


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

createOAuthString('https://github.com', 'abc123', 'repo,user')

Так делать, конечно, не надо, если вдруг кто-то из новичков решит использовать материалы статьи в своих проектах. Пишите


createOAuthString({
  host: 'https://github.com', 
  clientId: 'abc123', 
  scope: 'repo,user'
})

Иначе непременно запутаетесь в порядке, либо это приведет к антипаттерну, как во многих опенсорс-творениях, типа createOAuthString(null, null, 'repo,user'), плюс каждому разработчику придется изучать непосредственно реализацию функции (и не раз в зависимости от задач и ввиду забывчивости).

Получение текстовых строк 200 { error: "User already exists" } действительно с системой локализации не матчится и подходит только для неожиданных сбоев, поэтому во всех проектах делаем константы 200 { error: "USER_EXISTS" }, фронт уже выполнит необходимую логику и заберет из локалей перевод. С остальным согласен, HTTP коды только для транспортных ошибок удобны, а те, кто на них опирается в разработке сложных приложений, сталкиваются с их многозначностью, которую приходится специфицировать либо в body, либо в заголовках ответа (типа 402 resp.headers { validation_error: "FIELD_NOT_VALID" }).

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

Если только для фронтенда, то действительно, разделение на прод-зависимости и дев-зависимости не несет значимого смысла, т.к. в ci после сборки не нужны node_modules, и в финальную папку можно копировать только папку build.
В изоморфной же схеме либо при сборке для ноды, когда зависимости не включаются в билд, а подтягиваются в рантайме (например, с помощью опции externals в Webpack), разделение на разного рода зависимости становится актуальным. После сборки в финальном образе с файлами ci в идеале должен выполнить npm i --production, чтобы очистить node_modules от лишнего и уменьшить размер. Эту оговорочку надо бы включить в статью

Все еще вижу подобные конструкции:


this.each(function() {
  const $wrapper = $(this)
  $wrapper.slibox('slideTo', $wrapper.data('activeSlide') - 1)
})
return this

this.each не нужен, а само слово this в большинстве случаев лучше не использовать, т.к. непонятно, что в нем находится — контекст штука динамическая. В данном случае можно так


const $wrapper = this;
const prevSlideIndex = $wrapper.data('activeSlide') - 1;

$wrapper.slibox('slideTo', prevSlideIndex);

return $wrapper;

Также вижу анонимные функции, конструкции !1, перебор for вместо forEach, двойное равенство, проверку 'object' != typeof extOptions.imagesLinks вместо Array.isArray, хранение данных в $wrapper.data вместо обычного объекта. В общем, меньше половины рекомендаций реализовано, так что дальше анализировать пока не буду

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


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


Скрытый текст
!function ($) {
    /**
     * Все эти функции получаются в глобальной области видимости jQuery - это ненужно и может вступить в конфликт
     * с другими плагинами. Нужно оформить просто в функциях, кроме $.fn.slibox
     * Также лучше по-максимуму избегать анонимных функций, чтобы была возможность делать рекурсию и видеть
     * семантичный стек вызовов
     *
     */

    $.fn.slideTo = function (slideTo, fromTimer = false) {
        this.each(function () {
            let sliderId = '#' + this.id,
                slidesCount = $(sliderId).data("sb-slides-count");

            if ($(sliderId).data("sb-carousel") || fromTimer) {
                if (slideTo > slidesCount) {
                    slideTo = 1;
                } else if (slideTo < 1) {
                    slideTo = slidesCount
                }
            }

            if (fromTimer) {
                if ($(sliderId).data('sb-timer')) {
                    $(sliderId + ' .sb-timer').removeClass('sb-timer-animate');

                    setTimeout(function () {
                        $(sliderId + ' .sb-timer').addClass('sb-timer-animate');
                    }, 100);

                    clearInterval(this.slidingInterval);

                    this.slidingInterval = setInterval(function () {
                        if (!$(sliderId)[0].paused) {
                            // console.log($(sliderId).data('sb-timer-time') / 100, $(sliderId)[0].time);
                            if ($(sliderId).data('sb-timer-time') / 100 != $(sliderId)[0].time - 1) {
                                $(sliderId)[0].time++;
                            } else {
                                if (($(sliderId).data('sb-timer-carousel') || $(sliderId).data('sb-active-slide') < $(sliderId).data('sb-slides-count'))) {
                                    $(sliderId).slideToNext();
                                } else {
                                    $(sliderId + ' .sb-timer').css('animation-play-state', 'paused');
                                }
                                $(sliderId)[0].time = 0;
                            }
                        }
                    }, 100);
                }
            }

            if (slideTo) {
                $(sliderId).toggleClass('sb-last-slide', slideTo == $(sliderId).data('sb-slides-count'));
                $(sliderId).data("sb-active-slide", slideTo);
                $(sliderId + " .sb-slide").removeClass("active");
                $(sliderId + " .sb-slide:nth-of-type(" + slideTo + ")").addClass("active");
                $(sliderId + " .sb-controller").removeClass("active"), $(sliderId + " .sb-controller:nth-of-type(" + slideTo + ")").addClass("active");
            }

        })
    }

    let sbCanDrag = true;

    $.fn.slideToNext = function () {
        this.each(function () {
            if (this.className.match('slibox')) {
                $(this).slideTo($(this).data('sb-active-slide') + 1, $(this).data('sb-timed-carousel'));
            }
        })
        return $(this).data('sb-active-slide');
    }

    $.fn.slideToPrev = function () {
        this.each(function () {
            if (this.className.match('slibox')) {
                $(this).slideTo($(this).data('sb-active-slide') - 1, $(this).data('sb-timed-carousel'));
            }
        })
        return $(this).data('sb-active-slide');
    }

    $.fn.setTimeTo = function (time) {
        this.each(function () {
            $(this).data('sb-timer-time', time);
            this.time = Math.ceil(this.time / time);
            $('#' + this.id + ' .sb-timer').css('animation-duration', time + 'ms');
            $('#' + this.id + ' .sb-timer').css('animation-play-state', 'running');
        });
        return $(this);
    }

    $.fn.slibox = function slibox(options) {
        /**
         * Не стоит сокращать названия переменных до несемантичных значений типа o, это ухудшает читаемость
         * Также не стоит использовать хелперы ($.assign) в качестве замены стандарных возможностей языка
         *
         */
        const extendedOptions = Object.assign({
            /**
             * Не стоит использовать !0 или !1, код должен быть явным и с понятными типами,
             * не заставляя разработчика лишний раз интерпретировать его.
             * Также это упор на особенности языка, желательно избегать подобных конструкций
             *
             */
            height: false,
            width: false,
            activeSlide: 1,
            renderArrows: true,
            renderControllers: true,
            imagesLinks: [],
            loadErrorMessage: "Image is not loaded",
            noImagesMessage: "There are no images links you added<br><small>Slibox</small>",
            imageSize: "contain",
            loaderLink: false,
            imagePosition: "center",
            animateCSS: false,
            carousel: false,
            timer: false,
            timerTime: 5000,
            timerCarousel: true,
        }, options);

        /**
         * Перебор this.each излишен и не несет никакой смысловой нагрузки.
         * Для chaining можно просто сделать return this
         *
         */

        /**
         * Название el не подходит для строки, к тому же не стоит сокращать.
         * Селектор также можно взять из уже обернутого this вместо "#" + this.id
         * 
         * Также неизменяемые примитивы нужно объявлять через const
         *
         */
        const wrapperId = this.selector;

        /**
         * ВАЖНО: оборачивать элементы каждый раз - значит обращаться к DOM, это может быть нужно
         * только тогда, когда элементы динамические. Статичные необходимо кешировать в переменных.
         * Враппер можно взять из this, а не искать через $(wrapperId).
         * 
         * Названия jQuery-переменных рекомендую начинать с $, чтобы избежать путаницы с другими типами
         * сущностей
         * 
         */

        const $wrapper = this;

        $wrapper.addClass("slibox");

        /**
         * Двойное равенство не рекомендую использовать никогда, исключение - if (variable != null),
         * так как это более лаконичная форма для if (variable !== null && variable !== undefined).
         * 
         * imagesLinks - массив, если нужно на него проверить, то "object" != typeof extendedOptions.imagesLinks
         * не подойдет.
         *
         */

        if (!Array.isArray(extendedOptions.imagesLinks) || extendedOptions.imagesLinks.length === 0) {
            /**
             * Не стоит писать конструкции вида return 1, 2, false. Инструкции должны быть описаны отдельно,
             * а возвращаемое значение - соответствовать глобально возвращаемому, то есть this в данном случае
             *
             */

            extendedOptions.imagesLinks = [];

            $wrapper.html('<h1 class="sb-error">' + extendedOptions.noImagesMessage + "</h1>")

            return this;
        }

        if (!$wrapper.children('.sb-slides')) {
            $('<div/>', {
                class: 'sb-slides'
            }).appendTo(wrapperId);
        }

        /**
         * Хранить данные в объекте элемента конечно можно, но смысла в этом нет. Лучше переделать на
         * отдельный объект. Это позволит IDE выдавать подсказки, что не получится в паттерне $(this).data("sb-slide")
         *
         */

        /**
         * ВАЖНО: все строки нужно перевести в именованные константы для избежания опечаток и дубляжей
         *
         */

        $wrapper.data("sb-slides-count", extendedOptions.imagesLinks.length);
        $wrapper.data("sb-carousel", extendedOptions.carousel);
        $wrapper.data("sb-timer-time", extendedOptions.timerTime);
        $wrapper.data("sb-timer-carousel", extendedOptions.timerCarousel);
        $wrapper.data("sb-timer", extendedOptions.timer);

        /*
         * Setting a link of the loader
         */
        if ("string" === typeof extendedOptions.loaderLink) {
            $wrapper.css({
                background: "url(" + extendedOptions.loaderLink + ") no-repeat center"
            })
        }

        /*
         * Create Arrows
         */
        if (extendedOptions.renderArrows) {
            $('<div/>', {
                class: 'sb-arrow sb-arrow-left',
                html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><g><path fill="#da5858" stroke="#da5858" d="M45.539,63.41c0.394,0.394,0.908,0.59,1.424,0.59s1.031-0.197,1.424-0.59c0.787-0.787,0.787-2.061,0-2.848 L20.059,32.233L48.407,3.886c0.786-0.787,0.786-2.062,0-2.848c-0.787-0.787-2.062-0.787-2.849,0L15.822,30.773 c-0.205,0.206-0.384,0.506-0.484,0.778c-0.273,0.738-0.092,1.567,0.465,2.124L45.539,63.41z" /></g></svg>',
                data: {
                    'sb-slider': wrapperId,
                    'sb-arrow-direction': 'left'
                }
            }).appendTo(wrapperId);
            $('<div/>', {
                class: 'sb-arrow sb-arrow-right',
                html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><g><path fill="#da5858" stroke="#da5858" d="M44.152,32.024L15.824,60.353c-0.787,0.787-0.787,2.062,0,2.849c0.394,0.394,0.909,0.59,1.424,0.59 c0.515,0,1.031-0.196,1.424-0.59l29.736-29.736c0.557-0.557,0.718-1.439,0.445-2.177c-0.101-0.272-0.26-0.519-0.464-0.725 L18.652,0.828c-0.787-0.787-2.062-0.787-2.848,0c-0.787,0.787-0.787,2.061,0,2.848L44.152,32.024z" /></g></svg>',
                data: {
                    'sb-slider': wrapperId,
                    'sb-arrow-direction': 'right'
                }
            }).appendTo(wrapperId);
        }

        /**
         * Проще переборы делать через extendedOptions.imagesLinks.forEach
         *
         */
        for (let i = 1; i <= extendedOptions.imagesLinks.length; i++) {
            let slide = $(wrapperId + ' .sb-slides .sb-slide:nth-of-type(' + i + ')');
            if (slide.length != 0) {
                slide = slide
                    .data('sb-slide', i)
                    .attr('draggable', true)
                    .css({
                        'background-image': 'url("' + extendedOptions.imagesLinks[i - 1] + '")',
                        'background-repeat': 'no-repeat'
                    })
                    .addClass('sb-slide-' + i)
                    .html('<div class="sb-slider-content">' + slide.html() + '</div>');
            } else {
                slide = $('<div/>', {
                    class: 'sb-slide sb-slide-' + i,
                    data: {
                        'sb-slide': i
                    },
                    draggable: true,
                    css: {
                        'background-image': 'url("' + extendedOptions.imagesLinks[i - 1] + '")',
                        'background-repeat': 'no-repeat'
                    }
                }).appendTo(wrapperId + ' .sb-slides').html('<div class="sb-slider-content"></div>');
            }

            /*
             * Create Controllers
             */
            if (extendedOptions.renderControllers) {
                if (0 == i - 1) {
                    $('<div/>', {
                        class: 'sb-controllers',
                        data: {'sb-slider': wrapperId}
                    }).appendTo(wrapperId);
                }
                $('<div/>', {
                    class: 'sb-controller',
                    data: {
                        'sb-slider': wrapperId,
                        'sb-controller': i
                    }
                }).appendTo(wrapperId + ' .sb-controllers');
            }

            /*
             * Animate.css functionality
             */
            if (extendedOptions.animateCSS) {
                if ("object" == typeof extendedOptions.animateCSS) {
                    if (extendedOptions.animateCSS.length < i) {
                        $(slide).addClass(extendedOptions.animateCSS[extendedOptions.animateCSS.length - 1])
                    } else {
                        $(slide).addClass(extendedOptions.animateCSS[i - 1]);
                    }
                } else if ("string" == typeof extendedOptions.animateCSS) {
                    $(slide).addClass(extendedOptions.animateCSS);
                }
                $(slide).addClass("animated");
            }

            /*
             * Image Size
             */
            if (extendedOptions.imageSize) {
                if ("object" == typeof extendedOptions.imageSize) {
                    if (extendedOptions.imageSize.length < i) {
                        $(slide).css({
                            "background-size": extendedOptions.imageSize[extendedOptions.imageSize.length - 1]
                        })
                    } else {
                        $(slide).css({
                            "background-size": extendedOptions.imageSize[i - 1]
                        })
                    }
                } else if ("string" == typeof extendedOptions.imageSize) {
                    $(slide).css({
                        "background-size": extendedOptions.imageSize
                    })
                }
            }

            /*
             * Image Position
             */
            if (extendedOptions.imagePosition) {
                if ("object" == typeof extendedOptions.imagePosition) {
                    if (extendedOptions.imagePosition.length < i) {
                        $(slide).css({
                            "background-position": extendedOptions.imagePosition[extendedOptions.imagePosition.length - 1]
                        })
                    } else {
                        $(slide).css({
                            "background-position": extendedOptions.imagePosition[i - 1]
                        })
                    }
                } else {
                    $(slide).css({
                        "background-position": extendedOptions.imagePosition
                    })
                }
            }
        } // End for

        $wrapper.width(extendedOptions.width);

        if (!extendedOptions.height) {
            $wrapper.height(0.5625 * $wrapper.width())
        } else {
            $wrapper.height(extendedOptions.height)
        }

        if ("number" == typeof extendedOptions.activeSlide) {
            if (extendedOptions.activeSlide > extendedOptions.imagesLinks.length) {
                $wrapper.data("sb-active-slide", extendedOptions.imagesLinks.length)
            } else if (extendedOptions.activeSlide < 1) {
                $wrapper.data("sb-active-slide", 1)
            }
            $wrapper.data("sb-active-slide", extendedOptions.activeSlide)
        } else {
            $wrapper.data("sb-active-slide", 1)
        }

        if (extendedOptions.timer) {
            $wrapper.data('sb-timed-carousel', extendedOptions.timerCarousel);
            $('<div/>', {
                class: 'sb-timer-container',
            }).append($('<div/>', {
                class: 'sb-timer sb-timer-animate',
                style: 'animation-duration: ' + extendedOptions.timerTime + 'ms'
            })).appendTo(wrapperId);
            this.time = 2;
        }

        $(".sb-controller").unbind('click');
        $(".sb-controller").click(function () {
            $($(this).data("sb-slider")).slideTo($(this).data("sb-controller"))
        })

        $(".sb-arrow").unbind('click');
        $(".sb-arrow").click(function () {
            let sliderId = $(this).data("sb-slider"),
                activeSlide = $wrapper.data("sb-active-slide");
            if ("left" == $(this).data("sb-arrow-direction")) {
                $(sliderId).slideTo(activeSlide - 1)
            } else if ("right" == $(this).data("sb-arrow-direction")) {
                $(sliderId).slideTo(activeSlide + 1)
            }
        })

        $wrapper.hover(function () {
            let sliderId = '#' + this.id;
            $(sliderId + ' .sb-timer').css('animation-play-state', 'paused');
            $(sliderId)[0].paused = true;
        }, function () {
            let sliderId = '#' + this.id;
            $(sliderId + ' .sb-timer').css('animation-play-state', 'running');
            $(sliderId)[0].paused = false;
        });

        $wrapper.slideTo($wrapper.data("sb-active-slide"), true);

        $(wrapperId + ' .sb-slide').each(function () {
            let box = $(this),
                container = $wrapper[0];
            this.boxOffset, this.myDragFlag = false
            box.on('selectstart', function () {
                sbCanDrag = false;
            });

            box[0].ondragstart = function (e) {
                if (sbCanDrag) {
                    this.startX = e.pageX - box[0].offsetLeft - container.offsetLeft
                    this.myDragFlag = true
                }
            }
            box[0].ondragend = function (e) {
                if (sbCanDrag) {
                    this.boxOffset = e.pageX - this.startX;
                    if (this.boxOffset - container.offsetLeft <= -20) {
                        /**
                         * Вычисляемые переменные нужно оформлять в отдельных константах для лучшей читаемости
                         *
                         */
                        $wrapper.slideTo($(this).data("sb-slide") + 1)
                    }
                    if (this.boxOffset - container.offsetLeft >= 20) {
                        $wrapper.slideTo($(this).data("sb-slide") - 1)
                        this.myDragFlag = false
                    }
                } else {
                    sbCanDrag = true;
                }
            }
        })

        return this;
    }
}(jQuery);
Да, годное решение для разработки на ноде, если не отвлекают тексты ошибок в консоли процесса. Так как использую тайпчекинг только на финальной стадии с включенным noEmitOnError, то не подумал о таком варианте.
Проверка типов, подсветка, автодополнение для TS — есть в используемом мной WebStorm без дополнительных плагинов, насколько знаю — и в других IDE. Медленнее идет разработка, потому что при пересборке при любой ошибке компиляция падает и программа не работает, а то, что типы не указаны говорит и IDE, никакого смысла параллельно проверять компилятором не вижу — когда будет написана рабочая версия кода с несколькими итерациями рефакторинга я покрываю код типами и при коммите все типы в программе проверяются.
Это я и назвал «неблокирующим режимом», который помогает, а не мешает. Использую как для разработки для ноды, так и для фронта
А где, собственно, разбор кода как написать слайдер, на который дана ссылка? Зачем читателям простейшая информация из документации?

«Slibox — it's a very fast, powerful, tiny slider. Mobile-friendly, easy to use», код — github.com/GitCodeDestroyer/Slibox/blob/master/src/slibox.js. Красивое описание и трешовая реализация — бич опенсорс разработки, которая по моему опыту на 98% состоит из подобного. А статья — бесполезный самопиар, минус.
TS, на мой взгляд, слишком медленный и неудобный для разработки. При каждом изменении проверять все типы — значит блокировать полет мысли при разработке и замедлять тестирование работы в реальном окружении. Поэтому я все же предпочитаю при кодинге применять babel с плагином, вырезающим типы, а в качестве хелпера использовать встроенные в IDE подсказки тайпскрипта. Работает намного быстрее и не блокирует.
А проверку типов во всем коде можно выполнять на precommit и в ci
Спасибо за описание некоторых проблем при скриншотном тестировании и возможных путей их решения — но для чего они, собственно? Кроссбраузерность на Cypress не проверить, и кроссплатформенность верстки тоже, хотя из-за особенностей браузеров и систем могут быть значительные различия в отображении. Визуальное сравнение может помочь только в кейсах, когда возникло неожиданное влияние одних стилей на другие (неправильно указаны слои, совпадение имен классов на глобальном уровне, изменившийся при prod-оптимизации порядок следования стилей), но по большей части подобные баги выявятся на ручном feature-тестировании, а при грамотных подходах их вообще не должно возникнуть. Бывает, конечно, что в одном месте поменяли — и в очень далеком аукнулось, что можно выявить только полноценным регрессом, но создать систему скриншотного тестирования, способную отловить подобное — настолько сложное и муторное занятие, что вряд ли оправданно.
Спасибо, годно!
Для крупных проектов с интеграциями действительно пригодится знать смещения контента и влияние продолжительности загрузки ассетов на блокировку интерактивности. Для обычных корпоративных проектов, где больших ресурсов на исследование производительности нет, я обычно опираюсь на стандартные браузерные средства без обсерверов:

const navigationTiming = performance.getEntriesByType('navigation')[0]

const loggedNavigationKeys = [
'domComplete',
'responseStart',
'domInteractive',
'domainLookupEnd',
'domContentLoadedEventEnd',
];

добавляя туда userAgent и информацию из роутера. При разработке на Реакте классовым методом — еще навешиваю декоратором измерение на componentDidMount и количество ререндеров. При необходимости можно из window.performance также вытянуть информацию по времени загрузки ресурсов, но эти метрики лучше вести отдельно по регионам, так как они относятся к качеству CDN. В целом этого достаточно для локализации всех проблем с производительностью. Но если есть ресурсы на отдельную команду по перфомансу — то можно и заморочиться с детальной разбивкой метрик, как в статье
Хранить конфиг в jsx — это как хранить его в верстке, доступ к конфигурации каждого поля сложен и медленен, и вместо единообразных конфиг-объектов приходится дублировать код от формы к форме. Если у вас будет 50 таких форм, и нужно изменить какую-то логику, придется заниматься этим в каждом конкретном компоненте, который для всего остального приложения является «черным ящиком». Это в целом очень неудобный паттерн, и то, что он используется в нескольких опенсорс-решениях для упрощения интеграции в свой проект, ничуть не свидетельствует о том, что это решение лучше. Наоборот, проблем при интеграции этих библиотек по опыту больше, чем от использования движка на конфигах.

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

Насчет кучи проверок и ифов в конструкторе форм из конфига — вы явно перегибаете, их будет совсем немного при грамотном проектировании, а в большинстве случаев — ни одной: {formConfig.map(({ FieldComponent, fieldProps }) => <FieldComponent {...fieldProps}/>)}, но при динамической раскладке сюда добавится дополнительный слой в виде группировки полей из конфига по определенному признаку и их выведению в соответствующих размеру экрана рядах, но это несколько дополнительных строк, и уж точно «дополнительных фреймворков» не нужно.

If-конструкции уходят практически полностью благодаря переносу в конфиг в виде семантических параметров — isDisabled, isShown, isOptional, и их можно менять динамически в удобной манере: formConfig.phone.isOptional = formConfig.email.isValid() || formConfig.name.isDisabled || true, и зашивать это статично в валидатор — плохая идея. При сабмите формы легко можно пробежаться по isDisabled и исключить эти поля и из отправки на бэк, и из валидаций.

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

В целом для простейших форм и если их до десятка на проекте, можно и по принципу «черного ящика» создавать компоненты и с локальным стейтом, но в перспективе все равно придется создавать унифицированное решение с более открытым интерфейсом.
Я бы не бухтел при выдаче подобных задач, только если бы очень хотел попасть именно в эту контору и условия были бы отличными. Но если меня собеседует совсем «зеленый» человек и говорит что надо использовать двойное равенство вместо тройного, потому что так короче, и дает задачу написать сортировку «пузырьком», то чешу репу и говорю «я вам перезвоню»)) Не шучу, бывало и такое, причем на собеседовании на сеньорские позиции.
Еще на алгоритмы некоторые ребята дают… Тоже за то, чтобы узнавать у собеседующихся об их опыте путем проектирования решения для конкретных рабочих задач. Механика воплощения этих проектов будет зависеть от фреймворка и архитектуры, от подходов, использующихся в проекте, и, как правило, с вдохновением из StackOverflow, а не кодингом «с нуля». Но от юниоров, закончивших какие-нибудь курсы, хотелось бы ожидать как раз умения работать с чистым языком и логикой, так как опыта в решении реальных задач у них, как правило, нет, так что подобные задачки подойдут.
По поводу хранения состояния форм (валидаторы, значения, функции для проверки каждого поля, характеристики полей) я пришел к тому, что глобальный стейт для этого выгоднее локального, но только при реактивной архитектуре. Взаимодействие с бэком вне слоя апи, а напрямую из компонентов приводит к слишком разбросанной и сложно контролируемой логике. «Просто отображать ошибку» — интересно, каким образом вы планируете это сделать, и как планируете не позволять юзеру отправлять поле с некорректными значениями. Но интересно не очень, так как проектировал системы сложных форм десятки раз, и с помощью внешнего обращения к внутреннему стейту компонентов, и параллельной глобальной системой контроля состояния полей непосредственно через DOM, и двухсторонней связкой по системе событий — у этих способов довольно много недостатков, которые можно устранить именно глобальным реактивным состоянием. Попробуйте, должно понравиться.

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

Ну да, я лишь дополнил варианты в случае, когда «необходимость нужна», противоречий нет)
По сути просто не нужно обращаться к соседним сторам в constructor, то есть сначала создается весь стор целиком, а потом вызываются методы. Так не будет никаких undefined, и setTimeout не пригодится) Так что тоже не считаю это проблемой

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity