Pull to refresh

Comments 64

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

Если зацикливаться на использовании не более N уровней можно прийти к нарушению DRY.

Проблему с глубокой вложенностью абстракций можно получить абсолютно с чем угодно. Это могут быть хуки, классы, методы классов, объекты, даже чистые функции. И никакой линтер тут не поможет.

О разбросывании этого всего добра по разным файлам вообще молчу

Так уже давным давно (ещё в 2016 году) все уважающие себя разрабы используют React только в связке с MobX'ом, и жизнь у всех на порядок легче поэтому. Или в 2021 году вы не знали об этом?

Очевидно что голый реакт это убожество, реакт + редакс — убожество в квадрате, единственный вариант это реакт + mobx, тогда все недостатки сходят на нет.

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

Это говорит лишь о том, какой лютый говнокод они пишут. И насколько они некомпетентны в профессии. Ставить экономию на кол-ве символов выше читаемости кода — абсурд.

Вы большой фанат MobX я смотрю.

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

Мне с телефона не удобно смотреть сендбокс, но мутабельность и классы, ООП там где оно ни к чему несут за собой недостатки с которыми мне бы не хотелось мириться.

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

Я не говорю что MobX это плохо всегда. В конце концов он позволяет справляться с задачами простой и средней сложности достаточно эффективно. Однако это не панацея. Идеального подхода нет

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

С вами все понятно)) Искренне сочувствую тем, кому достается ваш код) Хотя, наоборот это же подарок, проект на переписывание с нуля)

но мутабельность и классы, ООП там где оно ни к чему

С чего это вдруг они ни к чему?? Они ещё как к чему как раз таки.
несут за собой недостатки с которыми мне бы не хотелось мириться

Это ещё какие недостатки? Только пожалуйста реальные, а не выдуманные, теоретические и т.п.

Я не говорю что MobX это плохо всегда

Это не плохо никогда, от слова совсем. Без шуток. Разве что в вашем выдуманном мире, где кроме никому не нужных чистых функций ничего не существует)

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

Сложно не заметить общую тенденцию в React по движению в сторону ФП. Но вы обвиняете всех вокруг в некомпетентности. У вас не закрадывается мысль что это вы что-то упускете, а не все остальные (включая разработчиков Facebook)?

У чистых функций слишком много преимуществ чтобы я мог перечислить их здесь, к тому же вы все-равно не поймёте. Чтобы объяснить это вам, со всеми вашими стереотипами нужна целая книга, и такая книга есть. Это composition software by Eric Elliott

https://medium.com/javascript-scene/composing-software-the-book-f31c77fc3ddc

Вы можете отрицать и делать вид, что проблем ООП не существует сколько угодно, но это не решение проблемы.

Так их реально не существует. Вы сами себе их придумали.
Те кто пишут код после меня не страдают, а разделяют такие же взгляды и понимают подход, нас таких много :)

Сочувствую работодателю вашему)
Сложно не заметить общую тенденцию в React по движению в сторону ФП. Но вы обвиняете всех вокруг в некомпетентности.

Есть люди которые думают своей головой, я как раз к таким и отношусь, а не поклоняюсь в слепую тому, куда движется технология Х или куда сказал Вася Пупкин.
У вас не закрадывается мысль что это вы что-то упускете, а не все остальные (включая разработчиков Facebook)?

Нет, я то как раз ничего не упускаю, от слова совсем. Я использую JS на 100%, в отличии от некоторых. Для меня нету авторитетов и авторитетного мнения, для меня есть только реальность, здравый смысл, читаемость и понятность кода.
У чистых функций слишком много преимуществ чтобы я мог перечислить их здесь, к тому же вы все-равно не поймёте.

Ахахаха, ясно. Вот вы реально сектант, жесть :D

преимущества чистых функций и проблемы ООП

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

Сложно не заметить общую тенденцию в React по движению в сторону ФП

Реакт-компоненты - это формат "одна большая функция". Потому там удобнее функциями. А логику удобнее в ООП (хотя это субъективно, конечно же). Тут нет противоречия. На мой взгляд, все ФП-шные стейтманагеры приводят к конструкциям, выносящим мозг.

Изменения состояния по своей сути это просто связь (state, event) -> newState чистая логика, соответственно чистая функция идеально подходит, чтобы это изменение выразить.

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

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

мутабельность и классы, ООП там где оно ни к чему

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


Конечно, гарантия отсутствия побочных эффектов это хорошо, но какой ценой? Ценой условного редакса? Но сколько к редаксу надо батареек, чтобы он перестал быть многословным и неудобным? Почему-то всегда, где есть редакс, есть ещё пара-тройка его улучшаторов. Я не против редакса, но он реально редко где оправдывается. А на практике, все эти побочные эффекты от мутаций крайне редко стреляют в ногу, если нормально писать.


Тут ещё такая тема, что ФП и иммутабельность в javascript (и даже в typescript) достаточно уныло писать. Ладно был бы какой-нибудь хаскель в браузере, но увы, у нас тут явно не лучший ФП-язык.

