Menu

JavaScriptでURLとクエリ文字列を扱う(URLSearchParams)

正規表現に頼らずに、URLオブジェクトとURLSearchParamsでURLをパース・組み立て・編集する方法を、エッジケースも含めて解説します。

このページのコードはエディタで実行できます — 編集してすぐに結果を確認できます。

URLを文字列操作でパースするのはもうやめよう

URL APIが登場する前は、みんなsplit('?')や正規表現、そして祈りを駆使してURLを切り刻んでいました。値に&=、スペース、非ASCII文字が混ざらない限りは、それでもなんとか動いていたんです。でも一度そういう文字が入れば、途端に壊れる。ブラウザにもNode.jsにも、ちゃんとしたパーサーが最初から用意されています。素直にそれを使いましょう。

呼び出し一発で、URL の各パーツがすでにバラされて、しかもデコードまで済んだ状態で手に入ります。不正な URL を渡すとコンストラクタは TypeError を投げますが、これはむしろ望ましい挙動です。おかしな URL は黙って通すよりも、その場で派手に落ちてくれたほうが、後続の処理でゴミを垂れ流すより安全ですよね。

クエリパラメータを取得する

URL オブジェクトには必ず .searchParams プロパティがあり、これが URLSearchParams オブジェクトです。クエリ文字列の読み書きはすべてこいつに任せられます。

押さえておきたいポイントがいくつかあります。

  • 値はデコード済みで返ってきます。?name=Ada%20Lovelace なら "Ada Lovelace" が取得できます。
  • すべて文字列として扱われます。"2"2 ではありません。数値として使いたい場合は Number() で変換しましょう。
  • 同じキーが複数あっても問題ありません。get は最初にマッチしたものだけを返し、getAll はすべての値を配列で返します。
  • 存在しないキーは undefined ではなく null を返します。なので ?? "default" との組み合わせが便利です。

クエリ文字列を組み立てる

URLSearchParams を使えば、クエリ文字列をゼロから組み立てられます。エスケープを手動でやる必要も、& で自分でつなぐ必要もありません。

オブジェクトから生成することもできます。[キー, 値] のペアを返すイテラブルならなんでも使えますし、普通のオブジェクトでもOKです。

setappend の違い: set は既存の値を上書きします。append は別の値を追加します。同じキーが複数回登場し得る場合(タグやフィルタなど)は append、単一の値しか持たないパラメータには set を使い分けましょう。

URL を書き換える

URL はライブオブジェクトなので、searchParams をいじれば .search.href も自動的に更新されます。

既存のURLにクエリパラメータを追加するなら、これが一番スマートな書き方です。「URLにすでに?が付いてるかな?」とチェックしたり、区切り文字として&?のどちらを使うか悩む必要もありません。

URLの他の部分も、同じ要領で書き換えられます。

パラメータをループで取り出す

URLSearchParams はイテラブルなので、for...of で回すと [キー, 値] のペアが順番に取れます。配列などと同じように keys()values()entries() といったヘルパーも用意されています。

キーの重複はそのまま保持される点に注目してください。tag = web の次に tag = beginner が別エントリとして出てきます。これはクエリ文字列の実際の中身に忠実な挙動です。

デバッグ用にサクッとプレーンなオブジェクトで中身を確認したいときは Object.fromEntries が便利です。ただし重複キーは潰れてしまい、最後の値だけが残る点には注意してください。

デバッグ目的なら問題ありませんが、同じキーが複数回登場しうる場合は正しく動作しません。

相対URLにはベースが必要

new URL("/search?q=js") を単独で呼ぶとエラーになります。相対パスだけでは有効なURLにならないからです。第2引数にベースURLを渡しましょう。

この解決ルールは、ブラウザが <a href> を解釈するときとまったく同じです。先頭が / ならホストからの絶対パス、スラッシュなしなら現在のパスからの相対、.. は 1 階層上に上がります。設定値のベース URL から API の URL を組み立てるときにかなり重宝します。

ブラウザ上では、window.location.href がそのまま現在ページの URL を解析するためのベースとして使えます。

const u = new URL(window.location.href);
const page = u.searchParams.get("page") ?? "1";

不正な URL を扱う

URL コンストラクタは、フォーマットが崩れた入力を渡すと例外を投げます。これ自体はありがたい仕様なのですが、ユーザーが入力した文字列や外部システムから受け取った値をパースするときは、try/catch で囲む必要があります。

モダンな実行環境では URL.canParse(input) も使えます。これは真偽値を返すチェック用のメソッドで、URL が有効かどうか確かめたいだけなら try/catch でわざわざ囲む必要がありません。

ちょっとした実用サンプル

ここまでの内容をまとめて、URL から現在のフィルタを読み取り、値を書き換えて、遷移先となる新しい URL を組み立ててみましょう。

null を渡すとそのパラメータが削除されます。それ以外の値を渡すと、設定もしくは上書きになります。フィルター UI やページネーション、ディープリンクを作るときには、形は違えど結局このパターンを書くことになります。

まとめ

  • new URL(string) は URL を意味のあるパーツに分解してくれます。不正な文字列を渡すと例外になります。
  • url.searchParamsURLSearchParams なので、getgetAllsetappenddeletehas をそのまま使えます。
  • エンコードは自動で処理されます。自分で文字列を組み立てているとき以外、encodeURIComponent の出番はありません。
  • 相対パスを解決したいときは、第2引数にベース URL を渡しましょう。
  • 信頼できない入力のバリデーションには URL.canParse(または try/catch)が便利です。

.split('?') で URL を分割したくなったときや、正規表現でクエリパラメータを抜き出したくなったときは、代わりにこれらの API を使ってください。コードは短く、挙動は正確で、しかもランタイムに最初から入っています。

よくある質問

JavaScriptでURLをパースするには?

文字列をURLコンストラクタに渡すだけです。const u = new URL('https://example.com/path?x=1')のように書くと、protocolhostpathnamesearchhash、そしてsearchParamsといったプロパティにアクセスできます。不正なURLを渡すと例外を投げるので、外部から受け取った文字列をパースする場合はtry/catchで囲んでおくと安心です。

クエリ文字列のパラメータを取得するには?

url.searchParams.get('name')を使います。デコード済みの値が返ってきて、該当するパラメータが無ければnullになります。?tag=a&tag=bのように同じキーが複数回出てくるケースでは、searchParams.getAll('tag')で配列としてまとめて取得できます。

URLとURLSearchParamsの違いは?

URLはプロトコル、ホスト、パス、クエリ、ハッシュといったURL全体を扱うオブジェクトです。一方、URLSearchParamsはクエリ文字列の部分だけを担当し、a=1&b=2のような文字列を単独で組み立てたり解析したりできます。URLインスタンスには.searchParamsプロパティが用意されていて、これがそのURLに紐づくURLSearchParamsとして機能します。

クエリパラメータを自分でエンコードする必要はある?

基本的には不要です。URLSearchParamssetappendを呼んだり、文字列として取り出したりするときに、キーと値は自動でエンコードされます。スペース、&=、Unicode文字もきちんと処理してくれます。encodeURIComponentを自分で呼ぶのは、どうしても文字列を手組みする場合だけで、普段はそもそも手組みしないのが正解です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める