パスを型で確かめる
リンクのパスは文字列なので、書き間違えてもそのままでは気づけません。このページでは、パターンやparamの誤りを型エラーにする方法と、その検査を自分のコードや@k8ordo/stateで使う方法を説明します。
このページの内容
paramは設定なしで確かめられる
hrefやnavigateTo、useParamsは、渡したパターンの文字列からparamの型を作ります。そのため、何も設定しなくても、paramの渡し忘れや名前の書き間違いは型エラーになります。
href('/products/:id', { id: '42' });
href('/products/:id');
idが無いので型エラーになる
href('/products/:id', { productId: '42' });
productIdはパターンに無いので型エラーになるパターンをルート表と照らし合わせる
paramとは違って、パターンそのものの書き間違いは、何も設定しないと通ってしまいます。ルート表をRegisterに登録すると、表に無いパターンも型エラーになります。
src/k8ordo-router.d.tsimport type { routes } from './routes';
declare module '@k8ordo/router' {
interface Register {
routes: typeof routes;
}
}href('/products/:id', { id: '42' });
href('/prodcuts/:id', { id: '42' });
表に無いパターンなので型エラーになる登録の前は、/で始まる文字列ならどれでも通ります。登録したあとは、hrefとnavigateToはリンク先にできるパターンだけを、useParamsは表のパターンだけを受け付けます。useMatchとmatchPathは、表のパターンと、その後ろに/*を付けたものを受け付けます。
登録はアプリの中で1度だけ行います。ライブラリで登録すると、そのライブラリを使うすべてのアプリに、自分のルート表を押し付けることになるからです。
メモ
@k8ordo/staticと@k8ordo/serverでは、この登録が.k8ordo/register.gen.tsに生成されます。自分で書くと同じ登録が2つになるので、書かないでください。
自分のコードでパターンの型を使う
ルート表から作った型は、自分のコードでも使えます。たとえば、ナビゲーションに並べる行き先を、表にあるページに限りたいときです。
src/links.tsimport type { RegisteredNavigablePattern } from '@k8ordo/router';
export type SitePath = Exclude<
Extract<RegisteredNavigablePattern, `/:locale${string}`>,
`/:locale${string}:${string}`
>;これはこのサイトのSitePathです。リンク先にできるパターンのうち、/:localeで始まり、ロケールのほかにparamを持たないものに絞っています。ナビゲーションのデータに表に無いページを書くと、型エラーになります。
登録した表から作られる型は、次の4つです。
RegisteredPattern:表のすべてのページのパターン(/*のパターンも含む)RegisteredNavigablePattern:リンク先にできるパターン(/*を含むものを除く)RegisteredParams<P>:パターンPへのリンクが受け取るparamsRegisteredPageParams<P>:パターンPのページが受け取るparams
登録の前は、はじめの2つは/で始まる任意の文字列になります。
メモ
フレームワークの下でparamsSchemaを書いたページでは、リンクもページも、paramをスキーマが作った型で扱います。詳しくは「フレームワークの下で使う」で説明します。
@k8ordo/stateのリンクも同じ表で確かめる
@k8ordo/stateのRegisterにも、同じ1行を書けます。こうすると、@k8ordo/stateのhrefに渡すパスも、このルーターと同じルート表で確かめられます。
src/k8ordo.d.tsimport type { routes } from './routes';
declare module '@k8ordo/router' {
interface Register {
routes: typeof routes;
}
}
declare module '@k8ordo/state' {
interface Register {
routes: typeof routes;
}
}listState.href('/products', { q: 'lamp' }); // '/products?q=lamp'
listState.href('/prodcuts', { q: 'lamp' });
表のどのパターンにも合わないので型エラーになる@k8ordo/stateのhrefが受け取るのは、パターンではなく実際のパスです。/products/42のように、paramの位置にも値を書きます。このパスがどれかのパターンに合うかを、区間ごとに確かめています。
パスを受け取る型を自分で書く
@k8ordo/stateが使っている検査は、NavigablePathという型です。ほかにパスを受け取るものがあれば、同じ型で確かめられます。
import type { NavigablePath } from '@k8ordo/router';
type A = NavigablePath<typeof routes, '/products/42'>;
// '/products/42'
type B = NavigablePath<typeof routes, '/products/42/reviews'>;
// never
type C = NavigablePath<typeof routes, '/products/'>;
// neverNavigablePathは、パスがリンク先にできるパターンのどれかに合えばそのパスを、どれにも合わなければneverを返します。:idのようなparamの位置には、空でない1区間ならどれでも入ります。
末尾にスラッシュのあるパスは受け付けません。照合では/products/も/productsと同じページになりますが、リンクに書くパスは1通りの書き方にそろえるためです。
表のすべてのパスを並べた型を作って比べないのは、/:localeのようなページがあると、その型が/${string}になってしまうからです。/${string}は、どんなパスでも受け付けてしまいます。