From 4f653f2fbc9f48f2d3069dd587907ebee667386c Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:30:35 +0900 Subject: enhance(frontend): typed nirax (#16309) * enhance(frontend): typed nirax * migrate router.replace * fix --- packages/frontend/src/lib/nirax.ts | 138 +++++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 6 deletions(-) (limited to 'packages/frontend/src/lib') diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts index a166df9eb0..70db47e24e 100644 --- a/packages/frontend/src/lib/nirax.ts +++ b/packages/frontend/src/lib/nirax.ts @@ -58,7 +58,7 @@ export type RouterEvents = { beforeFullPath: string; fullPath: string; route: RouteDef | null; - props: Map | null; + props: Map | null; }) => void; same: () => void; }; @@ -77,6 +77,110 @@ export type PathResolvedResult = { }; }; +//#region Path Types +type Prettify = { + [K in keyof T]: T[K] +} & {}; + +type RemoveNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +} & {}; + +type IsPathParameter = Part extends `${string}:${infer Parameter}` ? Parameter : never; + +type GetPathParamKeys = + Path extends `${infer A}/${infer B}` + ? IsPathParameter | GetPathParamKeys + : IsPathParameter; + +type GetPathParams = Prettify<{ + [Param in GetPathParamKeys as Param extends `${string}?` ? never : Param]: string; +} & { + [Param in GetPathParamKeys as Param extends `${infer OptionalParam}?` ? OptionalParam : never]?: string; +}>; + +type UnwrapReadOnly = T extends ReadonlyArray + ? U + : T extends Readonly + ? U + : T; + +type GetPaths = Def extends { path: infer Path } + ? Path extends string + ? Def extends { children: infer Children } + ? Children extends RouteDef[] + ? Path | `${Path}${FlattenAllPaths}` + : Path + : Path + : never + : never; + +type FlattenAllPaths = GetPaths; + +type GetSinglePathQuery> = RemoveNever< + Def extends { path: infer BasePath, children: infer Children } + ? BasePath extends string + ? Path extends `${BasePath}${infer ChildPath}` + ? Children extends RouteDef[] + ? ChildPath extends FlattenAllPaths + ? GetPathQuery + : Record + : never + : never + : never + : Def['path'] extends Path + ? Def extends { query: infer Query } + ? Query extends Record + ? UnwrapReadOnly<{ [Key in keyof Query]?: string; }> + : Record + : Record + : Record + >; + +type GetPathQuery> = GetSinglePathQuery; + +type RequiredIfNotEmpty> = T extends Record + ? { [Key in K]?: T } + : { [Key in K]: T }; + +type NotRequiredIfEmpty> = T extends Record ? T | undefined : T; + +type GetRouterOperationProps> = NotRequiredIfEmpty> & { + query?: GetPathQuery; + hash?: string; +}>; +//#endregion + +function buildFullPath(args: { + path: string; + params?: Record; + query?: Record; + hash?: string; +}) { + let fullPath = args.path; + + if (args.params) { + for (const key in args.params) { + const value = args.params[key]; + const replaceRegex = new RegExp(`:${key}(\\?)?`, 'g'); + fullPath = fullPath.replace(replaceRegex, value ? encodeURIComponent(value) : ''); + } + } + + if (args.query) { + const queryString = new URLSearchParams(args.query).toString(); + if (queryString) { + fullPath += '?' + queryString; + } + } + + if (args.hash) { + fullPath += '#' + encodeURIComponent(args.hash); + } + + return fullPath; +} + function parsePath(path: string): ParsedPath { const res = [] as ParsedPath; @@ -282,7 +386,7 @@ export class Nirax extends EventEmitter { } } - if (res.route.loginRequired && !this.isLoggedIn) { + if (res.route.loginRequired && !this.isLoggedIn && 'component' in res.route) { res.route.component = this.notFoundPageComponent; res.props.set('showLoginPopup', true); } @@ -310,14 +414,35 @@ export class Nirax extends EventEmitter { return this.currentFullPath; } - public push(fullPath: string, flag?: RouterFlag) { + public push

>(path: P, props?: GetRouterOperationProps, flag?: RouterFlag | null) { + const fullPath = buildFullPath({ + path, + params: props?.params, + query: props?.query, + hash: props?.hash, + }); + this.pushByPath(fullPath, flag); + } + + public replace

>(path: P, props?: GetRouterOperationProps) { + const fullPath = buildFullPath({ + path, + params: props?.params, + query: props?.query, + hash: props?.hash, + }); + this.replaceByPath(fullPath); + } + + /** どうしても必要な場合に使用(パスが確定している場合は `Nirax.push` を使用すること) */ + public pushByPath(fullPath: string, flag?: RouterFlag | null) { const beforeFullPath = this.currentFullPath; if (fullPath === beforeFullPath) { this.emit('same'); return; } if (this.navHook) { - const cancel = this.navHook(fullPath, flag); + const cancel = this.navHook(fullPath, flag ?? undefined); if (cancel) return; } const res = this.navigate(fullPath); @@ -333,14 +458,15 @@ export class Nirax extends EventEmitter { } } - public replace(fullPath: string) { + /** どうしても必要な場合に使用(パスが確定している場合は `Nirax.replace` を使用すること) */ + public replaceByPath(fullPath: string) { const res = this.navigate(fullPath); this.emit('replace', { fullPath: res._parsedRoute.fullPath, }); } - public useListener(event: E, listener: L) { + public useListener(event: E, listener: EventEmitter.EventListener) { this.addListener(event, listener); onBeforeUnmount(() => { -- cgit v1.2.3-freya