如何在没有提供程序的情况下使用上下文更新每个React组件?

时间:2020-08-19 18:23:03

标签: javascript reactjs react-hooks 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之外,还有什么选择?确实,这是真正的问题;如何使用此挂钩(或HOC)更新组件? useState是唯一的方法吗?怎么样?使用某些事件发射器?

1 个答案:

答案 0 :(得分:2)

我认为在这里使用上下文确实有意义,但是您将需要使用提供程序,因为这是上下文工作方式的核心部分。渲染提供程序使树后面的组件可以使用一个值,而使用新值进行渲染则促使消费者重新渲染。如果没有提供程序,则您至少可以访问默认值(这是您的代码中的值),但是默认值永远不会改变,因此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>)。诚然,这只是“视而不见”的解决方案,但这是我真正倾向于关心的所有情况。

如果您确实想完全避免使用提供程序,那么您也需要远离使用上下文。可能可以设置一个也是事件发射器的全局对象,然后让想要访问该对象的任何组件订阅事件。

以下代码不完整,但也许是这样的:

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