如何解决React Context Provider中的过时关闭
我有一个React Native应用程序,我想在其中创建一种启用/禁用来自应用程序流中多个位置的通知的方法。启用通知还涉及保留一些取消订阅处理程序,这些处理程序应在禁用期间进行适当的清理。它还需要写入/读取react-redux存储。
为此,我创建了一个上下文,以与应用程序中的任意组件共享功能。您可以在下面看到简化版本。
我似乎遇到了过时的关闭问题,我不知道如何正确解决。我添加了注释,以解释代码中的内联问题。
如果这是错误的方法,那么我也将对如何更好地解决指定的用例产生深刻的见解。
interface NotificationContextProps {
enableNotifications: (promptForPermission: boolean) => Promise<void>;
disableNotifications: () => void;
}
const SharedNotificationContext = React.createContext<NotificationContextProps>({
enableNotifications: async () => {/**/},disableNotifications: () => {/**/},});
export const SharedNotificationProvider: FunctionComponent = ({children}) => {
const notificationListenerUnsub = useRef(() => {/**/});
const tokenRefreshListenerUnsub = useRef(() => {/**/});
// PROBLEM: Would have liked to use `useState` here instead of useRef
// but the value change never reflected in the functions using the value.
// Probably same stale closure issue as the other PROBLEM.
const isEnabled = useRef(false);
// PROBLEM: When udating the state with the `registerToken` function,this gets
// updated correctly. When I place a `console.log(existingDeviceToken);` below this,// I can see that the change propagates here correctly.
// BUT the `registerDeviceToken` function which does a check if the token changed,// (called by `enableNotifications` which is called on every app state change)
// still uses the "old" `existingDeviceToken value` for the comparison
const existingDeviceToken = useSelector((state: any) => state.firebase.token);
const dispatch = useDispatch();
const registerToken = (token: string) => dispatch({ type: FirebaseEvents.TOKEN,payload: token });
const registerDeviceToken = async () => {
const deviceToken = await getFirebaseToken();
// PROBLEM: Even though the `existingDeviceToken` changed,// it's still using the old value.
if (deviceToken && deviceToken !== existingDeviceToken) {
registerToken(deviceToken);
}
};
const enableNotifications = async (promptForPermission: boolean) => {
const permissionStatus = await getPushPermission(promptForPermission);
if (permissionStatus.hasPermission && !isEnabled.current) {
// Has Push Permissions & Listeners have not yet been setup
cleanupNotificationHandlers();
await registerDeviceToken();
await updateNotificationListeners();
isEnabled.current = true;
} else if (permissionStatus.hasPermission && isEnabled.current) {
// Has Push Permissions but Listeners already exist
await registerDeviceToken();
} else if (permissionStatus.authStatus === messaging.AuthorizationStatus.DENIED) {
// Push Permissions were declined or removed
disableNotifications(true);
}
};
const disableNotifications = async (permissionsDeclined: boolean) => {
await removeFirebaseToken();
registerToken("");
cleanupNotificationHandlers();
isEnabled.current = false;
}
const updateNotificationListeners = async () => {
// Create listener and store the unsub function (simplified for demo)
notificationListenerUnsub.current = () => {};
tokenRefreshListenerUnsub.current = () => {};
};
const cleanupNotificationHandlers = () => {
notificationListenerUnsub.current();
notificationListenerUnsub.current = () => {/**/};
tokenRefreshListenerUnsub.current();
tokenRefreshListenerUnsub.current = () => {/**/};
isEnabled.current = false;
};
return (
<SharedNotificationContext.Provider value={{
enableNotifications,disableNotifications
}}>
{children}
</SharedNotificationContext.Provider>
);
};
export const useNotifications = () => {
return React.useContext(SharedNotificationContext);
};
我已经厌倦了用useCallback
包裹函数,像这样:
const registerDeviceToken = useCallback(async () => {/**/},[existingDeviceToken]);
const enableNotifications = useCallback(async (promptForPermission: boolean) => {/**/},[registerDeviceToken]);
但这没有帮助。
有效的方法是将existingDeviceToken
打包到useRef
中,然后使用
useEffect(() => {existingDeviceTokenRef.current = existingDeviceToken},[existingDeviceToken]);
进行更新。但这确实只是一种解决方法,而不是真正的解决方案。
正如您可能已经收集到的那样,React对我来说仍然是新手,因此我也不是100%知道如何以尽量减少更改价值道具的方式执行此操作,以使提供商不会导致应用重新渲染。
解决方法
我怀疑您在使用enableNotifications
或disableNotifications
时在效果上缺少依赖性,但是使用任何一种方法在您的问题中都没有代码。
这是仅当现有DeviceToken更改时才创建函数的代码。
export const SharedNotificationProvider = ({
children,}) => {
const [,setState] = useState({
notificationListenerUnsub: (x) => x,tokenRefreshListenerUnsub: (x) => x,});
//best to keep this in a ref so you don't create
// a needless dependency
const isEnabled = useRef(false);
const existingDeviceToken = useSelector(
(state) => state.firebase.token
);
const dispatch = useDispatch();
const registerToken = useCallback(
(token) =>
dispatch({
type: FirebaseEvents.TOKEN,payload: token,}),[dispatch]
);
//all these functions are created on mount and only
// change when existingDeviceToken changes
const registerDeviceToken = useCallback(async () => {
const deviceToken = await getFirebaseToken();
if (
deviceToken &&
deviceToken !== existingDeviceToken
) {
registerToken(deviceToken);
}
},[existingDeviceToken,registerToken]);
const cleanupNotificationHandlers = useCallback(
() =>
setState((state) => {
state.notificationListenerUnsub();
state.tokenRefreshListenerUnsub();
return {
notificationListenerUnsub: () => {
/**/
},tokenRefreshListenerUnsub: () => {
/**/
},isEnabled: false,};
}),[] //no deps,this method never changes after mount
);
const disableNotifications = useCallback(
async (permissionsDeclined) => {
await removeFirebaseToken();
registerToken('');
cleanupNotificationHandlers();
setState((state) => ({ ...state,isEnabled: false }));
},[cleanupNotificationHandlers,registerToken]
);
const updateNotificationListeners = useCallback(
async () =>
// Create listener and store the unsub function (simplified for demo)
setState((state) => ({
...state,notificationListenerUnsub: () => {},tokenRefreshListenerUnsub: () => {},})),[] //no dependencies,function never changes
);
const enableNotifications = useCallback(
async (promptForPermission) => {
const permissionStatus = await getPushPermission(
promptForPermission
);
if (
permissionStatus.hasPermission &&
!isEnabled.current
) {
// Has Push Permissions & Listeners have not yet been setup
cleanupNotificationHandlers();
await registerDeviceToken();
await updateNotificationListeners();
isEnabled.current = true;
} else if (
permissionStatus.hasPermission &&
isEnabled.current
) {
// Has Push Permissions but Listeners already exist
await registerDeviceToken();
} else if (
permissionStatus.authStatus ===
messaging.AuthorizationStatus.DENIED
) {
// Push Permissions were declined or removed
disableNotifications(true);
}
},[
cleanupNotificationHandlers,disableNotifications,registerDeviceToken,updateNotificationListeners,]
);
return (
<SharedNotificationContext.Provider
value={{
enableNotifications,}}
>
{children}
</SharedNotificationContext.Provider>
);
};
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。