Всем привет!

Все мы прекрасно знаем что построить полноценный стор на react context достаточно тяжело, а оптимизировать его ещё тяжелее.

А что если я расскажу как это можно сделать быстро и просто?

Введение

Буквально каждую конференцию мы слышим от спикеров, а вы знаете как работают контексты? а вы знаете что каждый ваш слушатель перерисовывает ваш умный компонент (useContext) Пора решить эту проблему раз и на всегда!

Во-первых, обозначим проблемы:

  1. Неявный DI (неясно какие сторы есть на странице и какой из десятков хуков имеет зависимость на стор).

  2. Рандомные рендеры дерева, независимо от того, слушаем мы эти пропсы или нет.

  3. Контексты, на самом деле, это лишь способ доставки пропсов, но мы же хотим построить полноценный стор?

1. withContext

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

// Пример HOC
export const withContexts = function <T>(
  Component: React.ComponentType<T>,
  contexts: React.ComponentType[]
) {}

// Использование будет выглядеть так
export default withContexts(TodoPage, [GlobalStore.Provider])

2. connectContext

Решая проблему рандомных рендеров мы рассмотрели несколько вариантов.
Кастомный хук useStore который бы отслеживал изменения контекста глобально и событиями бы сообщал о том что значение изменилось и ререндерил бы компонент в нужный момент времени. НО мы бы так и остались с проблемой не явного DI в месте использования, что доставляло бы нам немало проблем.

Поэтому выбор пал на HOC который бы умел принимать в себя контексты и получать только ТЕ аргументы, что мы используем.

// Пример HOC
export const connectContext = function <ContextValue, ComponentProps>(
  Context: Context<ContextValue>,
  getValueByKey: (value: ContextValue) => ComponentProps
) {}