JS не плохо подходит для FP и И это не только мое мнение. Для иммутабельности есть так же много всегда, линзы в рамде или иммер например, lodash/fp есть удобные утилиты. Для простых случаев, где состояние не сильно вложенно есть spreed.

Работа с массивами в иммутальном формате даже проще и читабельный

Я сам много пишу на JS в функциональном стиле. И после хаскеля или ML-языков функциональный код на JS вызывает неприятные ощущения. Плюс, был печальный опыт сопровождения проекта с использованием рамды. Код там был wrtie-only. Если бы тоже самое писалось бы в мутабельном императивном стиле, то понять его было бы проще, потому что я считаю, что писать понятный Ф-код труднее, чем ОО-код.

Я согласен, что на рамде можно знатно наговнокодить, сам такое видел не раз. Просто у людей которые только освоили рамду возникает соблазн писать на рамде все, часто это приводит к ужасному кода. JS нужно уметь правильно комбинировать подходы, это не сложно. Когда есть чистая логика - ООП явно ни к чему. Можете например посмотреть код @MaZaAa ниже

Работа с массивами в иммутальном формате даже проще и читабельный

Только иногда это может привести к O(N^2) вместо O(N), причем на ровном месте.

Наведите пожалуйста пример когда так происходит

Любой кейс, где в цикле используется [...arr, newItem] вместо arr.push(newItem). Часто такое можно увидеть в колбеке внутри reduce, когда вместо модификации результата хотят чистую функцию.

Если нужно преобразовать массив в другой массив, то reduce скоре всего не нужен. Комбинация map и filter куда лучше справляются с этой задачей. Если reduce используется для создания объекта из массива, то здесь так же можно обойтись без него, чем-то вроде:


Object.fromEntries(arr.map(item => [item.key, item])); // O(n)

сравните с


arr.reduce((acc, item) => ({ ...acc, [item.key]: item })), {}); // O(n^2)

Я согласен, что arr.push(newItem) с его O(1) временами деградирующим до O(n) работает быстрее чем arr.concat(newItem), так как здесь всегда O(n). Однако на практике это не стоит оптимизации так как JS все-равно делает какие простые операции очень быстро. Да, нагружается зборщик мусора и т.д. однако проблемы с производительностью на практике это не вызывает, все равно выполнится за 3 милисекунды. С большей вероятностью проблемы с производительностью вызовет последующий рендеринг вируального DOM и лучше потратить время на его оптимизацию, в место того чтобы заниматься микрооптимизациями таких вот вещей.

Есть ведь не только Redux, ещё куча библиотек для работы с иммутабельных состоянием. Все зависит от сложности вашего приложения и предпочтений. XState для сложного, Recoil для простого. А иногда и вообще можно обойтись каким-то react-query и useState с useReducer.

Но мне нравится @reduxjs-toolkit за его универсальность

Поглядел на эти Recoil и XState. Весьма трешово. XState, например, предлагает описывать состояние в виде огромного объекта, где нужно описать все переходы. В итоге, простые вещи, вроде TODO-list выглядят вот так печально:


Осторожно, }));}}),!
export const todosMachine = createMachine({
  id: "todos",
  context: {
    todo: "", // new todo
    todos: [],
    filter: "all"
  },
  initial: "loading",
  states: {
    loading: {
      entry: assign({
        todos: (context) => {
          // "Rehydrate" persisted todos
          return context.todos.map((todo) => ({
            ...todo,
            ref: spawn(createTodoMachine(todo))
          }));
        }
      }),
      always: "ready"
    },
    ready: {}
  },
  on: {
    "NEWTODO.CHANGE": {
      actions: assign({
        todo: (_, event) => event.value
      })
    },
    "NEWTODO.COMMIT": {
      actions: [
        assign({
          todo: "", // clear todo
          todos: (context, event) => {
            const newTodo = createTodo(event.value.trim());
            return context.todos.concat({
              ...newTodo,
              ref: spawn(createTodoMachine(newTodo))
            });
          }
        }),
        "persist"
      ],
      cond: (_, event) => event.value.trim().length
    },
    "TODO.COMMIT": {
      actions: [
        assign({
          todos: (context, event) =>
            context.todos.map((todo) => {
              return todo.id === event.todo.id
                ? { ...todo, ...event.todo, ref: todo.ref }
                : todo;
            })
        }),
        "persist"
      ]
    },
    "TODO.DELETE": {
      actions: [
        assign({
          todos: (context, event) =>
            context.todos.filter((todo) => todo.id !== event.id)
        }),
        "persist"
      ]
    },
    SHOW: {
      actions: assign({
        filter: (_, event) => event.filter
      })
    },
    "MARK.completed": {
      actions: (context) => {
        context.todos.forEach((todo) => todo.ref.send("SET_COMPLETED"));
      }
    },
    "MARK.active": {
      actions: (context) => {
        context.todos.forEach((todo) => todo.ref.send("SET_ACTIVE"));
      }
    },
    CLEAR_COMPLETED: {
      actions: assign({
        todos: (context) => context.todos.filter((todo) => !todo.completed)
      })
    }
  }
});

