Comments 11
this внутри функционального компонента, это уже за гранью добра и зла.
Чем useRef не угодил?
Скорее всего у автора путаница с классовыми компонентами
this находиться не в классовом компоненте в функции-конструкторе, которая просто создает обьект, который потом используется в функциональном компоненте провайдера для помещения в контекст )
UseCreateAppContext
- ну не пишут так, что это?
Если класс, то почему в виде функции, сейчас 2023 год.
Если функциональный компонент - то весь его стейт должен хранится в хуках. Если нужно автоматическое обновление (реактивность), то в useState/useReducer. Если обновление вручную - то можно использовать "сырой" useRef. Есть же руководство Roles of hooks.
А вообще, использовать useContext
в качестве глобального стора для большого приложения - это дикие проблемы с производительностью (постоянный ререндер на каждый чих).
Лучше использовать локальные сторы, везде, где это возможно. Можно попробовать MobX, если не хватает ванильных способов.
Если нужен реально глобальный (обычно не нужен) стор, то redux toolkit + reselect (для кеширования, чтобы не ререндерить).
В любом случае нужно глубже погрузиться в механизмы работы реакта. Вывести в нескольких местах console.log
и увидеть, где происходят лишние ререндеры.
по конвенции функции-конструкторы должны начинаться с заглавной буквы. функции, которые используют хуки - префикс Use.
смотрите на UseCreateAppContext
как на кастомный хук, который возвращает обьект.
полностью с вами согласен по поводу того, что стоит использовать Mobx :) а к вопросу проблем с производительностью - обратите внимание, что обратите внимание, что значение useLocalObservable мобикса храниться как раз в контексте и возвращается как раз useContext
- то есть проблемы не с самим контекстом, а с ререндерами связанными с использованием useState в сторе.
вообще, мне понятен ваш аргумент. задавать стор таким образом действительно неортодоксальный метод и для понимания кода стоило использовать просто кастомный хук
new UseCreateAppContext(props);
Впервые вижу оформление хука как функцию-конструктор, и вызов через new )
не хотелось писать в статью без оригинальных идей )
если вы не хотите использовать функцию-конструктор - UseCreateAppContext можно спокойно сделать функцией с явным return - этот вариант более популярный, но на мой взгляд удобнее возвращаемые переменные записывать в this
У этого подхода есть минусы. Например, react-hooks/rules-of-hooks не сможет проверить, что хук вызван внутри условия или цикла
мда...
Можно было просто ссылку на документацию оставить. Желательно не на легаси https://react.dev/
Предлагаю учиться у лучших:
// zustand/vanilla.js
const createStoreImpl = (createState) => {
// состояние
let state
// обработчики
const listeners = new Set()
// функция обновления состояния
const setState = (partial, replace) => {
// следующее состояние
const nextState = typeof partial === 'function' ? partial(state) : partial
// если состояние изменилось
if (!Object.is(nextState, state)) {
// предыдущее/текущее состояние
const previousState = state
// обновляем состояние с помощью `nextState` (если `replace === true` или значением `nextState` является примитив) или нового объекта, объединяющего `state` и `nextState`
state =
replace ?? typeof nextState !== 'object'
? nextState
: Object.assign({}, state, nextState)
// запускаем обработчики
listeners.forEach((listener) => listener(state, previousState))
}
}
// функция извлечения состояния
const getState = () => state
// функция подписки
// `listener` - обработчик `onStoreChange`
// см. код `useSyncExternalStoreWithSelector`
const subscribe = (listener) => {
listeners.add(listener)
// отписка
return () => listeners.delete(listener)
}
// функция уничтожения (удаления всех обработчиков)
const destroy = () => {
listeners.clear()
}
const api = { setState, getState, subscribe, destroy }
// инициализируем состояние
state = createState(setState, getState, api)
// возвращаем методы
return api
}
export const createStore = (createState) =>
createState ? createStoreImpl(createState) : createStoreImpl
// zustand/react.js
import useSyncExternalStoreExports from 'use-sync-external-store/shim/with-selector'
import { createStore } from './vanilla.js'
const { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports
export function useStore(api, selector = api.getState, equalityFn) {
// получаем срез состояния
const slice = useSyncExternalStoreWithSelector(
api.subscribe,
api.getState,
api.getServerState || api.getState,
selector,
equalityFn,
)
// и возвращаем его
return slice
}
const createImpl = (createState) => {
// получаем методы
const api =
typeof createState === 'function' ? createStore(createState) : createState
// определяем хук
const useBoundStore = (selector, equalityFn) =>
useStore(api, selector, equalityFn)
// не понял, зачем это нужно
Object.assign(useBoundStore, api)
return useBoundStore
}
export const create = (createState) =>
createState ? createImpl(createState) : createImpl
// hooks/useModal.js
import { create } from '../zustand/react'
const useModal = create((set) => ({
isOpen: false,
open: () => set({ isOpen: true }),
close: () => set({ isOpen: false }),
}))
export default useModal
// components/Modal.tsx
import { useEffect, useRef } from 'react'
import { useClickAway } from 'react-use'
import useModal from '../hooks/useModal'
export default function Modal() {
const modal = useModal()
const modalRef = useRef(null)
const modalContentRef = useRef(null)
useEffect(() => {
if (!modalRef.current) return
if (modal.isOpen) {
modalRef.current.showModal()
} else {
modalRef.current.close()
}
}, [modal.isOpen])
useClickAway(modalContentRef, modal.close)
if (!modal.isOpen) return null
return (
<dialog
style={{
padding: 0,
}}
ref={modalRef}
>
<div
style={{
padding: '1rem',
display: 'flex',
alignItems: 'center',
gap: '1rem',
}}
ref={modalContentRef}
>
<div>modal content</div>
<button onClick={modal.close}>X</button>
</div>
</dialog>
)
}
// App.jsx
import Modal from './components/Modal'
import useModal from './hooks/useModal'
function App() {
const modal = useModal()
return (
<>
<button onClick={modal.open}>Open modal</button>
<Modal />
</>
)
}
export default App
React Context: создание глобального стора, используя useContext и useState