Pull to refresh
6
0
Владислав Муращенко @Vlad_Murashchenko

Front-end разработчик

Send message

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

Также это скорее частичное применение, чем каррирование. Я бы переименовал хук в usePartial.

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

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

Во-первых, не все на свете, а только работать с формами.

Да, но я немного о другом. Есть 2 подхода к переиспользованию кода.
1) Написать один хук который принимает большой конфиг и может делать абсолютно все, что связано с формами. А потом мы используем его в каждой нашей форме, берем только половину от возможностей, какие-то вещи переопределяем или используем хаки вроде setFieldValue из Formik. В итоге складывается впечатление, что инструмент не помогает, а мешает в некоторых ситуациях.
2) Создать много маленький хуков и утилит которые хорошо сочетаются между собой и собирать разные формы используя их как строительные блоки, которые стараются помочь, но не диктуют архитектуру и не ограничивают, так как любой из блоков можно легко выкинуть и заменить кодом который делает что-то вручную. При этом, эти строительные блоки не догадываются о существовании друг друга, если какой-то из них недостаточно хорош для текущей задачи, остальные все же полезны. В худьшем случае ниодна из утилит не подойдет и мы напишем абсолютно все вручную, но они хотя бы не мешают

Комбинация функции-конструктора и прототипа - это и есть класс

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

Хуки - это функции с сайд эффектами.

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

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

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

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

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

То что вы делаете с input-ами, выражая их через массив обычных объектов и рендеря через map в своей статье я называл "формы из замоканных конфигов". И я по прежнему считаю это антипаттерном. Вот ссылка на мою статью о формах:
https://habr.com/ru/post/495518/

Еще один пример моего подхода можно посмотреть здесь:
https://codesandbox.io/s/github/VladislavMurashchenko/use-prop-change-sandbox?file=/src/containers/UserForm.tsx
К сожелению, пока нет примера валидации.

Согласен, однако если сравнивать это с полным отсутствием документации, то тесты это хотя-бы что-то.

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

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

Тесты более однозначные, допустим есть кусок кода написанный в TDD. Чтобы понять зачем в каком-то месте сделано именно так, а не вот так, можно просто поменять код и посмотреть, что отвалится. Если ничего - значит рефакторинг готов. Думаю вся идея TDD в этом.

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

Если у нас есть модуль, который состоит из нескольких других модулей и мы его тестируем. Это считается юнит тестом или интеграционным?

Автор относит этот случай к юнит тестам, но на мой взгляд, он подходит под оба определения

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

В общем, после чтения этого кода видно, что есть куча нюансов которые команда redux уже решили за нас. И я не вижу смысла повторять это все для того, чтобы перейти на useReducer + useContext. К тому же в этом подходе не закрыты еще 2 вопроса - асинхронность и интеграция с devtools. Они конечно разрешими, вот только зачем их решать самому если в итоге получится тот же redux?

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

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

Архитектура Flux (в Redux) предполагает следующее:
состояние изменяется только с помощью чистых функций — операций (actions);

actions - это не чистые функции, а обычные объекты. И функции которые создают события (action creators) так же не обязательно должны быть чистыми. Чистыми функциями обязаны быть только reducers и selectors. И в этом очень много смысла. Я могу расписывать часами то, насколько сильно чистые функции упрощают жизнь, если понимать их преимущества.

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

И делается это с помощью вызова dispatch 2500 раз. Это абсолютно не соответсвует идеям redux и здравому смыслу. Когда нужно добавить или отредактировать 2500 элементов, это должно делаться с помощью одной тракзакции. Об этом написано в strongly recommended правилах в redux style-guide:
https://redux.js.org/style-guide/style-guide#avoid-dispatching-many-actions-sequentially

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

Я почти на 100% уверен, что проблема с производетельностью является производной нарушения правила выше и особенностями работы immer который redux-toolkit использует под коробкой. Дело в том, что immer работает очень медленно, если вызывать его в цикле как это делается в примерах статьи.
Из документации immer:

for (let x of y) produce(base, d => d.push(x)) is exponentially slower than produce(base, d => { for (let x of y) d.push(x)})

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

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

useSelector из react-redux работает таким образом, что если данные на которые ссылается селектор не изменились, компонент не обновлялся и команда react-redux приложила не мало усилий, чтобы сделать поведение useSelector именно таким и исправить баги "Stale Props" и "Zombie Children".

TDD на одних юнит тестах - это, очевидно, не правильное TDD. И я считаю, что не TDD в этом виновато. Почти любую концепцию можно понять не правильно и применить там, где неуместно.

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

Понимаю Вас. Однако, если не готовы доказать это на деле, не стоит бросаться громкими фразами вроде:

Фанаты ФП - одни из самых вредных людей на проекте.

А доказать что парадигма X лучше парадигмы Y в случае Z, можно лишь переписав код с одной парадигмы на другую без изменений в поведении программы и проанализировав результат.

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

PS. да я знаю, я отношусь к этому слишком серьезно

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

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

Я согласен, Redux - это очень низкоуровневый инструмент в несколько сток кода, который реализует голую концепцию и оставляет все остальное на усмотрение тех, кто его использует. Весь его код можно изучить за 30 минут.

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

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

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