export const createTodoMachine = ({ id, title, completed }) =>
  createMachine(
    {
      id: "todo",
      initial: "reading",
      context: {
        id,
        title,
        prevTitle: title,
        completed
      },
      on: {
        TOGGLE_COMPLETE: {
          actions: [
            assign({ completed: true }),
            sendParent((context) => ({ type: "TODO.COMMIT", todo: context }))
          ]
        },
        DELETE: "deleted"
      },
      states: {
        reading: {
          on: {
            SET_COMPLETED: {
              actions: [assign({ completed: true }), "commit"]
            },
            TOGGLE_COMPLETE: {
              actions: [
                assign({ completed: (context) => !context.completed }),
                "commit"
              ]
            },
            SET_ACTIVE: {
              actions: [assign({ completed: false }), "commit"]
            },
            EDIT: {
              target: "editing",
              actions: "focusInput"
            }
          }
        },
        editing: {
          entry: assign({ prevTitle: (context) => context.title }),
          on: {
            CHANGE: {
              actions: assign({
                title: (_, event) => event.value
              })
            },
            COMMIT: [
              {
                target: "reading",
                actions: sendParent((context) => ({
                  type: "TODO.COMMIT",
                  todo: context
                })),
                cond: (context) => context.title.trim().length > 0
              },
              { target: "deleted" }
            ],
            BLUR: {
              target: "reading",
              actions: sendParent((context) => ({
                type: "TODO.COMMIT",
                todo: context
              }))
            },
            CANCEL: {
              target: "reading",
              actions: assign({ title: (context) => context.prevTitle })
            }
          }
        },
        deleted: {
          onEntry: sendParent((context) => ({
            type: "TODO.DELETE",
            id: context.id
          }))
        }
      }
    },
    {
      actions: {
        commit: sendParent((context) => ({
          type: "TODO.COMMIT",
          todo: context
        })),
        focusInput: () => {}
      }
    }
  );

Даже redux на этом фоне выглядит уже не так страшно

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

Цель XState не ставит перед собой задачу бла бла бла как и redux

Цель XState и redux заставить разработчиков разочароваться в профессии, ощутить сильное кровотечении из глаз и перегрев стула.

Это конечно можно все и в MobX но там, скорее всего никто просто не будет об этом думать. Точно так же как не будут думать о разделении сайд эффектов и изменения состояния, детерминизме, декомпозиции и многом ещё

Все это и не нужно, эти накладные расходы никогда себя не оправдают из-за write-only говнокода, тупому и слепому следованию паттернам, серьезного усложнения на ровном месте и т.п.

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

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


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

Особенно когда вся сложная логика написана в TDD

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

Ну да, конечно, чистые функции ведь так сложно поддерживать.

Да, ведь это реальный говнокод и лапша, без иронии и без шуток.

нужно намешать сайд эффекты c мутациями в одном методе

Все что угодно, лишь было удобно разрабатывать без боли и страданий.

при декомпозиции забыть сохранить прежнее поведение класса

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

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


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

Тесты — зеркало требований. Меняются требования меняются меняются и тесты причем в первую очередь. И тут не важно какой подход вы используете для разработки, конечно если вы не используете методику "кодим без тестов"


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

Вот как раз при разработке с помощью чистых функций принцип tight coupling, high cohesion достигается очень легко. Функциональная композиция позволяет добиться минимальной вложенности абстракций друг в друга. Например если у вас есть 3 слоя, из не нужно вкладывать один в другой как в ООП (class A принимает class B в параментрах и использует его методы или и вовсе просто создает его экземпляр в конструкторе). В место этого делается compose(A, B, C) и функции даже не знают о существовании друг друга. Если интерфейсы A и B не совпадают это легко решается на уровне композиции, а не внутри абстракций.


Вы просто сами не понимаете о чем говорите

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

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


Просто обычно, чем больше кода, тем больше вероятность ошибки. По мне, куда проще допустить ошибку конструируя монструозные состояния в XState для тривиальных вещей. 180 строк для стейта TODO-List. Мне страшно представить, как будет выглядеть состояние в более-менее реальном проекте. 500-1000 строк на домен?

Мне тоже страшно смотреть на XState если честно, но я очень проникся подходом так как это дейстительно позволяет сделать некоторые неявные вещи более явными. В redux style guide так же рекоммендуется рассматривать редюсеры как сейт машины, вот здесь. Однако ничего для работы с ними в @reduxjs-toolkit нет, потому я придумал свое решение и даже создал issue. Там ты тоже можешь найти примеры

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

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

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

Да, и я писал, что XState — это для сложного

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

Ну что за смехотворные утверждения, нет ни одного кейса, вообще ни одного даже близко, где XState будет лучше MobX'a или даже того же ущербного redux'a.

Вот эти ваши рассуждения в стиле вот это «для сложного», вот это для «простого» нелепы.

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

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

Вы как, нормально себя чувствуете?) Голова не болит? :D
Все сверх просто, легко читаемо, очевидно и понятно.
get continueBtnActive() {
    if (this.title.value.trim() === '') {
        return false;
    }
    if (this.addedItems.length === 0) {
        return false;
    }

    return true;
}

