Pull to refresh

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 в сторе.

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

не хотелось писать в статью без оригинальных идей )

если вы не хотите использовать функцию-конструктор - UseCreateAppContext можно спокойно сделать функцией с явным return - этот вариант более популярный, но на мой взгляд удобнее возвращаемые переменные записывать в this

Предлагаю учиться у лучших:

// 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
Sign up to leave a comment.

Articles