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

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

Send message

Ох, как злободневно — вот недавно поработал с проектом на Mobx, но пока разберешься как это все сделано… 20 быстрых переходов по пропсам компонента — и то в абстрактную фабрику, то в ООП-шный класс, унаследованный от нескольких других классов, то в "нормализованный" entity-стор, то в многотомные типы, то в обертку для сериализации-десериализации, то в файлы-помойки на тысячи строк, содержащие никак не связанные данные… Я бы мог понять еще 2-3 перехода до финального хранилища и типа, но когда вот так вот и с большими объемами кода — это как-то за гранью адекватного. Для меня это дичь, для них — синьорский код с десятком паттернов. А функционал-то обычный, получить данные и отформатированно отобразить в интерфейсе. Не в клозетах разруха, как говорится

Думаю, не совсем так...


  1. Компонент в тыщу строк — прост в поддержке?
    Я не раз видел такие компоненты, как правило в них очень много бизнес-логики, которая должна была бы быть в других слоях + очень много разметки и стилей, которые во многом дублируются по проекту. При разбиении на удобные блоки дубляжа кода будет меньше, изучение этих более мелких компонентов будет проще, правка багов в них приведет к их исправлению во всех местах, где используются эти компоненты. Налицо снижение сложности, уменьшение времени на правки, уменьшение количества кода, улучшение производительности (при SCU у чайлдов)


  2. Редакс в Реакте — проблема, а если еще и подключен к мелким компонентам — тем более?
    Безусловно, спорить не с чем.


  3. Атомизированность приводит к props drilling, снижая возможности переиспользования и уменьшая контроль?
    Зависит от того, какая именно атомизированность. Я имел в виду не обертки на каждый div или список, которые действительно сложно переиспользовать ввиду их потенциально громадного количества — для этого уже существуют html-компоненты, которым в props (атрибуты) передаем нужные параметры. А подразумевал я под "сейчас стремятся к большей атомизированности" то, что разбивают код на крупные семантические блоки — Header, Footer, Menu, Table, UsersList. Тысячестрочный компонент, как правило, легко разбить на подобные части, и возможность переиспользования и контроля увеличится.


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

В кучу можно при любом подходе смешать, я говорил про синтаксис. Реактовый {isShown && <Component />} в моем понимании выигрывает у вьюшного v-if="isShown" по критериям: в первом случае синтаксис джаваскриптовый и может быть вынесен за пределы рендера, во втором — строковый, который работает схоже с eval, и должен присутствовать в тегах элемента; в первом кастомных тегов нет и изучать нечего, во втором — нужно изучать документацию и специфику работы.


Таких семантических историй накапливается довольно много, а для динамики HTML вполне достаточно обойтись внедрением в него JS-синтаксиса и компонентов, все остальное — излишества. Ну и известные фреймворки не ограничиваются кастомными тегами, а еще вводят переменные с $, @, # и т.п., которые работают с особой логикой. Ну и TS с этим слабо работает, так как строка в дата-атрибуте — есть строка, а в JSX это ссылка на переменную, соответственно рефакторинг проще. Я не настолько глубоко изучал Vue, но на базовых примерах сталкивался с перечисленными проблемами в системе шаблонизации.


Дробление на компоненты в Реакте мучительно, а во Vue просто? Во Vue можно писать длинные неразбитые по компонентам шаблоны? Видел я такое в проектах (Angular), шаблоны по 500-1000 строк, но думал подобное уходит в прошлое и сейчас стремятся к большей атомизированности с целью переиспользования и упрощения контроля. Да, Реакт плохо оптимизирован для рендеринга таких вот больших компонентов, и придуман ряд подходов для блокирования перерендера дочерних компонентов: PureComponent (подходит для случаев, когда пропы равны по ссылкам), shouldComponentUpdate (для сложных сценариев), Mobx.observer (для глубокого сравнения пропсов, не считая функций, с функционалом трекинга и перерендера), Redux.connect (тоже имеет встроенный SCU). Соглашусь, что можно было бы сделать коробочный вариант для deep compare props, но большой проблемы в этом не вижу.

