如何在没有提供者的情况下使用上下文更新每个 React 组件?
How can I update every React components using a context without provider?
给定这个简单的自定义挂钩
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
是唯一的方法吗?如何?使用一些事件发射器?
我认为在这里使用上下文确实有意义,但您需要使用提供程序,因为这是上下文工作方式的核心部分。呈现提供者使值可用于树中更远的组件,而使用新值呈现是促使消费者重新呈现的原因。如果没有提供者,你至少可以访问默认值(这是你的代码中的值),但默认值永远不会改变,所以 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
给定这个简单的自定义挂钩
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
是唯一的方法吗?如何?使用一些事件发射器?
我认为在这里使用上下文确实有意义,但您需要使用提供程序,因为这是上下文工作方式的核心部分。呈现提供者使值可用于树中更远的组件,而使用新值呈现是促使消费者重新呈现的原因。如果没有提供者,你至少可以访问默认值(这是你的代码中的值),但默认值永远不会改变,所以 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