// ...
<Button 
    onClick={state.nextStep} 
    disabled={state.continueBtnActive === false}>
        Продолжить
</Button>


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

Разве что в ваших нездоровых мечтах и снах :D Как обычно к реальности нулевое отношение имеют ваши слова.

А какой механизм в MobX ударит вас по рукам если вы этого не сделаете? Что поможем вам отследить все ли переходы описаны правильно? Гуляние по коду в компонент и в Store обратно?


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


К тому же если событие вызывается в 3-х местах, вам нужно проследить за тем, чтобы во всех трех местах было:


disabled={!state.continueBtnActive}

Также вам бы не мешало научиться пользоваться операторами && и || в место того чтобы городить условные операторы только для того, чтобы вернуть Boolean:


get continueBtnActive() {
  const hasValue = !!this.title.value.trim();
  const hasAddedItems = !!this.addedItems.length;
  return hasValue && hasAddedItems;
}
Также вам бы не мешало научиться пользоваться операторами && и || в место того чтобы городить условные операторы только для того, чтобы вернуть Boolean:

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

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

Абсолютно все.

К тому же если событие вызывается в 3-х местах, вам нужно проследить за тем, чтобы во всех трех местах было:
disabled={!state.continueBtnActive}


И что с того? Это props кнопки, он магическим образом не знает сам надо ему быть задисейбленым или нет. Ваш XState ему в этом не поможет, т.к. этому компоненту нужно сообщить об этом.

А какой механизм в MobX ударит вас по рукам если вы этого не сделаете? Что поможем вам отследить все ли переходы описаны правильно? Гуляние по коду в компонент и в Store обратно?

Что вы курите?) Берешь и пишешь, все элементарно, наглядно и прозрачно.

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

И что это за набор слов не несущий смысла?)
Вам русским языком было сказано, максимальная простота и читаемость сверху вниз, слева на право.

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


continueBtnActive (when) hasValue && hasAddedItems

Что вы курите?) Берешь и пишешь, все элементарно, наглядно и прозрачно.

С таким успехом вам ни MobX ни React не нужен, берешь и пишешь что уж там, все ведь элементарно.


Абсолютно все.

Некоторые события могут быть вызваны в каком-то условном useEffect или autorun как реакции. Какую кнопку будете блокировать в этом случае? Вам прийдется if дописать в каждом таком месте.


И что это за набор слов не несущий смысла?)

Да, я наверное сложновато формулировал. Но суть в том, что стейт машины заставляют думать когда вызов события актуален до того, как вы начали писать код обработки события. Таким образом вы никогда не упустите какой-то кейс. События обробатываются только тогда, когда им разрешено. Если вам не понятно и это объяснение, посмотрите видео об XState от его автора.
https://www.youtube.com/watch?app=desktop&v=VU1NKX6Qkxc


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


Например react-query. Скорее всего эта библиотека позволит вам заменить MobX в большенстве мест, где вы его используете

Таким образом вы никогда не упустите какой-то кейс

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

Например react-query. Скорее всего эта библиотека позволит вам заменить MobX в большенстве мест, где вы его используете

Вообще нет, даже не близко, он слишком примитивен, ограничен и ущербен.

Некоторые события могут быть вызваны в каком-то условном useEffect или autorun как реакции. Какую кнопку будете блокировать в этом случае?

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

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

Хотя я не согласен с MaZaAa, что код читается слева-направо, в вашем continueBtnActive много недостатков.


Во-первых, использовать специальные конструкции языка — значит ухудшать читаемость и скорость восприятия для разработчиков в целом. Если вы видели конструкции Elixsir или Ruby, тогда не нужно объяснять, если не видели — то Boolean() всегда предпочтительнее !!. Описание логики "не на языке, а с помощью языка" — одно из ключевых требований к синьорскому коду, по крайней мере в проектах с долгосрочной поддержкой.


Во-вторых, приведение типов в JS — очень сомнительное "удовольствие", так как часто результат будет не тем, что ожидается. TS решает эту проблему только частично, оставляя пространство для логических багов. Поэтому чистое сравнение this.title.value.trim() === '' выглядит намного лучше.


В-третьих, работа с операторами && и || требует хорошего знания работы языка, так как сравнение производится как boolean, а результат возвращается как значение, соответственно 0 && 1 выведет 0, а не false. В конкретном коде это не играет роли, но такие ошибки предикции распространены очень сильно. И даже в примере — чуть удлинится код, и предсказать возвращаемое значение будет сложно как путем визуального анализа, так и статическим анализом, либо результатом выполнения.


Что касается темы "кнопки не должны сейчас срабатывать" — в любом случае, стейт машины запущены или отдельные функции, придется описывать эту логику отдельно. Если, конечно, в XState нет проверки disabled={someStateMachine.isTransitioning}, так как добрая половина логики в приложениях — асинхронная. С MobX это описывается очень просто.


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

