1+ import type { FC } from "react" ;
12import { createObserver } from "./createObserver" ;
23import type { AnyFunction , StringRecord } from "./types" ;
34
4- interface Route < Handler extends AnyFunction > {
5+ interface Route < Handler extends FC < unknown > > {
56 regex : RegExp ;
67 paramNames : string [ ] ;
78 handler : Handler ;
@@ -12,11 +13,11 @@ type QueryPayload = Record<string, string | number | undefined>;
1213
1314export type RouterInstance < T extends AnyFunction > = InstanceType < typeof Router < T > > ;
1415
15- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16- export class Router < Handler extends ( ...args : any [ ] ) => any > {
16+ export class Router < Handler extends FC < unknown > > {
1717 readonly #routes: Map < string , Route < Handler > > ;
1818 readonly #observer = createObserver ( ) ;
1919 readonly #baseUrl;
20+ #ssrQuery: StringRecord = { } ;
2021
2122 #route: null | ( Route < Handler > & { params : StringRecord ; path : string } ) ;
2223
@@ -25,37 +26,57 @@ export class Router<Handler extends (...args: any[]) => any> {
2526 this . #route = null ;
2627 this . #baseUrl = baseUrl . replace ( / \/ $ / , "" ) ;
2728
28- window . addEventListener ( "popstate" , ( ) => {
29- this . #route = this . #findRoute( ) ;
30- this . #observer. notify ( ) ;
31- } ) ;
32-
33- document . addEventListener ( "click" , ( e ) => {
34- const target = e . target as HTMLElement ;
35- if ( ! target ?. closest ( "[data-link]" ) ) {
36- return ;
37- }
38- e . preventDefault ( ) ;
39- const url = target . getAttribute ( "href" ) ?? target . closest ( "[data-link]" ) ?. getAttribute ( "href" ) ;
40- if ( url ) {
41- this . push ( url ) ;
42- }
43- } ) ;
29+ if ( typeof window !== "undefined" ) {
30+ window . addEventListener ( "popstate" , ( ) => {
31+ this . #route = this . #findRoute( ) ;
32+ this . #observer. notify ( ) ;
33+ } ) ;
34+
35+ document . addEventListener ( "click" , ( e ) => {
36+ const target = e . target as HTMLElement ;
37+ if ( ! target ?. closest ( "[data-link]" ) ) {
38+ return ;
39+ }
40+ e . preventDefault ( ) ;
41+ const url = target . getAttribute ( "href" ) ?? target . closest ( "[data-link]" ) ?. getAttribute ( "href" ) ;
42+ if ( url ) {
43+ this . push ( url ) ;
44+ }
45+ } ) ;
46+ }
4447 }
4548
4649 get query ( ) : StringRecord {
47- return Router . parseQuery ( window . location . search ) ;
50+ if ( typeof window !== "undefined" ) {
51+ return Router . parseQuery ( window . location . search ) ;
52+ }
53+ return this . #ssrQuery;
4854 }
4955
5056 set query ( newQuery : QueryPayload ) {
51- const newUrl = Router . getUrl ( newQuery , this . #baseUrl) ;
52- this . push ( newUrl ) ;
57+ if ( typeof window !== "undefined" ) {
58+ const newUrl = Router . getUrl ( newQuery , this . #baseUrl) ;
59+ this . push ( newUrl ) ;
60+ } else {
61+ this . #ssrQuery = Object . entries ( newQuery ) . reduce ( ( acc , [ key , value ] ) => {
62+ if ( value !== null && value !== undefined && value !== "" ) {
63+ acc [ key ] = String ( value ) ;
64+ return acc ;
65+ }
66+ return acc ;
67+ } , { } as StringRecord ) ;
68+ }
5369 }
5470
5571 get params ( ) {
5672 return this . #route?. params ?? { } ;
5773 }
5874
75+ set params ( newParams : StringRecord ) {
76+ this . #route ??= { } as Route < Handler > & { params : StringRecord ; path : string } ;
77+ this . #route. params = newParams ;
78+ }
79+
5980 get route ( ) {
6081 return this . #route;
6182 }
@@ -66,7 +87,7 @@ export class Router<Handler extends (...args: any[]) => any> {
6687
6788 readonly subscribe = this . #observer. subscribe ;
6889
69- addRoute ( path : string , handler : Handler ) {
90+ addRoute < T > ( path : string , handler : FC < T > ) {
7091 // 경로 패턴을 정규식으로 변환
7192 const paramNames : string [ ] = [ ] ;
7293 const regexPath = path
@@ -76,17 +97,19 @@ export class Router<Handler extends (...args: any[]) => any> {
7697 } )
7798 . replace ( / \/ / g, "\\/" ) ;
7899
79- const regex = new RegExp ( `^${ this . #baseUrl} ${ regexPath } $` ) ;
100+ const regex =
101+ typeof window !== "undefined" ? new RegExp ( `^${ this . #baseUrl} ${ regexPath } $` ) : new RegExp ( `^${ regexPath } $` ) ;
80102
81103 this . #routes. set ( path , {
82104 regex,
83105 paramNames,
84- handler,
106+ handler : handler as Handler ,
85107 } ) ;
86108 }
87109
88110 #findRoute( url = window . location . pathname ) {
89- const { pathname } = new URL ( url , window . location . origin ) ;
111+ // pathname 만 쓰기 때문에 임시 값 설정
112+ const { pathname } = new URL ( url , "http://localhost" ) ;
90113 for ( const [ routePath , route ] of this . #routes) {
91114 const match = pathname . match ( route . regex ) ;
92115 if ( match ) {
@@ -125,8 +148,8 @@ export class Router<Handler extends (...args: any[]) => any> {
125148 }
126149 }
127150
128- start ( ) {
129- this . #route = this . #findRoute( ) ;
151+ start ( url ?: string ) {
152+ this . #route = this . #findRoute( url ) ;
130153 this . #observer. notify ( ) ;
131154 }
132155
0 commit comments