Files
height_manager/client/hooks/useSafeRouter.ts

153 lines
5.2 KiB
TypeScript
Raw Normal View History

/**
* Hook - useRouter useLocalSearchParams
*
* Hook
* - useSafeRouter: 代替 useRouter push/replace/navigate/setParams
* - useSafeSearchParams: 代替 useLocalSearchParams
*
*
* 1. URI - useLocalSearchParams router.push
* %
* 2. - URL stringNumber/Boolean
* 3. - URL search params
*
*
* Payload JSON Base64
*
*
*
* 1. %&=Emoji
* 2. NumberBoolean String
* 3.
* 4. iOSAndroidWeb
*
* 使
* ```tsx
* // 发送端 - 使用 useSafeRouter 代替 useRouter
* const router = useSafeRouter();
* router.push('/detail', { id: 123, uri: 'file:///path/%40test.mp3' });
* router.replace('/home', { tab: 'settings' });
* router.navigate('/profile', { userId: 456 });
* router.back();
* if (router.canGoBack()) { ... }
* router.setParams({ updated: true });
*
* // 接收端 - 使用 useSafeSearchParams 代替 useLocalSearchParams
* const { id, uri } = useSafeSearchParams<{ id: number; uri: string }>();
* ```
*/
import { useMemo } from 'react';
import { useRouter as useExpoRouter, useLocalSearchParams as useExpoParams } from 'expo-router';
import { Base64 } from 'js-base64';
const PAYLOAD_KEY = '__safeRouterPayload__';
const LOG_PREFIX = '[SafeRouter]';
const getCurrentParams = (rawParams: Record<string, string | string[]>): Record<string, unknown> => {
const payload = rawParams[PAYLOAD_KEY];
if (payload && typeof payload === 'string') {
const decoded = deserializeParams<Record<string, unknown>>(payload);
if (decoded && Object.keys(decoded).length > 0) {
return decoded;
}
}
const { [PAYLOAD_KEY]: _, ...rest } = rawParams;
return rest as Record<string, unknown>;
};
const serializeParams = (params: Record<string, unknown>): string => {
try {
const jsonStr = JSON.stringify(params);
return Base64.encode(jsonStr);
} catch (error) {
console.error(LOG_PREFIX, 'serialize failed:', error instanceof Error ? error.message : 'Unknown error');
return '';
}
};
const deserializeParams = <T = Record<string, unknown>>(
payload: string | string[] | undefined
): T | null => {
if (!payload || typeof payload !== 'string') {
return null;
}
try {
const jsonStr = Base64.decode(payload);
return JSON.parse(jsonStr) as T;
} catch (error) {
console.error(LOG_PREFIX, 'deserialize failed:', error instanceof Error ? error.message : 'Unknown error');
return null;
}
};
/**
* Hook useRouter
* @returns useRouter
* - push(pathname, params) -
* - replace(pathname, params) -
* - navigate(pathname, params) - push
* - setParams(params) -
*/
export function useSafeRouter() {
const router = useExpoRouter();
const rawParams = useExpoParams<Record<string, string | string[]>>();
const push = (pathname: string, params: Record<string, unknown> = {}) => {
const encodedPayload = serializeParams(params);
router.push({
pathname: pathname as `/${string}`,
params: { [PAYLOAD_KEY]: encodedPayload },
});
};
const replace = (pathname: string, params: Record<string, unknown> = {}) => {
const encodedPayload = serializeParams(params);
router.replace({
pathname: pathname as `/${string}`,
params: { [PAYLOAD_KEY]: encodedPayload },
});
};
const navigate = (pathname: string, params: Record<string, unknown> = {}) => {
const encodedPayload = serializeParams(params);
router.navigate({
pathname: pathname as `/${string}`,
params: { [PAYLOAD_KEY]: encodedPayload },
});
};
const setParams = (params: Record<string, unknown>) => {
const currentParams = getCurrentParams(rawParams);
const mergedParams = { ...currentParams, ...params };
const encodedPayload = serializeParams(mergedParams);
router.setParams({ [PAYLOAD_KEY]: encodedPayload });
};
return {
...router,
push,
replace,
navigate,
setParams,
};
}
/**
* Hook useLocalSearchParams
*
* 1. useSafeRouter - Payload
* 2. 访- 退
* @returns
*/
export function useSafeSearchParams<T = Record<string, unknown>>(): T {
const rawParams = useExpoParams<Record<string, string | string[]>>();
const decodedParams = useMemo(() => {
return getCurrentParams(rawParams) as T;
}, [rawParams]);
return decodedParams;
}