Также вам бы не мешало научиться пользоваться операторами && и || в место того чтобы городить условные операторы только для того, чтобы вернуть Boolean:

get continueBtnActive() {
  const hasValue = !!this.title.value.trim();
  const hasAddedItems = !!this.addedItems.length;
  return hasValue && hasAddedItems;
}


Непонятный наезд. Вы какой-то «самозванец», потому что так пишут джуны..))
Код выше на If-ах с ранними ретурнами намного легче читается.

Таким образом дополнительный плюс в позицию MaZaAa:
т.е. выразительность, читаемость, поддерживаемость кодовой базы на Mobx ценнее, чем вот эти вот redux, xstate, тесты и говнокод из лапшы чистых функций простигосподи.

Не использовал xstate, но применял небольшие самопимсные стейт машины для управления анимациями и диалогами

В случае с анимациями - была панель уведомлений и анимации на появление\скрытие новых уведомлений. Уведомления убиралась по клику или таймауту. Таймаут сбрасывается при добавлении новых уведомлений. По итогу лапшу с кучей if, переписал на простую таблицу состояний\переходов.

Использование для вложенных диалогов или многоэтапных форм. Удобно описать весь граф переходов как FSM.

А можете аргументировать, в чем заключаются недостатки классов и ООП?

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

Есть ещё много нюансов но у меня нет времени все описывать, рекомендую книгу выше, там все проблемы классов описаны подробно

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

1) создать процедуру и уйти в процедурное программирование (самое плохое что можно сделать)

2) создать отдельный класс + агрегация + делегирование

3) продолжать дублировать код.

Какой вариант вам нравится больше всего?

Это как раз та проблема, из-за которой реакт перешёл на хуки. Иногда сложно быть DRY на ООП.

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

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

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

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

И в чем проблема??? Разбиваются элементарно.

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

Да где проблемы то??? Ну все же элементарно, есть множество вариантов решения, вот например:
class Test1 {
    num = 2;
    num2 = 4;
    flag = false;

    someLogic = () => {
        // some stuff here
        someBasicClassesLogic(this);
        // some stuff here
    }
}

const test1 = new Test1();

class Test2 {
    num = 4;
    num2 = 8;
    flag = true;

    someLogic = () => {
        // some stuff here
        someBasicClassesLogic(this);
        // some stuff here
    }
}

const test2 = new Test2();

function someBasicClassesLogic(classEx) {
    classEx.num *= 2;
    classEx.num2 /= 2;
    classEx.flag = !classEx.flag;
}

test1.someLogic();
test2.someLogic();

console.log(`test1.num`, test1.num);
console.log(`test1.num2`, test1.num2);
console.log(`test1.flag`, String(test1.flag));
console.log('----');
console.log(`test2.num`, test2.num);
console.log(`test2.num2`, test2.num2);
console.log(`test2.flag`, String(test2.flag));


или вот
class WithSomeLogic {
    num = 4;
    num2 = 8;
    flag = true;

    constructor(num, num2, flag) {
        this.num = num;
        this.num2 = num2;
        this.flag = flag;
    }

    logic = () => {
        this.num *= 2;
        this.num2 /= 2;
        this.flag = !this.flag;
    }
}


class Test1 {
    logic = new WithSomeLogic(2, 4, false);

    someLogic = () => {
        // some stuff here
        this.logic.logic();
        // some stuff here
    }
}

const test1 = new Test1();

class Test2 {
    logic = new WithSomeLogic(4, 8, true);

    someLogic = () => {
        // some stuff here
        this.logic.logic();
        // some stuff here
    }
}

const test2 = new Test2();

test1.someLogic();
test2.someLogic();

console.log(`test1.num`, test1.logic.num);
console.log(`test1.num2`, test1.logic.num2);
console.log(`test1.flag`, String(test1.logic.flag));
console.log('----');
console.log(`test2.num`, test2.logic.num);
console.log(`test2.num2`, test2.logic.num2);
console.log(`test2.flag`, String(test2.logic.flag));


и т.д. и т.п. как удобно, так и разбивай, вариантов масса, ну элементарно же.

В чем прикол говорить о проблемах, которые проблемой не являются от слова совсем? Вы пытаетесь оправдать свой убогий код, что якобы если писать по человечески то ты утонешь в «проблемах»? Смешно и абсурдно.

Вы называете проблемой элементарнейшие вещи.

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

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


Вы написали 2 варианта кода
1) использует императивную процедуру someBasicClassesLogic. Внутри нее ООП уже нет, это просто процедурное программирование и как раз для того, чтобы уменьшить количество такого кода ООП и было придумано. Пока здесь 3 строчки кода кажется что все хорошо, но на самом деле это не совсем так даже сейчас. Мне нужно объяснять вам все недостатки процедурного подхода при масшатбировании?
2) Использует агрегацию, доделегирование. Но у вас делегирование недоделанное так как после рефакторига у test1 и test2 должен остаться такой же интерфейс как был до рефакторинга. то есть данные должны читаться как test1.num а не как test2.logic.num. Кажется вы забыли написать 3 геттера для полей чтобы избежать этой проблемы.