Охох, спасибо за ссылку на код, всколыхнуло моего перфекциониста) Попробовал разобраться в


if (this.rules.filter(rule => rule.symbols[0] === this.rules[ind].new_symbols[0]).length +
            this.rules.filter(rule => rule.new_symbols[0] === this.rules[ind].new_symbols[0]).length +
            this.machine[this.history_pos].tapes[0].tape.filter(frame => frame === this.rules[ind].symbols[0]).length > 1) {
            if (this.symbols.filter(st => st.name === val).length === 0) {
                this.symbols.push({name: val, description: ''});
                this.rules[ind].new_symbols[0] = this.symbols.length - 1;
            } else {
                this.rules[ind].new_symbols[0] = this.symbols.findIndex(st => st.name === val);
            }
        }

Получилось


const getRulesBySymbol = (symbol) => this.rules.filter(({symbols}) => symbols[0] === symbol;
const getRulesByNewSymbol = (symbol) => this.rules.filter(({new_symbols}) => new_symbols[0] === symbol);
const getTapeBySymbol = (symbol) => {
    const currentTape = this.machine[this.history_pos].tapes[0].tape;

    return currentTape.filter(frame => frame === symbol);
};

const targetRule = this.rules[ind];
const targetSymbol = targetRule.symbols[0];
const targetNewSymbol = targetRule.new_symbols[0];

const tapeBySymbol = getTapeBySymbol(targetSymbol);
const rulesBySymbol = getRulesBySymbol(targetNewSymbol);
const rulesByNewSymbol = getRulesByNewSymbol(targetNewSymbol);

const symbolIndex = this.symbols.findIndex(st => st.name === val);
const hasSomeRecords = [rulesBySymbol, rulesByNewSymbol, tapeBySymbol].some(arr => Boolean(arr.length))

if (hasSomeRecords) {
    if (symbolIndex === -1) this.symbols.push({
      name: val, 
      description: ''
    });

    targetRule.new_symbols[0] = symbolIndex === -1 ? this.symbols.length - 1 : symbolIndex;
}

Соответственно, почти все легко выносится в mobx-computed-геттеры и computedFn функции, в итоге может получиться достаточно лаконичный и читаемый код. А в изначальном варианте, конечно, сложновато было словить смысл… Я в целом к тому, если еще грамотней использовать mobx то получится вообще красиво.

Да, уже не раз обсуждали в комментах — на хуках в целом писать сложнее, они подразумевают множество условностей, легче пишется непроизводительный код, разрастаются render-функции, взаимосвязи между хуками превращаются в клубок. В концепции разрабов Реакта как бы все логично по поводу зачем хуки, как и логично, зачем FLUX и другой react-way, но в реальности это оказывается очень неудобно использовать.


Я вот после трех крупных проектов на хуках (где в каком-нибудь кастомном wysiwyg было 30-40 кастомных хуков в рендере и они использовали результат работы друг друга) вернулся к классам и забыл про все эти беды. Жизненный цикл — человекопонятный, стрелочные методы — равны по ссылкам при ререндерах, render-функция — чистая, все организовано в семантичные геттеры и методы, бонус — сокращение времени на создание командных гайдов, ревью, обсуждения.


Касательно "недостатков", описанных в статье — this совсем не сложный концепт для понимания, а базовый; увеличенный размер классовых компонентов — ну в сжатом виде на 100 компонентов добавится 1кб, даже упоминания не стоит; горячая перезагрузка — проблема любых сложных приложений, для надежности лучше пользоваться полной перезагрузкой; замена условностями (типа возврата анонимной функции в useEffect) человекопонятного жизненного цикла и явного порядка выполнения операций — скорее негативная оптимизация, из разряда сокращения названий переменных до одного символа. Заведение 4 слушателей в componentDidMount и их последовательная очистка в componentWillUnmount или они же, сгруппированные по 4 хукам — скорее дело вкуса, чем явного превосходства одного из вариантов.


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

Тяжел этап вступления в начинающие миддлы — но как себя вспомню, и похуже писал… createModal(await router.delete(todo)) — "дорогой роутер, удали объект который я в тебя передал, и верни мне такой ответ, который сразу отображу в модалке". Полная бессмыслица.


А в самописном "реакте", кажется, лишние кавычки...


export const List = (todos) => /*html*/ `
  <ul id="list">
    ${todos.reduce(
      (html, todo) =>
        (html += `
            ${Item(todo)}
        `),
      ''
    )}
  </ul>
`

// Походу подразумевался один из этих вариантов

export const List = (todos) => /*html*/ `
  <ul id="list">${todos.reduce((html, todo) => (html += Item(todo)), '')}</ul>
`
export const List = (todos) => /*html*/ `
  <ul id="list">${todos.map(Item).join('')}</ul>
`

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

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


В вебе существует большая проблема — статичность интерфейсного слоя и сложность взаимодействия с ним, JSX предоставляет возможность писать околостандартный HTML в JS с возможностью выполнять логику в жизненном цикле, выводить переменные, разбивать на компоненты, делать изоморфный рендеринг. По сравнению с другими dom-sync-подходами здесь намного меньше кастомщины и хаков (v-if="some logic in strings?!", @click="stringRefToFunction") и отсутствие "детских болезней", т.к. сообществом выработаны эффективные практики.


Все остальное — FLUX, props drilling, отсутствие эффективного стейт-менеджмента и встроенных библиотек для DI, роутинга и т.п. — это другие области, в которых использовать голый React неэффективно. И это не недостаток, а свобода в выборе решений. Да, мы часто пишем в комменты про MobX, т.к. он позволяет максимально минимизировать boilerplate, сделать явный направленный поток данных view-action-store, просто сочетается с разбиением по чанкам и SSR, предоставляет autorun-подписки для сайд-эффектов. В сочетании с простой и эффективной реактовой схемой описания компонентов эти инструменты покрывают большинство сценариев, которые могут потребоваться во front-разработке. В общем, претензии могут быть только при использовании Реакта не по назначению (а сюда я включаю использование подходов, придуманных его разработчиками, которые напрасно пытались расширить сферу его применения, вместо специализирования на dom-sync-функционале). Отсюда в обиход вошел термин react-way, который означает "так как бы правильно by design, но совершенно неудобно" и многие упираются в легко предсказуемые проблемы, который этот react-way привносит.


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

184 итерирования слов "итератор | iterator" в статье, фактически целая страница — будто рекламная операция по итер… интеграции некоего бренда в головы потребителей. А так полезно, жду генерации перевода про генераторы

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

У меня несжатый бандл вырос с 2.63MB до 2.67MB, сжатый с 641.24KB до 642.69 KB соответственно. У gzip версий увеличение всего на 0.27KB получилось. В целом — не большая цена за удаление ненужных импортов

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


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

Тоже 4 раза уже прошел все круги ада в собеседованиях, чтобы увидеть внутреннюю кухню и код — и работать с этим всем никакого желания не возникло. Да и постоянные попытки принизить и срезать зарплату в полтора раза от того, что в приличных стартапах… Спасибо за статью, теперь шире понял, для чего это делается — игры на желании быть лучшим и hr-технологии. Как вижу, Яндекс остался только мечтой программистов из регионов с небольшим опытом, как средство поработать в известном монополистическом бренде и построить карьеру, только вот эта строчка в резюме давно перестала быть значимой при устройстве в другие компании

Тоже так думал на каком-то этапе, но каждый разработчик должен создать тысячу разного рода решений, чтобы понять "как лучше", а если выкладывать каждый из экспериментов, то Хабр действительно превратится в треш, и найти действительно что-то полезное станет еще сложней (так, плодятся как грибы первые эксперименты по созданию todo list, модалок, базовой настройки Webpack). Второй аргумент против — многие разработчики хотят попробовать на практике то, что читают, причем в коммерческих проектах, что ведет к распространению неэффективных практик по сотням репозиториев. Поэтому все же лучше самостоятельно проработать множество вариантов и написать статью по их сравнению, выбрав лучшее и получив фидбек по возможным улучшениям, чем выложить один из промежуточных.

Кажется, некоторые молодые домашние эксперименты должны оставаться в домашних репозиториях

Вы говорите про реализацию, когда fetchData вызывается в конструкторе стора, но никто не мешает вызывать отдельно:


 const [state] = useState(() => new ItemsState());

useEffect(() => { state.fetchData(props.userId) }, [props.userId])

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


const userData = state.getUserById(props.userId)

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

Хотел бы еще добавить по моменту сложности. В mobx версии все топорно и просто, есть свойства класса — есть функция фетчинга, которая записывает результат в эти свойства. В эффекторе добавляются вызовы функций createEffect, createStore, 2 стрим-пайплайна, завязанные на специфические свойства (.doneData, .fail), используется 3 импорта (getSomeItemsFx, $someItem, $error) вместо одного, и для них добавляются специфические константы (вместо обычных названий у свойств в классе), а также 2 дополнительных константы в самом компоненте вместе с двумя дополнительными хуками. Вместо стандартных джаваскриптовых try-catch-finally используются методы эффектора, которые требуется изучить. Структура стора размазывается по 3 сущностям, и требуется изучить код, чтобы понять их взаимосвязь, в то время как в mobx версии свойства класса видно с первого взгляда, и легко найти, где они изменяются, с помощью IDE (findUsages). Функция getSomeItemsFx не содержит полную логику взаимодействия, соответственно при рефакторинге придется искать, в каких стримах используются специальные свойства ее прототипа, а не просто места вызова.


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


Ну и не могу не добавить, что в реальном проекте, судя по статье, это превратится в кромешный ад — combine, use, attach, restore, forward… Все эти стримы работают параллельно, и если страничка сложная и их сотня, даже самый искушенный разраб не сможет составить цельную картину… Зачем это все

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


Недостатки mobx высосаны из пальца: первый (ручные @observable, @action) автоматизируется makeAutoObservable; второй ("нужно просто положить данные из ответа на запрос в хранилище, без взаимодействия с компонентом") — следствие отсутствия архитектуры, запросы нужно посылать из api-слоя, а результаты обрабатывать в экшенах (записывать данные в стор), реактовые компоненты здесь могут быть только триггером старта загрузки данных, обрабатывать в них ответ и не нужно; третий — что для отслеживания состояния запроса нужно вручную записывать в локальные стейты параметр isLoading, тоже следствие отсутствия архитектуры — экшены это обычные функции, и можно добавить в них как в подтип объекта observable-свойство, к примеру data или state, в которое записывать все, что необходимо — количество вызовов, статус обработки, стек ошибок, стратегию перезапросов и т.п., универсально для всех или уникально для каждого. И никакого оверхеда по TS, и работает отлично с SSR (в котором можно просто пробежаться по actions и отдать страницу, когда все экшены перейдут в состояние finished)


componentWillMount() { this.context.actions.fetchUsers() }

const { actions, state } = this.context;

if (actions.fetchUsers.state.isLoading) return <Loader />

return <>{state.users}</>

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


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

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

Да, сейчас я тоже бы использовал что-то подобное. Довольно быстро работает связка
babel-plugin-inferno (преобразует jsx в vdom) + inferno-server, который все это в строку рендерит. Тоже ради интереса объединял это дело в простую оптимизированную функцию, чтобы и все фичи jsx, и быстрая скорость работы (хуков в inferno и так нет, так что выпиливать пришлось не много). Если еще отвязать от реактовой специфики, то получится как раз ваш вариант — согласен, что бенефитов от такого подхода масса. Там же и стримовая реализация есть

Information

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