А насчет легко "достижимы и при совершенно других подходах", вот код, покажите мне как достигнуть такого уровня декларативности, с помощью других подходов.
https://codesandbox.io/s/runtime-sun-1qhtw?file=/src/pages/Game/gameSlice.js
Просто форкните и перепишите на MobX или на то, что вам нравится.

PS. если codesandbox по какой-то причине упал, потрогайте какой-то код, это баг на codesandbox. Раньше замечал за ним такое

Я бы поспорил насчет того, что ФП в redux не уместно. Я считаю, что переход из одного состояния в другое состояние является чистым вычислением по своей природе. И самым очевидным выражением перехода будет:
(state, event) => state
По этому мне очень нравятся редьюсеры.
Какими бы сложными небыли эти вычисления чистые функции легко масштабируются. Код между ними легко переиспользовать. Ненужные детали легко скрыть в абстракции и положить в другой файл.

Мне в работе встречались сложные переходы не раз (особенно когда я работал над видео редактором) и редьюсеры здесь были как нельзя кстати. Ну а пляски с иммутабельностью легко исправляются в помощью immer. Redux toolkit позволяет сгладить все моменты за которое redux критикуют. Подобные инструменты были и раньше, но качество было хуже и поддержки TS кажется нигде небыло.

Один нюанс, слепки памяти не являются полными при иммутабельности. Общие объекты между изменениями переиспользуются по ссылкам, это называется structural sharing. Так что это сводится к тому же хранению разницы. Возможно иммутабельный подход будет кушать даже меньше памяти при undo/redo, так как все эти proxy и observers на каждое поле тоже едят не мало

Я и так его никогда не упущу, в прочем как и любой опытный разработчик

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

На мой взгляд модель которая у вас получилась недотягивает до Actor model.
Вот например что XState пишет об Actor-ах:


"actor" that can do three things:
  • Receive messages
  • Send messages to other actors
  • Do something with the messages it received (its behavior), such as:
    — change its local state
    — send messages to other actors
    — spawn new actors

Википедия пишет примерно то же самое.
Давайте разберем пункты по порядку:


1) Может ли ваш Actor получать сообщения?
Нет, он может только реагировать на изменение состояния redux. Если сильно включить фантазию, то можно конечно считать это сообщением. Но строго говоря это не то.


2) Может ли ваш Actor отправлять сообщения другим Actor-ам?
Нет, он может только диспатчить в store. Это конечно в итоге может вызвать useEffect в другом Actor-е. Но если проходить к вопросу строго, то это все же не то.


3) Может ли Actor изменить свое внутреннее состояние?
Нет. Если рассматривать за внутреннее состояние состояние redux, то оно не внутреннее. Ну а если рассматривать локальное состояние компонента, то может конечно, только толку от такого локального состояния не много и статья как раз о работе с глобальным состоянием таким образом.


4) Может ли ваш Actor создать еще акторов?
Да. Он может рендерить их внутри себя, как у вас в примере.


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


const AppActor = ({ param1, param2, showModalX }) => ghosts(
   ghost(MenuActor, { param1 }),
   ghost(PagesActor, { param2 }),
   showModalX && ghost(ModalXActor)
)

Почему нельзя просто отрендерить MenuActor в компоненте Menu, PagesActor (не уверен что это допустим что-то что нужно для всех страниц) в App и ModalXActor в компоненте ModalX? Получится почти то же самое, что держать useEffects прямо в компонентах или хуках, только без лишних рендеров. Я использовал такой подход.


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

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


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


Такой подход рекомендует сама документация redux, вот здесь:
https://redux.js.org/style-guide/style-guide#put-as-much-logic-as-possible-in-reducers

action знает, что функция pureFn вернула два новых значения (просто упаковав их в объект).

Вот я как раз и говорю о том, что action-у об этом знать не нужно так как это детали работы pureFn. Все то, что она там делает c value это ее личное дело, внешник код знает только о том, что она принимает value и возвращает value. То есть для внешнего кода ее интерфайс value => value.


Допустим наша pureFn может по какому-то внутреннему условию менять a + b или a + c или только b или ничего. Внешнему коду должно быть абсолютно по барабану, иначе толку от этой абстракции мало.


Чистая функция не будет их менять изнутри, она создаст новые, в иммутабельном стиле. И вернет новые. Которые точно так же будут помещены в value.

Или вернет старые, но поскольку value мутейтилось, то даже если a не поменялось, компонент ниже перерисуется.


const Component1 = () => <div>{Store.value.a}</div>;

А ведь а на самом деле тоже может быть не примитивным. Тогда в этом случае:


this.value.a = a;

Все компоненты следующего вида так же обновятся


const Component1 = () => <div>{Store.value.a.someValue}</div>;

Даже если someValue не поменялся. В общем чтобы это работало, нужно раскладывать все до примитивов и мутейтить свойства, что приводит к tight coupling.


А с какими-то условными массивами так и вообще не поработаешь иммутабельно, так как стандартной практикой в MobX является в массивы ложить не сырые данные, а другие сторы, как в примере здесь:
https://mobx.js.org/defining-data-stores.html


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

Information

Rating
Does not participate
Location
Полтава, Полтавская обл., Украина
Date of birth
Registered
Activity