А теперь решение на ФП:


// some utils.js
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

// logic.js
const someLogic = (data) => ({
  num: data.num * 2,
  num2: data.num2 / 2,
  flag: !data.flag
});

const test1 = pipe(
  // some stuff here
  someLogic,
  // some stuff here
);

const test2 = pipe(
  // some stuff here
  someLogic,
  // some stuff here
);

Для использования с реакт вы можете сделать что-то вроде


setSomeState(test1);
setSomeState(test2);
setSomeState(someLogic);

Вот только имена нужно дать адекватные

Мне нужно объяснять вам все недостатки процедурного подхода при масшатбировании?

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

Я не сектант и не фанатик как вы, у которого есть подходы ради подходов и выдуманные проблемы, которых не существует в реальной жизни.

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

Использует агрегацию, доделегирование. Но у вас делегирование недоделанное так как после рефакторига у test1 и test2 должен остаться такой же интерфейс как был до рефакторинга. то есть данные должны читаться как test1.num а не как test2.logic.num. Кажется вы забыли написать 3 геттера для полей чтобы избежать этой проблемы.

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

так как после рефакторига у test1 и test2 должен остаться такой же интерфейс как был до рефакторинга.

Боже, что за бред, с чего это вдруг он должен остаться таким же?? С того что вы в вашем выдуманном мире так захотели что ли?

Кажется вы забыли написать 3 геттера для полей чтобы избежать этой проблемы.

Нет никакой проблемы, сколько раз говорить.

А теперь решение на ФП:
// some utils.js
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

// logic.js
const someLogic = (data) => ({
  num: data.num * 2,
  num2: data.num2 / 2,
  flag: !data.flag
});

const test1 = pipe(
  // some stuff here
  someLogic,
  // some stuff here
);

const test2 = pipe(
  // some stuff here
  someLogic,
  // some stuff here
);


Боже, вот это убожество, аж кровь из глаз идет.Разумеется такое говно после вас никто поддерживать не будет. Любитель write-only кода.

Более того вы корявое решение предлагаете, т.к. пример банальный, а если у классов test1 и test2 есть ещё по 6-7 методов и всех их разная логика.

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


Например:


Это называется композиция

Вы ничего не понимете в композиции. Вы пишите код неосознанно, совершенно не понимая, что делаете. Композиция объектов бывает 3-х видов:


  • агрегация
  • конкатенация
  • делегирование

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


Вы сейчас похожи на строителя недоучку который руководствуясь "здравым смыслом" строит дом, скрепля кирпичи клеем и cкотчем.


Боже, что за бред, с чего это вдруг он должен остаться таким же?? С того что вы в вашем выдуманном мире так захотели что ли?

Я ставил задачу так:


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

Объясняю подробно. Изначально, когда мы только начинаем писать код он всегда простой и понятный, классы маленькие. Но требования меняются, дополняются, баги исправляются, логика усложняется и если мы не будем предпринимать меры, то класс начинает разростаться. Что вам подсказывает ваш здравый смысл? Мне он подсказывает, что нужно провести декомпозицию нашего класса. Однако для того, чтобы внешний код который уже работает с этим классом не сломался, очевидно нам нужно оставить интерфейс таким, каким он был до этого. Таким образом декомпозиция может быть приемом для рефакторнига. Вы можете прийти в новый проект, увидеть там класс на 1000 строк и провести рефакторинг этого класса с помощью декомпозиции не трогая код, который этот класс использует. Однако с классами декомпозиция это тот еще геморой. Так как их часто пишут такие ребята как вы, которые смешивыат логику с сайд эффектами, мутации с паралельными вычислениями в один метод. В итоге получается что логику сложно протестировать потому что там какой-то Math.random и обращение к API внутри + еще один метод другого класса который тоже это все может содержать в себе. С чистыми же функциями декомпозиция и тестирование это всегда очень просто и предсказуемо. Чистые функции очень просто писать в TDD.


Боже, вот это убожество, аж кровь из глаз идет.Разумеется такое говно после вас никто поддерживать не будет. Любитель write-only кода.

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


const c = (x) => b(a(x));
const c = compose(b, a);
const c = pipe(a, b);
const c = (x) => { // процедурный стиль полезен для сложных случаев, но можно легко навкладывать функции друг в друга больше чем надо и усложнить код за счет ненужных переменных
   const aResult = a(x);
   const bResult = b(aResult);
   return bResult ;
}

Если же вы считаете, что это


class WithSomeLogic {
    num = 4;
    num2 = 8;
    flag = true;

    constructor(num, num2, flag) {
        this.num = num;
        this.num2 = num2;
        this.flag = flag;
    }

    logic = () => {
        this.num *= 2;
        this.num2 /= 2;
        this.flag = !this.flag;
    }
}

Читабельней чем это:


const initialData = {
  num: 4,
  num2: 8,
  flag: true,
};

const logic = (data) => ({
  num: data.num * 2,
  num2: data.num2 / 2,
  flag: !data.flag
});

