title | emoji | type | topics | published | ||
---|---|---|---|---|---|---|
なるべく型安全にvue-routerのqueryを格納する |
🛡️ |
tech |
|
true |
vue-router
では$route.query
からURLクエリパラメーターが取得できます。
import Vue from 'vue'
class Hoge extends Vue {
created() {
const query = this.$route.query // Dictionary<string | (string | null)[]>
}
}
このとき$route.query
はDictionary<string | (string | null)[]>
という型になっています。Dictionary
というのはvue-router
の用意している型で、いわゆる連想配列のことです。
実際のところ各キーに対して文字列か配列どちらかだけを値として許したいケースが多いかと思いますので、このままの型では使い勝手があまりよろしくありません。
そこで値を取得するときに適宜型キャストを行うわけですが、これをなるべく型安全に行う方法をパターン別にいくつか考えてみたいと思います。
以下のコードはtypescript v4.3.2で型チェックを行っています。古いTSでは型推論がうまくいかない可能性があるのでご了承ください。
値を取得する時、要件に応じて例外処理としていくつかパターンが考えられるかと思います。 例えば
- 無効な値が来た場合
undefined
を格納してそのまま進める - デフォルト値を用意して無効な値が来た場合はデフォルト値を格納する
- 無効な値が来た場合はエラーをはいて処理をやめる
などでしょうか。 今回はあまり複雑な例外処理は考えず、上記のパターンだけ考慮することにします。
import Vue from 'vue';
interface Params {
term?: string;
}
function queryToString(value: string | (string | null)[] | undefined): string | undefined {
return Array.isArray(value) ? value[0] || undefined : value;
}
class Hoge extends Vue {
params: Params = {};
created() {
this.getURLQuery();
}
getURLQuery() {
const query = this.$route.query;
this.params = {
term: queryToString(query.term),
};
}
}
一番シンプルなパターンかと思います
以下型変換のみ書き出します
function queryToString(value: string | (string | null)[] | undefined, defaultValue: string): string {
return Array.isArray(value) ? value[0] || defaultValue : value || defaultValue;
}
function queryToString(value: string | (string | null)[] | undefined): string {
if (typeof value === 'string' && value) {
return value;
}
throw new Error('Invalid query value');
}
これ以外にも数値として、配列として格納する場合も同じ要領で変換することができます。
Enumに含まれるかどうかはObject.valuesを使って調べることができます
import Vue from 'vue';
enum Genre {
rock = 'rock',
jazz = 'jazz',
progress = 'progress'
}
interface Params {
genre?: Genre;
}
function queryToEnum<T extends string>(value: string | (string | null)[] | undefined, enumObject: Record<string, T>): T | undefined {
const temp = Array.isArray(value) ? value[0] : value;
if ('string' === typeof temp) {
if (Object.values(enumObject).find((el): boolean => el === temp) !== undefined) {
return temp as T;
}
}
return undefined; // デフォルト値やエラーを返す場合はここを変える
}
class Hoge extends Vue {
params: Params = {};
created() {
this.getURLQuery();
}
getURLQuery() {
const query = this.$route.query;
this.params = {
genre: queryToEnum(query.genre, Genre)
};
}
}
複数選択のチェックボックスでの絞り込み条件などを格納する場合です。 このコードは
- Enumに含まれない値は破棄する
- 重複した値は除外する
- 該当するものがない場合は空の配列を格納する
という要件での例になります
import Vue from 'vue';
enum Genre {
rock = 'rock',
jazz = 'jazz',
progress = 'progress'
}
interface Params {
genre: Genre[];
}
function queryToEnumArray<T extends string>(value: string | (string | null)[] | undefined, enumObject: Record<string, T>): T[] {
const enumArray = Object.values(enumObject)
if (Array.isArray(value)) {
const result: T[] = [];
value.forEach((item): void => {
const test = enumArray.find((el): boolean => { return el === item; });
if (test && !result.includes(test)) result.push(test);
});
return result;
} else {
const test = enumArray.find((el): boolean => { return el === value; });
if (test) return [test];
return [];
}
}
class Hoge extends Vue {
params: Params = {
genre: []
};
created() {
this.getURLQuery();
}
getURLQuery() {
const query = this.$route.query;
this.params = {
genre: queryToEnumArray(query.genre, Genre)
};
}
}
場所によって要件が細かく変わってくる場合はいろいろと面倒ですが、プロジェクト全体でざっくり要件の方向性が揃えられるようであれば各々変換用のメソッドを用意して使うようにすることで、そこまで工数を増やさずに取り廻せると思います。