Как использовать React Hooks Context с несколькими значениями для провайдеров - PullRequest
0 голосов
/ 10 января 2019

Каков наилучший способ поделиться некоторыми глобальными значениями и функциями в реакции?

Теперь у меня есть один ContextProvider со всеми ними внутри:

<AllContext.Provider
  value={{
    setProfile, // second function that changes profile object using useState to false or updated value
    profileReload, // function that triggers fetch profile object from server
    deviceTheme, // object
    setDeviceTheme, // second function that changes theme object using useState to false or updated value
    clickEvent, // click event
    usePopup, // second function of useState that trigers some popup
    popup, // Just pass this to usePopup component
    windowSize, // manyUpdates on resize (like 30 a sec, but maybe can debounce)
   windowScroll // manyUpdates on resize (like 30 a sec, but maybe can debounce)
  }}
>

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

Это плохо:

<Provider value={{something: 'something'}}>

Это нормально:

this.state = {
      value: {something: 'something'},
    };
<Provider value={this.state.value}>

Я думаю, что в будущем у меня будет до 30 провайдеров контекста, и это не очень дружелюбно: /

Так как я могу передать эти глобальные значения и функции компонентам? Я могу просто

  1. Создайте отдельный contextProvider для всего.
  2. Сгруппируйте что-то, что использовалось вместе, например, профиль и его функции, тема и ее функции (как насчет ссылочной идентичности?)
  3. Может быть, группа функционирует только потому, что она не меняет себя? какие о ссылочной идентичности, чем?)
  4. Другой самый простой способ?

Примеры того, что я использую в провайдере:

// Resize
  const [windowSize, windowSizeSet] = useState({
    innerWidth: window.innerWidth,
    innerHeight: window.innerHeight
  })
// profileReload
const profileReload = async () => {
    let profileData = await fetch('/profile')
    profileData = await profileData.json()

    if (profileData.error)
      return usePopup({ type: 'error', message: profileData.error })

    if (localStorage.getItem('deviceTheme')) {
      setDeviceTheme(JSON.parse(localStorage.getItem('deviceTheme'))) 
    } else if (profileData.theme) {
      setDeviceTheme(JSON.parse(JSON.stringify(profileData.theme)))
    } else {
      setDeviceTheme(settings.defaultTheme) 
    }
    setProfile(profileData)
  }

// Click event for menu close if clicked outside somewhere and other
const [clickEvent, setClickEvent] = useState(false)
const handleClick = event => {
  setClickEvent(event)
}
// Or in some component user can change theme just like that
setDeviceTheme({color: red})

Ответы [ 3 ]

0 голосов
/ 10 января 2019

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

Например, я ожидал бы, что deviceTheme будет довольно статичным для данного пользователя и, вероятно, будет использоваться большим количеством компонентов. Я бы предположил, что popup может что-то управлять тем, открыто ли у вас всплывающее окно, поэтому оно, вероятно, меняется при каждом действии, связанном с открытием / закрытием всплывающих окон. Если popup и deviceTheme связаны в одном и том же контексте, то при каждом изменении popup все компоненты, зависящие от deviceTheme, также будут повторно визуализироваться. Так что я бы, наверное, имел отдельный PopupContext. windowSize и windowScroll могут иметь похожие проблемы. Какой точный подход использовать глубже в поле мнений, но у вас может быть AppContext для нечасто меняющихся частей, а затем более конкретный контекст для вещей, которые меняются чаще.

Следующий CodeSandbox обеспечивает демонстрацию взаимодействия между useState и useContext с разделением контекста несколькими различными способами и некоторыми кнопками для обновления состояния, которое содержится в контексте.

Edit zk58011yol

Вы можете перейти на этот URL , чтобы просмотреть результат в полном окне браузера. Я рекомендую вам сначала разобраться, как работает результат, а затем посмотреть на код и поэкспериментировать с ним, если есть другие сценарии, которые вы хотите понять.

0 голосов
/ 10 января 2019

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

На данный момент проблема является общей для большинства реализаций React глобального состояния, например, Redux. И общее решение состоит в том, чтобы потребительские компоненты обновлялись только при необходимости с помощью React.PureComponent, React.memo или shouldComponentUpdate hook:

const SomeComponent = memo(({ theme }) => <div>{theme}</div>);

...

<AllContext>
  {({ deviceTheme }) => <SomeComponent theme={deviceTheme}/>
</AllContext>

SomeComponent будет перерисован только при deviceTheme обновлениях, даже если обновлен контекст или родительский компонент. Это может быть или не быть желательным.

0 голосов
/ 10 января 2019

Вы все еще можете объединить их! Если вы беспокоитесь о производительности, вы можете создать объект раньше. Я не знаю, меняются ли используемые вами значения, если нет, это довольно просто:

state = {
  allContextValue: {
    setProfile,
    profileReload,
    deviceTheme,
    setDeviceTheme,
    clickEvent,
    usePopup,
    popup,
    windowSize
  }
}

render() {
  return <AllContext.Provider value={this.state.allContextValue}>...</AllContext>;
}

Всякий раз, когда вы захотите обновить любое из значений, которые вам нужно сделать, мне это нравится, хотя:

this.setState({
  allContextValue: {
    ...this.state.allContextValue,
    usePopup: true,
  },
});

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

Тем не менее, если ваша ценность не сильно меняется, беспокоиться не о чем.

...