И считаете, что так вы используете "все возможности языка", что ж, флаг вам в руки. Мощь JS совсем не в ООП на классах, но в прототипном ООП и функциональном программировани. И да прототипне ООП это не только о __proto__ и prototype, а о создании объектов с помощью {} и каскадировании с помощью { ...obj, ...obj2 }. Создание объектов с помощью new досталась JS-у от Java, так как Брендану Эйху задачу поставили сделать язык похожим на Java. Некоторые даже считают добавление классов в JS ошибкой и я согласен. Они не дают никакой пользы, кроме того, что синтаксис знаком тем, кто раньше писал на C# или Java. Все то, что написано с class и new в JS можно написать без них и получится понятнее. Используя классы вы пишите на части JS которая была создана только для того, чтобы сделать JS более похожим на Java по синтаксису.


Более того вы корявое решение предлагаете, т.к. пример банальный, а если у классов test1 и test2 есть ещё по 6-7 методов и всех их разная логика.

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


const updatedDomainState = domainLogic.pureFn(arg, domainState)

А для setState в реакт так совсем просто, если pureFn каррирована


setDomainState(domainLogic.pureFn(arg))
Все равно как об стенку горох т.к. вы секстант говнокодер и гордитесь этим.

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

Код который пишете вы — типичное одноразовое write-only говно, которое со старта убивает дальнейшую жизнь проекта, понятное (в течении 2-3 дней после написания) только вам одному (не надо выдумывать сказки якобы вас там много с вами пишет один проект в таком ущербном стиле, в это никто никогда не поверит, когда такая ерись на проекте, то 99% ее пишет только один человек, который ошибочно думает и даже верит в то, что если к нему в команду придет кто-то, то он все поймет и вольется в проект).

Я говорю о реальном коде и реальных проектах, а не о выдуманных функция которые складывают 2+2.

В вашем комментарии нет никакой аргументации, одни оскорбления.


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

А как насчет того, что вы даже в простом примере забыли написать геттеры при рефакторинге тем самым изменив поведение класса (чтение данных из экземпляра не такое как было раньше) который рефакторили? Еще и говорили о том, что поведение не должно остаться таким же. С таким успехом после каждого рефакторинга можно идти править пол кодовой базы.


Код который пишете вы — типичное одноразовое write-only говно, которое со старта убивает дальнейшую жизнь проекта, понятное (в течении 2-3 дней после написания) только вам одному

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


Я говорю о реальном коде и реальных проектах, а не о выдуманных функция которые складывают 2+2.

Приведите пример где при расширении код на классах лучше кода без классов. Заметте я не говорю без ОПП совсем, я говорю без классов, используя чистое прототипное ООП (каскадирование) и ФП.

Однако это не панацея

По ощущениям как раз наоборот, на сегодняшний день react + mobx + TS — это лучшая спасительная панацея на рынке.

Это прекрасно что этих иструментов достаточно для решения ваших задач. Однако это не значит, что мы достигли совершенства и дальше развиваться не нужно :)
Я считаю, что с задачами по описыванию логики переходов из состояния A в состояние B чистые функции справляются лучше классов с мутациями внутри. И у меня есть очень много аргументов :)


Если в вашем случае все просто и главная проблема "обновлять компоненты в разных местах когда данные меняются", то MobX с ней прекрасно справляется до тех пор, пока изменения в данных простые. Есть вероятность, что вся работа с глобальными данными в приложении будет простой и чистые функции как-бы не нужны, все и так просто. Однако, все приложения над которыми я работал имели хотя бы несколько сложных случаев. Я ни разу не пожалел, что использовал redux, так как понимал зачем я это делаю :) Если нет понимая зачем нужен redux — он вам не нужен. Однако говорить, что все остальные идиоты потому, что используют его, так как это делает MaZaAa тоже не правильно

Я считаю, что с задачами по описыванию логики переходов из состояния A в состояние B чистые функции справляются лучше классов с мутациями внутри. И у меня есть очень много аргументов :)

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

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

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

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

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

Почему не смогу? Вполне всё используется:

@observable.ref value = {a: 1, b: 2}; // иммутабельность
@computed get valueA() {
  // чистая функция - аналог селекторов редукс
  return pureFuncGetA(this.value);
}
@action setValueA(arg) {
  // весьма похоже не редьюсер, тоже чистая
  this.value = pureFuncUpdateA(arg, this.value);
}

Как видим, МобХ никоим образом не зажимает использование ФП, если такое вдруг понадобилось.

У меня небыло проектов с MobX, но я вижу разработку на нем примерно так:


Ми пишем на MobX и у нас есть состояние value с которым работаем по-обычному как это делают в MobX, то есть мутейтим его в 3-х методах например как-то так (псевдокод):


incrementA() {
  this.value.a++
},
incrementB() {
  this.value.b++
},
incrementC() {
  this.value.c++
},

// потом где-то в разных компонентах мы их читаем
const Component1 = () => <div>{Store.value.a}</div>;
const Component2 = () => <div>{Store.value.b}</div>;
const Component3 = () => <div>{Store.value.c}</div>;

Потом у нас появляется метод:


