Comments 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.
Что говорят разработчики реакта по этому поводу:
Приёмы для оптимизации
Обсуждение
Интересный подход. Одна из проблем с которой сталкиваются те кто пишут решения с использованием селекторов — это вызов селекторов с устаревшими props но новым context-state. Что легко вызывает их падение. Ввиду чего в react-redux
внутри useSelector
-а лежит try{}catch{}
. А тут вроде получается что обновление происходит если затронутые ранее поля в state поменялись. Т.е. т.к. нет селектора, то нечему и падать. Это правда имеет и свои недостатки — в случае когда селектор вернул бы тоже самое значение при разном state, теперь будет rerender. Ну и код ищущий изменения в боевом виде (древовидном) будет куда сложнее. Плюс это уже ни разу не pure хук получается и могут быть проблемы с react-dev-tools, который подменяет хуки всякой пургой.
Но мысль интересная. Есть о чём подумать.
Небольшая практика с JS Proxy для оптимизации перерисовок React компонентов при использовании useContext