jQuery
【移行ガイド】jQueryからVanilla JS/モダンJSへ脱却する方法
カービー
#jQuery#JavaScript#移行#リファクタリング#モダンJS
jQueryからVanilla JavaScriptへの移行方法を解説。よく使うjQueryコードの書き換え例、段階的な移行手順、注意点を紹介します。
目次
なぜjQueryから移行するのか
移行のメリット
- パフォーマンス向上: ライブラリのオーバーヘッドを削減
- ファイルサイズ削減: 約30KB(gzip)の削減
- 依存関係の削減: セキュリティリスクを低減
- モダンJS習得: 将来的なスキルアップ
- フレームワーク移行の準備: React/Vue.jsへの足がかり
移行すべきでない場合
- WordPressテーマ/プラグイン開発
- jQueryプラグインに強く依存している
- IE11対応が必須
- チームにVanilla JSの知識がない
- 保守のみで機能追加の予定がない
移行の準備
ブラウザ対応の確認
ES6+の機能を使うため、対応ブラウザを確認します。
// ES6+の主な機能と対応状況
// アロー関数: IE非対応
// let/const: IE11で部分対応
// Promise: IE非対応
// fetch: IE非対応
// classList: IE10+
// ポリフィルが必要な場合
// <script src="https://polyfill.io/v3/polyfill.min.js"></script>
現状の把握
プロジェクト内でjQueryがどう使われているか調査します。
# jQueryの使用箇所を検索
grep -r "\$(" --include="*.js" ./src
grep -r "jQuery" --include="*.js" ./src
移行計画の策定
- 新規コード: Vanilla JSで書く
- 既存コード: 優先度をつけて段階的に移行
- テスト: 移行後の動作確認を徹底
基本的な書き換えパターン
DOM取得
// jQuery
$('#element') // → document.querySelector('#element')
$('.class') // → document.querySelectorAll('.class')
$('div') // → document.querySelectorAll('div')
$('#parent .child') // → document.querySelectorAll('#parent .child')
// 単一要素の取得
const el = document.querySelector('#element');
// 複数要素の取得(NodeListを配列に変換)
const items = [...document.querySelectorAll('.item')];
// または
const items = Array.from(document.querySelectorAll('.item'));
クラス操作
// jQuery
$('#el').addClass('active');
$('#el').removeClass('active');
$('#el').toggleClass('active');
$('#el').hasClass('active');
// Vanilla JS
const el = document.querySelector('#el');
el.classList.add('active');
el.classList.remove('active');
el.classList.toggle('active');
el.classList.contains('active');
// 複数クラスを一度に
el.classList.add('class1', 'class2', 'class3');
el.classList.remove('class1', 'class2');
属性操作
// jQuery
$('#el').attr('href');
$('#el').attr('href', 'https://example.com');
$('#el').removeAttr('disabled');
$('#el').data('id');
$('#el').data('id', 123);
// Vanilla JS
const el = document.querySelector('#el');
el.getAttribute('href');
el.setAttribute('href', 'https://example.com');
el.removeAttribute('disabled');
el.dataset.id; // data-id属性
el.dataset.id = 123;
CSS操作
// jQuery
$('#el').css('color');
$('#el').css('color', 'red');
$('#el').css({ color: 'red', fontSize: '16px' });
$('#el').show();
$('#el').hide();
// Vanilla JS
const el = document.querySelector('#el');
getComputedStyle(el).color;
el.style.color = 'red';
Object.assign(el.style, { color: 'red', fontSize: '16px' });
el.style.display = ''; // show
el.style.display = 'none'; // hide
コンテンツ操作
// jQuery
$('#el').text();
$('#el').text('新しいテキスト');
$('#el').html();
$('#el').html('<p>HTML</p>');
$('#el').val();
$('#el').val('新しい値');
// Vanilla JS
const el = document.querySelector('#el');
el.textContent;
el.textContent = '新しいテキスト';
el.innerHTML;
el.innerHTML = '<p>HTML</p>';
el.value;
el.value = '新しい値';
要素の追加・削除
// jQuery
$('#parent').append('<p>末尾</p>');
$('#parent').prepend('<p>先頭</p>');
$('#el').after('<p>後ろ</p>');
$('#el').before('<p>前</p>');
$('#el').remove();
$('#el').empty();
// Vanilla JS
const parent = document.querySelector('#parent');
parent.insertAdjacentHTML('beforeend', '<p>末尾</p>');
parent.insertAdjacentHTML('afterbegin', '<p>先頭</p>');
el.insertAdjacentHTML('afterend', '<p>後ろ</p>');
el.insertAdjacentHTML('beforebegin', '<p>前</p>');
el.remove();
el.innerHTML = '';
// 要素として追加する場合
const newEl = document.createElement('p');
newEl.textContent = '新しい要素';
parent.appendChild(newEl);
イベント処理
// jQuery
$('#btn').on('click', function(e) { });
$('#btn').off('click');
$(document).on('click', '.dynamic', function() { });
// Vanilla JS
const btn = document.querySelector('#btn');
btn.addEventListener('click', function(e) { });
btn.removeEventListener('click', handler);
// イベント委譲
document.addEventListener('click', function(e) {
if (e.target.matches('.dynamic')) {
// 処理
}
// または closest を使用
const target = e.target.closest('.dynamic');
if (target) {
// 処理
}
});
ドキュメント準備完了
// jQuery
$(document).ready(function() { });
$(function() { });
// Vanilla JS
document.addEventListener('DOMContentLoaded', function() { });
// または、scriptをbody末尾に配置すればDOMは準備完了している
ループ処理
// jQuery
$('.items').each(function(index, el) {
$(this).addClass('active');
});
// Vanilla JS
document.querySelectorAll('.items').forEach((el, index) => {
el.classList.add('active');
});
// for...of も使える
for (const el of document.querySelectorAll('.items')) {
el.classList.add('active');
}
Ajax
// jQuery
$.ajax({
url: '/api/data',
type: 'POST',
data: JSON.stringify({ key: 'value' }),
contentType: 'application/json',
success: function(data) { },
error: function(xhr, status, error) { }
});
// Vanilla JS (Fetch API)
fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
})
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();
})
.then(data => { })
.catch(error => { });
// async/await
async function postData() {
try {
const response = await fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
});
if (!response.ok) throw new Error('Network error');
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
ユーティリティ関数の作成
jQueryライクな便利関数を作成して、移行をスムーズにします。
基本ユーティリティ
// シンプルなセレクタ関数
const $ = (selector, context = document) => context.querySelector(selector);
const $$ = (selector, context = document) => [...context.querySelectorAll(selector)];
// 使用例
const header = $('#header');
const items = $$('.item');
const childLinks = $$('a', header);
拡張ユーティリティ
const DOM = {
// 要素取得
get: (selector) => document.querySelector(selector),
getAll: (selector) => [...document.querySelectorAll(selector)],
// クラス操作
addClass: (el, ...classes) => el.classList.add(...classes),
removeClass: (el, ...classes) => el.classList.remove(...classes),
toggleClass: (el, className) => el.classList.toggle(className),
hasClass: (el, className) => el.classList.contains(className),
// 表示/非表示
show: (el) => el.style.display = '',
hide: (el) => el.style.display = 'none',
toggle: (el) => el.style.display = el.style.display === 'none' ? '' : 'none',
// イベント
on: (el, event, handler) => el.addEventListener(event, handler),
off: (el, event, handler) => el.removeEventListener(event, handler),
// DOMContentLoaded
ready: (fn) => {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
};
// 使用例
DOM.ready(() => {
const btn = DOM.get('#btn');
DOM.on(btn, 'click', () => {
DOM.toggleClass(btn, 'active');
});
});
Fetchラッパー
const api = {
async get(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
},
async post(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
},
async put(url, data) {
const response = await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
},
async delete(url) {
const response = await fetch(url, { method: 'DELETE' });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
}
};
// 使用例
const users = await api.get('/api/users');
await api.post('/api/users', { name: '山田' });
段階的な移行手順
ステップ1: 新規コードはVanilla JSで書く
// 新しい機能はVanilla JSで実装
document.addEventListener('DOMContentLoaded', () => {
const newFeature = document.querySelector('#new-feature');
newFeature.addEventListener('click', handleClick);
});
ステップ2: 独立した機能から移行
依存関係の少ない小さな機能から始めます。
// Before: jQuery
$('#toggle-btn').on('click', function() {
$('#menu').slideToggle();
});
// After: Vanilla JS + CSS
document.querySelector('#toggle-btn').addEventListener('click', () => {
document.querySelector('#menu').classList.toggle('open');
});
// CSS
// #menu { max-height: 0; overflow: hidden; transition: max-height 0.3s; }
// #menu.open { max-height: 500px; }
ステップ3: jQueryプラグインの置き換え
// 例: スライダープラグイン
// Before: jQuery + Slick
$('.slider').slick({ ... });
// After: Vanilla JS ライブラリ (Swiper等)
import Swiper from 'swiper';
new Swiper('.slider', { ... });
ステップ4: 残りの移行とjQuery削除
すべてのjQueryコードを移行後、jQueryの読み込みを削除します。
<!-- 削除 -->
<!-- <script src="jquery.min.js"></script> -->
移行時の注意点
1. this の違い
// jQuery: this は DOM要素
$('.item').on('click', function() {
$(this).addClass('active'); // this = クリックされた要素
});
// Vanilla JS: アロー関数では this が異なる
$$('.item').forEach(item => {
item.addEventListener('click', function() {
this.classList.add('active'); // OK: 通常関数
});
});
// アロー関数を使う場合は e.target か e.currentTarget を使用
$$('.item').forEach(item => {
item.addEventListener('click', (e) => {
e.currentTarget.classList.add('active');
});
});
2. NodeList は配列ではない
// querySelectorAll は NodeList を返す
const items = document.querySelectorAll('.item');
// forEach は使える
items.forEach(item => { });
// map, filter は使えない(配列に変換が必要)
const filtered = [...items].filter(item => item.classList.contains('active'));
3. fetch のエラーハンドリング
// fetch は HTTPエラーでも reject しない
fetch('/api/data')
.then(response => {
// 404 や 500 でも then に来る
if (!response.ok) {
throw new Error('HTTP error: ' + response.status);
}
return response.json();
})
.catch(error => {
// ネットワークエラーまたは上で throw したエラー
console.error(error);
});
4. アニメーションはCSSを使う
// jQuery のアニメーションより CSS の方が高パフォーマンス
// CSS
// .fade { opacity: 0; transition: opacity 0.3s; }
// .fade.show { opacity: 1; }
// JavaScript
element.classList.add('show'); // フェードイン
element.classList.remove('show'); // フェードアウト
まとめ
移行チェックリスト
- 対応ブラウザの確認
- jQueryの使用箇所を洗い出し
- ユーティリティ関数の準備
- 新規コードはVanilla JSで書く
- 独立した機能から順次移行
- jQueryプラグインの代替を検討
- 十分なテストを実施
- jQueryの削除
主な書き換え早見表
| jQuery | Vanilla JS |
|---|---|
$() |
document.querySelector() |
.addClass() |
.classList.add() |
.on() |
.addEventListener() |
$.ajax() |
fetch() |
.show()/.hide() |
CSS + classList |
.animate() |
CSS Transitions |
焦らず段階的に移行を進めることで、安全にjQueryから脱却できます。