Как я могу обновить все компоненты React, используя контекст без поставщика?

#javascript #reactjs #react-hooks #react-hoc

#javascript #reactjs #реагирующие хуки #react-hoc

Вопрос:

Учитывая этот простой пользовательский хук

 import React, { createContext, useContext } from 'react';


const context = {
   __prefs: JSON.parse(localStorage.getItem('localPreferences') || null) || {} ,
   get(key, defaultValue = null) {
      return this.__prefs[key] || defaultValue;
   },
   set(key, value) {
      this.__prefs[key] = value;
      localStorage.setItem('localPreferences', JSON.stringify(this.__prefs))
   }
};

const LocalPreferenceContext = createContext(context);

export const useLocalPreferences = () => useContext(LocalPreferenceContext);

export const withLocalPreferences = Component => () => <Component localPreferences={ useLocalPreferences() } />;
  

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

** Редактировать **

Хорошо, итак, какова альтернатива, кроме использования useContext then? Это реальный вопрос, на самом деле; как мне обновить компоненты, используя этот хук (или HOC)? useState Это единственный способ? Как? Используя какой-либо источник событий?

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

1. Почему вы не хотите использовать провайдера? Если вам не нужен провайдер, context, вероятно, не тот инструмент, который нужно использовать.

2. Если вы хотите обновить компонент, просто смешайте его с useState .

3. У вас должен быть поставщик, если вы хотите использовать context. Обойти это невозможно. Судя по тому, как это выглядит, все, что вы делаете, это получаете и устанавливаете элементы из localStorage, которые, если это так, вы могли бы просто использовать вместо этого хук useState .

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

5. @NicholasTower ага. в значительной степени.

Ответ №1:

Я думаю, что использование контекста здесь имеет смысл, но вам нужно будет использовать поставщика, поскольку это основная часть того, как работает context. Рендеринг поставщика делает значение доступным для компонентов, расположенных дальше по дереву, а рендеринг с новым значением — это то, что побуждает потребителей выполнять повторный рендеринг. Если провайдера нет, вы можете, по крайней мере, получить доступ к значению по умолчанию (которое у вас есть в вашем коде), но значение по умолчанию никогда не меняется, поэтому react не о чем уведомлять потребителей.

Итак, моей рекомендацией было бы добавить компонент с поставщиком, который управляет взаимодействиями с локальным хранилищем. Что-то вроде:

 const LocalPreferenceProvider = () => {
  const [prefs, setPrefs] = useState(
    () => JSON.parse(localStorage.getItem("localPreferences") || null) || {}
  );

  // Memoized so that it we don't create a new object every time that
  //   LocalPreferenceProvider renders, which would cause consumers to
  //   rerender too.
  const providedValue = useMemo(() => {
    return {
      get(key, defaultValue = null) {
        return prefs[key] || defaultValue;
      },
      set(key, value) {
        setPrefs((prev) => {
          const newPrefs = {
            ...prev,
            [key]: value,
          };
          localStorage.setItem("localPreferences", JSON.stringify(newPrefs));
          return newPrefs;
        });
      },
    };
  }, [prefs]);

  return (
    <LocalPreferenceContext.Provider value={providedValue}>
      {children}
    </LocalPreferenceContext.Provider>
  );
};

  

Вы упомянули в комментариях, что хотели избежать множества вложенных компонентов, и у вас уже есть большой стек провайдеров. Это то, что часто будет происходить по мере увеличения размера приложения. Лично мое решение этой проблемы заключается в том, чтобы просто извлечь поставщиков в их собственный компонент, а затем использовать этот компонент в моем основном компоненте (что-то вроде <AllTheProviders>{children}</AllTheProviders> ). По общему признанию, это просто решение «с глаз долой, из сердца вон», но это все, что меня действительно волнует в данном случае.

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

Следующий код неполон, но, возможно, что-то вроде этого:

 const subscribers = [];
let value = 'default';
const globalObject = {
  subscribe: (listener) => {
    // add the listener to an array
    subscribers.push(listener);
    
    // TODO: return an unsubscribe function which removes them from the array
  },
  set: (newValue) {
    value = newValue;
    this.subscribers.forEach(subscriber => {
      subscriber(value);
    });
  },
  get: () => value
}

export const useLocalPreferences = () => {
  let [value, setValue] = useState(globalObject.get);
  useEffect(() => {
    const unsubscribe = globalObject.subscribe(setValue);
    return unsubscribe;
  }, []);
  return [value, globalObject.set];
})
  

Вы могли бы использовать библиотеку pub / sub, если вы не хотите внедрять ее самостоятельно, или если это превращается в большую часть проекта, вы могли бы использовать существующую глобальную библиотеку управления состоянием, такую как Redux или MobX

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

1. отсутствует children при деструктурировании реквизитов функции…

2. В итоге я написал AllProviders компонент, как вы и предлагали, поместив в него свою «лестницу в ад» провайдеров. Думаю, я мог бы провести рефакторинг и объединить несколько, но я выступаю за разделение архитектуры контрактов 🙂 Возможно, мне следует написать useGlobalState хук и опубликовать его…