doSomethingComplicatedWithAandB() {
   // 50 строк каких-то мутаций которые не трогают `value.c`
}

Мы думаю ага, как-то сложновато, сейчас бы вынести всю эту логику в чистую функцию и в другой файл рядом, так как это слишком глубокие детали реализации которые для чтения этого файла как-бы не нужны.
Но для того, чтобы вынести логику в чистаю функцию и не сломать себе ногу, нам нужно сделать миграцию observable(value) -> observable.ref(value), переписать 3 метода на иммутабельный подход к value, добавить 3 компьютеда, использовать компьютеды в компонентах в место прямого доступа.


Я что-то не так понимаю?

Вынос логики в чистую функцию предполагает, что эта функция в итоге вернёт новые значения а и б, вычисленные по хитрому алгоритму? Так и сделаем - получаем результат в виде объекта {a, b}, обновляем значения в составе value. Ну то есть вовсе не обязательно несколько раз мутировать их в процессе выполнения этой функции.

В МобХ можно обновить несколько отдельных значений в одной транзакции, тогда всё, что за ними следит в данный момент, отработает только по завершению этой транзакции (и только если значения реально поменялись)

Если вы имеете ввиду делать что-то вроде:


const { a, b } = pureFn(this.value);
this.value.a = a;
this.value.b = b;

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


То у этого решения я вижу как минимум 2 серьезных недостатока:
1) Наш action знает что именно делает наша pureFn с данными. Это tight coupling. Если логака работы pureFn c this.value изменится, нам нужно будет обновлять экшен
2) Иногда это может стать вовсе не возможным, например если "a" и "b" это массивы объектов, а pureFn может добавлять, удалять, менять данные в зависимости от разных условий


Для того чтобы action не зависел от логики это должно выглядеть так:


this.value = pureFn(this.value);

action знает, что функция pureFn вернула два новых значения (просто упаковав их в объект). Больше он ничего не знает. Даже если a и b - массивы или какие-либо иные сложные значения, это не суть важно. Чистая функция не будет их менять изнутри, она создаст новые, в иммутабельном стиле. И вернет новые. Которые точно так же будут помещены в value.

this.value = pureFn(this.value) можно использовать, и это удобно, если value не содержит ничего кроме a и b. Иначе придется делать this.value = { ...this.value, ...pureFn(this.value) } , и я не вижу каких-то плюсов у этого варианта.

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), но он задизайнен больше под ООП

Насчет примера, из того что могу показать у меня есть один достаточно простой, но суть должна быть понятна:


https://codesandbox.io/s/runtime-sun-1qhtw?file=/src/pages/Game/gameSlice.js


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


Конечно вы можете написать то же самое на ООП и MobX. Но получится ли отделить важное от неважного так просто? Будет ли просто отделить Math.random() от метода step() чтобы сделать step() предсказуемым и тестируемым? Можете форкнуть и попробовать, я в любом случае с удовольствием посмотрю на результат, может научусь чему-то новому в ООП

Я считаю, что с задачами по описыванию логики переходов из состояния A в состояние B чистые функции справляются лучше классов с мутациями внутри. И у меня есть очень много аргументов :)

Ахахах ну да ну да, разве что выдуманные и принтянутые за уши в вашем мире.
Я ни разу не пожалел, что использовал redux, так как понимал зачем я это делаю :)

:D:D ага
Если нет понимая зачем нужен redux — он вам не нужен. Однако говорить, что все остальные идиоты потому, что используют его

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

Очередное заблуждение, он справляется прекрасно когда данные любые, абсолютно любые.

Не очень понимаю смысл претензий. Когда вы используете компонент, вам же не обязательно заглядывать в его код, чтобы понять, какие props он ожидает и чего отрендерит? Так же и с переиспользуемыми хуками – есть четкое API, что он получает на вход и что даёт на выход.

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

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


По поводу вложенности хуков — соглашусь, с первого взгляда даже видна следующая ошибка:


Notification вызывает на каждый рендер хук useBestFriendNotifier -> 
useBestFriendNotifier вызывает на каждый вызов useFriendStatus -> 
useFriendStatus на каждый вызов создает новую функцию handleStatusChange -> 
return () => ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange) не может отписаться от функции handleStatusChange, так как в componentWillUnmount она не будет равна по ссылке той, на которую он подписался.

Вариант решения — обернуть handleStatusChange в useCallback. Не о многом ли заставляет задуматься этот пример? Может, функциональная композиция в таком виде — скорее боль и причина возникновения сложно уловимых багов? В реальности видел большое количество подобных проблем, где приходилось копаться в композиции хуков, корректировать массивы данных, при которых он будет обновляться, глубоко продумывать равенство по ссылкам. На Хабре уже много обсуждали недостатки хуков (которых огромное количество) и то, что писать некорректный код на них проще простого, а корректный становится намного более перегруженным, чем в классовых компонентах. Учитывая все недостатки хуков, больше не использую их в энтерпрайзе и, соответственно, никаких новых сложностей в проекты не добавляется.


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

Sign up to leave a comment.

Articles