// Пример использования
export const StoreExample = connectContext(
  // Стор который используем
  // Стоит улучшить и сделать здесь больше чем 1 контекст
  GlobalStore.Context,
  // Значения сторов
  ({ state: { notifications }, actions: { updateNotifications } }) => 
  // Значения которые получит компонент 
  ({
    globalNotifications: notifications,
    updateGlobalNotifications: updateNotifications,
  })
  // Пропсы которые ожидает увидеть компонент помимо тех что получит из контекста
)<{ minDate: string }>(
  ({ globalNotifications, updateGlobalNotifications, minDate }) => {}

3. createStore

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

// Пример функции которая бы создавал стор
export const createStore = function <State, Actions>(
  // тут стоит явно расширить и научить стор слушать другие сторы,
  // планируем внедрить в ближайшее время
  useCustomHook: () => { state: State; actions?: Actions }
) {}

// Пример Создания стора
export const GlobalStore = createStore(() => {
  const [notifications, updateNotifications] = useState<string[]>([]);
  const [user] = useState({ name: "Grigoriy" });

  return { state: { notifications, user }, actions: { updateNotifications } };
});

И так в результате этих наработок я создал библиотеку и решили поделиться ею с комьюнити.

https://www.npmjs.com/package/context-base-api

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

Спасибо за внимание. Надеюсь на обратную связь.

P.S

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

Следите за обновлениями!

P.S.S

Библиотека была обновлена для использования нескольких сторов одновременно, а так для же для использования зависимых сторов. Ознакомится с кодом можно на гитхабе или npmjs.

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


  1. markelov69
    09.01.2023 18:46
    +1

    Использовать React Context вместо стейт менеджера здорового человека(MobX'a) это конечно нелепость вселенского масштаба.


    1. StiPAFk Автор
      09.01.2023 19:39
      +1

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

      Данный подход используется повсеметсно. но тот же Redux используется чаще. Но его в понимающих кругах не любят. как и Mobx.
      Тот же effector очень хорошо, НО имеет нереальный порог входа)

      Остаётся или редакс или контекст. и в большинстве случаев выбор падает на контекст. но работать с ним умееют еденицы. Библиотека и решает те проблемы с которым сталкивается каждый!


      1. markelov69
        09.01.2023 20:05
        -1

        Но его в понимающих кругах не любят. как и Mobx.

        Не любят MobX? В поминающих кругах? Вы может сделали опечатку и хотели сказать в не понимающих или в кругах любителей write-only кода?

        Вообще что за странное утверждение в котором адекватные люди не любят MobX.


        1. StiPAFk Автор
          09.01.2023 20:50
          -2

          а вы однако однолюб :)

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


          1. markelov69
            10.01.2023 00:22
            +3

            В кругах в которых понимают минусы каждого из подхода

            И какие же минусы у MobX? Настоящие, а не бред сивой кобылы только, по типу он якобы весит много(ничтожный процент по сравнение с весом целого приложения real world), якобы дебажить приложение сложнее(если руки из жопы, то ничего не спасет конечно)

            Вам это ещё предстоит

            С 2016 года использую React + MobX и что-то как-то больше нет альтернатив, только лютый говнокод в виде голого react'a, redux(и похожего шлака), effoector, recoil и т.д. и т.п. А я предпочитаю писать нормальный код, человеко-понятный, и не бороться с проблемами которые сами себе создают те, у кого redux головного мозга.


            1. popuguytheparrot
              11.01.2023 22:34

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

              Сделайте комбайн сторов или зависимый стор на мобх (спойлер, такого не получится)

              Так что не надо говорить про человеко-понятный код на мобх, где куча доп утил, которые работает когда им захочется (autorun,makeAutoObservable)

              Коммуникацию между сторами так вообще говорят обходить надо и это считается нормальным решением?


              1. Alexandroppolus
                11.01.2023 23:09

                Сделайте комбайн сторов или зависимый стор на мобх

                Что конкретно надо сделать?

                куча доп утил, которые работает когда им захочется (autorun,makeAutoObservable)

                У меня в проекте они работают, когда мне захочется.

                Коммуникацию между сторами так вообще говорят обходить надо

                Тоже не совсем понял мысль.


  1. yroman
    09.01.2023 21:27
    +1

    >>This is library help u with solve problem with rerenders and simplify DI to your pages.
    Ребят, вы серьёзно?

    >>// @ts-ignore const useBlabla
    Это, извините, детский сад какой-то.


    1. StiPAFk Автор
      09.01.2023 21:33

      хоть кто то нашёл пасхалку)

      а по поводу // @ts-ignore я написал что типы не описаны до конца, есть возможность помочь. не хватает времени и в опен сорс и проекты писать.

      а про серьёзность темы оптимизации не очень ясно в чём консёрн? есть вариант лучше оптимизировать работу с контекстами велком с предложениями) а так выглядит больше как сюр какой то а не комментарий.


      1. yroman
        09.01.2023 21:35
        +1

        Сюр это ваш английский язык, вообще-то. Я бы постеснялся такое писать, неужели гугль транслейт не помог?


        1. StiPAFk Автор
          09.01.2023 21:36
          -1

          вас U смутило? или что то конкретное?) не претендую на учителя английского но всё же интересно!


          1. yroman
            09.01.2023 21:44
            +1

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


            1. StiPAFk Автор
              09.01.2023 22:03

              а в статье не упоминается сравнение с редакс)
              Этот подход лучше чем классический контекст — если ваш контекст делает больше чем 1 действие (что бывает достаточно чаще если у вас приложение сложнее чем калькулятор) то например если вы храните в глобальном контексте, юзера, количество нотификаций и например тему. при обновление ЛЮБОГО из этих параметров у вас будут рисоваться ВСЕ слушатели (например пользователь в шапке + нотификации + все компоненты что используют тему). данный подход избегает этих ререндеров. внутри библиотеки есть простой кейс который на 2х параметрах сравнивает скорость работы в 1000 кликов.

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

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

              Ну и третье преимущество, контекст не является стором. мы это исправляем функцией createStore которая реализовывает весь бойлер плейт по генерации контекста и стейта который мы потом спускаем вниз. тем самым создавай «подобие стора»

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

              При сравнение скорости работы — я уверен что будет плюс минус одинаково. но тут скорее важна чистота и понятность. Что достаточно субъективная штука. но от боейлер плейта редакса не деться) по этому выбирать редакс в 2022 уже как то плохо.

              Эффектор классный НО имеет нереальный высокий порог входа. 95% если даже не больше НЕ знают эфектор.

              Всё это имхо. Статья была написана чтобы поделится своим опытом кому это нужно. Если вы считаете что ваш опыт отличается — в этом нет ни чего плохого! мне или вам ещё стоит многому научится. Возможно я или вы не правы!)

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


  1. Alexandroppolus
    09.01.2023 22:46

    Неявный DI (неясно какие сторы есть на странице и какой из десятков хуков имеет зависимость на стор).

    Не уверен, что эта проблема была исправлена. Пусть в хуках нет вызовов useContext, но ведь всё равно на странице будут компоненты, обернутые в connectContext, которые используются в других компонентах, и т.д. Вот так и получается, что где-то на странице есть обращения к тем или иным стейтам. Разницы с useContext никакой.

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


    1. StiPAFk Автор
      09.01.2023 22:57

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

      Мы используем в рамках своего достаточно большого проекта десятки сторов в разных страницах. Если есть сомнения есть ли контекст. просто открываешь страницу где используется компонент и проверяешь) не надо искать десятки мест. всего одно.

      Мы используем Next.JS в качестве основного фрейма. Полёт отличный на протяжение нескольких лет. просто решил запаблишить только сейчас. и стали активно развивать тоже только сейчас так как окончательно отказались от СТМ в пользу контекстов.


      1. Alexandroppolus
        09.01.2023 23:19

        Ну то есть у вас проблема с неявными DI на самом деле решена строгим учетом используемых сторов/провайдеров?

        Мы используем в рамках своего достаточно большого проекта десятки сторов в разных страницах. Если есть сомнения есть ли контекст. просто открываешь страницу где используется компонент и проверяешь) не надо искать десятки мест. всего одно.

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


        1. StiPAFk Автор
          09.01.2023 23:33

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

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


  1. DarthVictor
    10.01.2023 00:37

    А можете объяснить, почему вам не подошёл https://beta.reactjs.org/reference/react/useSyncExternalStore и было нужно именно Context-base хранилище?


    1. StiPAFk Автор
      10.01.2023 09:02

      К сожалению это событийная модель которая очень плохо дебажиться. Плюс писать код в событийной моделе достаточно тяжело. по этой причине проще использовать баховый подход который предлагает React.


      1. DarthVictor
        10.01.2023 09:38

        Почему событийная? Какой стор напишите такой и будет.

        Это вполне себе рекомендованный встроенный способ для работы с состоянием. Собственно и react-redux и zustand через неё работают.


  1. gohrytt
    10.01.2023 05:21

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


    1. StiPAFk Автор
      10.01.2023 09:04

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

      Не имеет смысла увеличивать порог входа на проект и увеличивать сложность проекта.
      Опять же таки - моё имхо!


      1. gohrytt
        10.01.2023 19:31

        Например?


  1. ReinRaus
    10.01.2023 08:36

    Удалённ.


  1. Andy_Francev
    10.01.2023 18:47
    +1

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

    На моей практике прекрасно себя показали: valtio, zustand, preact/signals, причём сейчас я больше склоняюсь к последнему.

    У signals хороший коллектив авторов – создатели Preact (а значит, ничтожна вероятность того, что проект закроют, когда автору он надоест), у него отличные биндинги к React, отличная документация.


    1. StiPAFk Автор
      10.01.2023 19:09

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

      За 10 лет моей практике в 99% приложениях СТМ был лишним (Redux/Mobx/Effector) всё это можно было реализовать куда проще на стейте, и куда быстрее. и порог вода в проект снижается каласально.

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


      1. markelov69
        10.01.2023 19:25

        используем около 3х лет данный подход.

        - Уверены что во множественном числе?) Может вы все таки один на проекте с таким подходом к написанию кода) Как бы управлять состоянием так, как это сделано в реакте и react-way подходе - это просто брать себя за голову и окунать в чан с говном. Поэтому всё адекватные люди уже много лет используют MobX в паре с ректом.
        - Представляю какой там "красивый, понятный и поддерживаемый" код скопился с такими подходами)

        И на данный момент полёт отличный

        Да ладно? :D Это как "В принципе цены нормальные, если ничего не покупать"

        За 10 лет моей практике в 99% приложениях СТМ был лишним (Redux/Mobx/Effector)

        Да, если проекты реальные не писать, то стейт менеджер будет лишним, впрочем как и сам по себе код) HTML + CSS за глаза)

         у нас приложение сильно выростало, и нам приходилось всё это дело оптимизировать

        Вангую - разумеется это был не MobX, ибо там оптимизировать нечего, всё и так из коробки оптимизированно.


      1. Andy_Francev
        10.01.2023 20:35

        Там нечего учить. У Signals всего одна страничка, освоить которую можно за 15 минут. По поводу простоты – ну не знаю, куда проще-то. API из 4 (четырёх) функций, из которых вы постоянно будете использовать 1 (реже 2, а 2 остальных ну очень редко).

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


  1. Imanariii
    11.01.2023 14:19
    +1

    Не ругайтесь, я начинающий)

    А чем людям redux не нравится, в чем его минусы?

    Redux ведь подходит как и маленьким так и большим проектам????


    1. markelov69
      11.01.2023 15:35

      А чем людям redux не нравится, в чем его минусы?

      Redux ведь подходит как и маленьким так и большим проектам????

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


    1. StiPAFk Автор
      11.01.2023 19:13

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

      Mobx не решает всех проблем но решит часть проблем Redux.

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


      1. markelov69
        12.01.2023 00:11

        Mobx не решает всех проблем

        Решает абсолютно все и каждую. Без вариантов. Если руки не из того места, то дело не в молотке и гвоздях.


        1. StiPAFk Автор
          12.01.2023 00:19

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


          1. markelov69
            12.01.2023 00:29

            А вы людей в заблуждение не вводите и всё будет хорошо.


            1. StiPAFk Автор
              12.01.2023 00:31

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


              1. markelov69
                12.01.2023 00:42

                я лишь указываю на факты

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