既存システムでjQueryを見つけると、全部Vanilla JavaScriptへ置き換えたくなることがあります。現在のブラウザにはquerySelector、classList、fetchなどがあり、小さな処理ならjQueryなしで書けます。
ただし、長年動いているコードを一括変換すると、イベント、アニメーション、Ajax、プラグインなどの細かい違いで障害が起きます。
自分は「jQueryを消す」ことを目的にせず、新しい依存を増やさず、テストできる機能から少しずつ移行するのが安全だと考えています。
最初に依存箇所を調べる
次の文字列をプロジェクト全体から検索します。
jQuery(
$(
$.ajax
$.fn
.on(
.animate(さらに、jQuery UI、Slick、Select2などjQueryに依存するプラグインも一覧化します。本体コードを置換してもプラグインが残るなら、jQuery自体はまだ削除できません。
移行単位を決める
一行ずつ混在させるより、独立した機能単位で進めます。
- メニュー開閉
- タブ切替
- フォーム入力補助
- 一つのAjax検索
- 一つのモーダル
修正前に再現手順を残し、移行後も同じ操作で比較します。
セレクタ
// jQuery
const $button = $('.menu-button')
// Vanilla JS:最初の1件
const button = document.querySelector('.menu-button')
// Vanilla JS:複数
const buttons = document.querySelectorAll('.menu-button')querySelector()は見つからない場合nullを返すため、存在確認が必要です。
const button = document.querySelector('.menu-button')
if (!button) returnクラス操作
// jQuery
$('#menu').toggleClass('is-open')
// Vanilla JS
document.querySelector('#menu')?.classList.toggle('is-open')表示非表示はインラインstyleを直接変えるより、状態クラスへ寄せるとCSS側の責任が分かりやすくなります。
イベント
button?.addEventListener('click', handleClick)
button?.removeEventListener('click', handleClick)解除するには同じ関数参照が必要です。匿名関数をその場で二回書いても解除できません。
イベント委譲はclosest()で実装できます。
const list = document.querySelector('#itemList')
list?.addEventListener('click', (event) => {
const target = event.target
if (!(target instanceof Element)) return
const button = target.closest('.delete-button')
if (!button || !list.contains(button)) return
deleteItem(button.getAttribute('data-id'))
})値と属性
input.value
checkbox.checked
button.disabled = true
element.getAttribute('data-id')
element.dataset.idjQueryの.attr()と.prop()を機械的に一つのAPIへ変換せず、扱っているのがHTML属性か現在状態かを確認します。
DOM追加
const item = document.createElement('li')
item.className = 'user-item'
item.textContent = user.name
list.append(item)外部入力をinnerHTMLへ直接入れるとXSSの原因になります。文字列ならtextContentを使います。
Ajaxをfetchへ
async function loadUsers(signal) {
const response = await fetch('/api/users', {
signal,
credentials: 'same-origin'
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json()
}fetchは404や500で自動的にrejectされないため、response.okを確認します。jQuery AjaxとCookie、タイムアウト、データ変換の挙動も異なるため、単純な文字置換では不十分です。
キャンセルにはAbortControllerを使えます。
const controller = new AbortController()
loadUsers(controller.signal)
controller.abort()アニメーションは最後に移行する
.slideToggle()や複雑な.animate()は、表示状態やコールバックに依存していることがあります。先にセレクタや単純イベントを移行し、アニメーションは別タスクにしたほうが安全です。
CSS transitionで置き換える場合も、途中で連打したとき、display: noneとの組合せ、アニメーション終了イベントを確認します。
jQueryとVanillaをつなぐ境界
一時的な混在は問題ありません。ただし、同じ要素の状態を両方から管理しないようにします。
// 新しい機能の入口はVanilla JS
export function initMenu(root) {
// root内だけを管理
}新規コードはjQueryに依存させず、既存プラグイン周辺だけjQueryを残すという段階移行も現実的です。
移行後の確認
- 要素0件と複数件の場合
- 後から追加される要素
- イベントの二重登録
thisとevent.currentTarget- Ajaxの4xx、5xx、通信中断
- CookieとCSRFトークン
- HTML文字列の安全性
- 古いブラウザの対象範囲
- jQuery依存プラグイン
- キャッシュ削除後の本番ファイル
まとめ
jQueryからの移行で大切なのは、書き方を変えることより、既存処理の責任範囲を明確にすることです。
まず依存箇所を調べ、独立した小さな機能から移行し、同じ再現手順で確認します。最終的にjQueryが残っても、依存範囲が明確になれば保守性は上がります。無理に一度でゼロにする必要はありません。

