Как стать автором
Обновить

Комментарии 10

Привет! Хороший подход, proxy в целом пока недооцененная история.
Советую глянуть mobx. Там нет слова "контекст", но принципы в основе такие же, например, см. useLocalObservable.

В случае создания своей обертки над React.createContext, нет нужды лезть в кишки реакта и что-то делать с _currentValue. Можно сделать обёртку над провайдером, которая будет хранить не значение контекста, а что-то вроде observable:


function CustomProvider({ value, children }) {
  // observableValue создается едоножды и соответственно не вызывает перерисовок
  const [observableValue] = useState(() => new ObservableValue());

  useEffect(() => {
    // засовываем туда наше значение из контекста
    // под капотом `setValue` должен посчитать что изменилось и оповестить соответствующих слушателей
    observableValue.setValue(value);
  }, [value]);

  return (
    <Context.Provider value={observableValue}>{children}</Context.Provider>
  );
}

И в кастомной обертке над React.useContext:


function useMyCustomContext(context) {
  const observableValue = useContext(context);

  const [, update] = useState(0);

  // вместо boolean лучше использовать число
  const forceUpdate = useCallback(() => {
    update(prev => prev + 1);
  }, [update])

  useEffect(() => {
      // детали я оставил за кадром
      // тут мы подписываемся только на интересующие нас поля по какому-то правилу `rule`
      // это может быть списко полей объекта или функция, или что-то ещё.
      // например если мы хотим использовать Proxy как в статье, то
      // `observableValue.getValue()` может вернуть Proxy, который будет создавать `rule`
      observableValue.listen(rule, () => forceUpdate()))
  }, [])

  return observableValue.getValue();
}

При это в имплементации может быть любая существующая библиотека для реактивности, например rxjs или mobx.


опора на внутреннюю реализацию реакта, которая в будущем может поменяться
все проблемы Monkey patchинга
нельзя использовать в классовых компонентах — только вместо useMyCustomContext нужна похожая обертка для классов
нельзя использовать один контекст в нескольких провайдерах

А ещё я бы рекомендовал посмотреть на второй аргумент в useContext и createContext:
React Context: a Hidden Power

Можно сделать обёртку над провайдером, которая будет хранить не значение контекста, а что-то вроде observable

Именно так. Контекст — не стейт-манагер, о чем уже миллион раз говорилось. Это лишь то место, где хранится стейт-манагер (или манагеры), например, тот же редукс так делает. Не понимаю современной тенденции пытаться использовать контекст не по назначению.

Я так думаю это потому-что можно сделать что-то похожее очень просто. Конечно делать так не стоит, но руки у людей чешутся.

Спасибо. Это именно то, до чего я хотел бы сам додуматься) Кстати, это то же решение с правайдером, что я увидел в исихониках библиотеки упомянутой в заключении. И за ссылку на статью отдельное спасибо
А ещё я бы рекомендовал посмотреть на второй аргумент в useContext и createContext:
React Context: a Hidden Power

Не первый раз встречаю упоминание этого API. Но вот пока нигде не попадалось его применения по делу. Видел как его используют чтобы отметить useContext-ы вообще (возвращают 0). Но не видел чтобы народ использовал битовые маски.


Мне кажется применимость такого API в таком виде очень осложнена. Нужно как-то соотносить селекторы (или их аналог) с конкретными битами, коих у нас, кажется до 32. Полагаю всё же надо дождаться нормального API.

Это эксперементальное API, в официальной документации не описано. Соотносить селекторы на уровне того как в статье — для свойств объекта первого уровня — не проблема. Каждое свойство получает маску по типу 0b1 << номер_свойства. И тогда селектор это селектор_свойста_1 | ... | селектор_свойства_n. Как-то соотносить это автоматически наверное можно, но нужно использовать свой API. Например useContextSelector(context, ["foo", "bar"]). Или может какой-нибудь babel плагин, что-бы посчитать заранее.
Это может работать когда у нас есть несколько независимых свойств в контексте, но не для чего-то по типу redux.


Что говорят разработчики реакта по этому поводу:
Приёмы для оптимизации
Обсуждение

Соотносить селекторы на уровне того как в статье — для свойств объекта первого уровня — не проблема

Ну как не проблема. Дичь же :) Работает, но не более того. За ссылки спс, читаю.

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

Интересный подход. Одна из проблем с которой сталкиваются те кто пишут решения с использованием селекторов — это вызов селекторов с устаревшими props но новым context-state. Что легко вызывает их падение. Ввиду чего в react-redux внутри useSelector-а лежит try{}catch{}. А тут вроде получается что обновление происходит если затронутые ранее поля в state поменялись. Т.е. т.к. нет селектора, то нечему и падать. Это правда имеет и свои недостатки — в случае когда селектор вернул бы тоже самое значение при разном state, теперь будет rerender. Ну и код ищущий изменения в боевом виде (древовидном) будет куда сложнее. Плюс это уже ни разу не pure хук получается и могут быть проблемы с react-dev-tools, который подменяет хуки всякой пургой.


Но мысль интересная. Есть о чём подумать.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории