338
402

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

jQueryへの別れ:現代的な開発のための必須JavaScriptメソッド

338
Last updated at Posted at 2023-06-21

2026年7月に内容を更新しました。

現在のWeb APIとjQuery 4.0を前提に、コード例、イベント委譲、Fetch API、
アニメーションの説明を見直しました。

記事の主旨は変えず、jQueryからネイティブJavaScriptへ移行する際の
注意点が伝わりやすくなるように修正しています。

はじめに

私は長い間、レガシーコードを扱ってきました。その中で、jQueryを残すべきか、ネイティブJavaScriptへ移行すべきかという話題には何度も向き合っています。

jQueryは現在も有用なライブラリです。特に、既存システム、jQueryプラグインへ強く依存する画面、古いブラウザを対象とする環境では、無理に削除する必要はありません。

一方、新しく作るWebアプリケーションでは、jQueryが担っていた用途の多くを、次のWeb Platform APIで実装できます。

  • DOM API
  • addEventListener()
  • Fetch API
  • classList
  • CSS Transition
  • CustomEvent

これらはECMAScriptだけに含まれる機能ではありません。DOM Standard、Fetch Standard、HTML、CSSなど、複数のWeb標準によって提供されています。

この記事では、よく使われるjQueryの処理を、ネイティブJavaScriptへ置き換える方法をまとめます。

jQueryとネイティブJavaScriptでは、戻り値や対象件数が異なる場合があります。

特に、jQueryのセレクタは複数要素をまとめて扱いますが、querySelector()は最初の1件だけを返します。

対応表

用途 jQuery ネイティブJavaScript
最初の要素を取得 $('.item').first() document.querySelector('.item')
すべての要素を取得 $('.item') document.querySelectorAll('.item')
子孫要素を取得 $container.find('.item') container.querySelectorAll('.item')
イベント登録 .on('click', handler) .addEventListener('click', handler)
クラス追加 .addClass('active') .classList.add('active')
要素を隠す .hide() element.hidden = true
要素を表示 .show() element.hidden = false
HTTPリクエスト $.ajax() fetch()
要素作成 $('<div>') document.createElement('div')

要素を選択する

最初の1件を取得する

// jQuery
const $item = $('.item').first();

// JavaScript
const item = document.querySelector('.item');

querySelector()は、条件に一致する最初の要素を返します。見つからない場合はnullです。

const item = document.querySelector('.item');

if (!item) {
  return;
}

item.textContent = '更新しました';

すべての要素を取得する

// jQuery
const $items = $('.item');

// JavaScript
const items = document.querySelectorAll('.item');

querySelectorAll()は、静的なNodeListを返します。

document.querySelectorAll('.item').forEach((item) => {
  item.classList.add('active');
});

IDや属性で取得する

const itemById = document.getElementById('item');
const itemBySelector = document.querySelector('#item');

const externalLinks = document.querySelectorAll('a[target="_blank"]');

属性値は引用符で囲むと、安全で読みやすくなります。

特定の要素内を検索する

jQueryの.find()は、条件に一致するすべての子孫要素を返します。

同じ挙動に近づける場合、querySelectorAll()を使います。

// jQuery
const $container = $('.container');
const $items = $container.find('.item');

// JavaScript
const container = document.querySelector('.container');
const items = container?.querySelectorAll('.item') ?? [];

最初の1件だけが必要な場合は、querySelector()を使います。

const firstItem = container?.querySelector('.item') ?? null;

複数の要素へ処理を適用する

jQueryは、選択した複数要素へ同じ処理をまとめて適用できます。

// jQuery
$('.item').addClass('active');

ネイティブJavaScriptでは、対象を反復処理します。

document.querySelectorAll('.item').forEach((item) => {
  item.classList.add('active');
});

共通処理が多い場合は、関数として切り出せます。

const forEachElement = (selector, callback) => {
  document.querySelectorAll(selector).forEach(callback);
};

forEachElement('.item', (item) => {
  item.classList.add('active');
});

親要素や隣接要素へ移動する

// jQuery
const $nextItem = $('.item').first().next();
const $previousItem = $('.item').first().prev();
const $parent = $('.item').first().parent();

// JavaScript
const item = document.querySelector('.item');

const nextItem = item?.nextElementSibling ?? null;
const previousItem = item?.previousElementSibling ?? null;
const parent = item?.parentElement ?? null;

条件に一致する最も近い祖先を取得する場合は、closest()を使います。

const button = document.querySelector('.button');
const form = button?.closest('form') ?? null;

イベントを登録する

// jQuery
$('.button').on('click', handleClick);

// JavaScript
const button = document.querySelector('.button');
button?.addEventListener('click', handleClick);

複数の要素へ登録する場合は、反復処理します。

document.querySelectorAll('.button').forEach((button) => {
  button.addEventListener('click', handleClick);
});

リスナーを後で解除する場合は、同じ関数参照が必要です。

const handleClick = (event) => {
  console.log(event.currentTarget);
};

button?.addEventListener('click', handleClick);
button?.removeEventListener('click', handleClick);

イベント委譲を使う

動的に追加される要素へイベントを適用する場合は、親要素へリスナーを登録します。

// jQuery
$('.container').on('click', '.active', handleClick);

ネイティブJavaScriptでは、イベントバブリングとclosest()を使います。

const container = document.querySelector('.container');

container?.addEventListener('click', (event) => {
  const clickedElement =
    event.target instanceof Element ? event.target.closest('.active') : null;

  if (!clickedElement || !container.contains(clickedElement)) {
    return;
  }

  handleClick(event, clickedElement);
});

event.target.matches('.active')だけでは、.active内のspanやアイコンをクリックした場合に検出できません。

closest()を使うと、クリックされた要素から祖先をたどって対象を探せます。

カスタムイベントを作成する

データを伴う独自イベントには、CustomEventを使います。

const item = document.querySelector('.item');

const event = new CustomEvent('item:updated', {
  bubbles: true,
  detail: {
    itemId: 'example-item',
  },
});

item?.dispatchEvent(event);

受信側では、detailからデータを取得できます。

document.addEventListener('item:updated', (event) => {
  if (!(event instanceof CustomEvent)) {
    return;
  }

  console.log(event.detail.itemId);
});

dispatchEvent()は、登録済みのリスナーを同期的に実行します。

クラスを操作する

// jQuery
$('.item').addClass('active');
$('.item').removeClass('active');
$('.item').toggleClass('active');

// JavaScript
const item = document.querySelector('.item');

item?.classList.add('active');
item?.classList.remove('active');
item?.classList.toggle('active');

複数のクラスもまとめて指定できます。

item?.classList.add('active', 'highlighted');
item?.classList.remove('active', 'highlighted');

クラスを置き換える場合は、replace()を使います。

item?.classList.replace('inactive', 'active');

クラスの有無は、contains()で確認できます。

if (item?.classList.contains('active')) {
  console.log('activeです');
}

スタイルを変更する

最初の1件を変更する

const item = document.querySelector('.item');

if (item instanceof HTMLElement) {
  item.style.color = '#000';
  item.style.backgroundColor = 'red';
}

すべての要素を変更する

jQueryの.css()は、選択された全要素へ適用されます。

// jQuery
$('.item').css('color', '#000');

// JavaScript
document.querySelectorAll('.item').forEach((item) => {
  if (item instanceof HTMLElement) {
    item.style.color = '#000';
  }
});

複数のスタイルをまとめて変更する場合は、Object.assign()も使えます。

if (item instanceof HTMLElement) {
  Object.assign(item.style, {
    color: '#000',
    backgroundColor: 'red',
  });
}

style.cssTextは既存のインラインスタイルを上書きするため、通常は個別プロパティの変更を推奨します。

要素を非表示・表示する

display: blockを直接設定すると、本来flexgridだった要素の表示形式を壊す可能性があります。

単純な表示切り替えには、hidden属性が使えます。

const item = document.querySelector('.item');

if (item instanceof HTMLElement) {
  item.hidden = true;
  item.hidden = false;
}

CSSクラスで管理する方法もあります。

.is-hidden {
  display: none;
}
item?.classList.add('is-hidden');
item?.classList.remove('is-hidden');

アプリケーションでは、表示状態をCSSクラスへ寄せる方が管理しやすい場合が多いです。

フェードイン・フェードアウトする

アニメーションは、JavaScriptで細かく制御するより、CSSへ責務を寄せると扱いやすくなります。

.fade-item {
  opacity: 1;
  transition: opacity 200ms ease;
}

.fade-item.is-hidden {
  opacity: 0;
  pointer-events: none;
}
const setVisible = (element, isVisible) => {
  element.classList.toggle('is-hidden', !isVisible);
};

const item = document.querySelector('.fade-item');

if (item) {
  setVisible(item, false);
  setVisible(item, true);
}

完全にレイアウトから取り除きたい場合は、transitionend後にhiddenを設定します。

const fadeOut = (element) => {
  const handleTransitionEnd = () => {
    element.hidden = true;
    element.removeEventListener('transitionend', handleTransitionEnd);
  };

  element.addEventListener('transitionend', handleTransitionEnd);
  element.classList.add('is-hidden');
};

const fadeIn = (element) => {
  element.hidden = false;

  requestAnimationFrame(() => {
    element.classList.remove('is-hidden');
  });
};

transitionendは、対象プロパティの値が変化しない場合には発火しません。

実運用では、タイムアウトによるフォールバックやWeb Animations APIも検討してください。

DOMの準備完了を待つ

// jQuery
$(() => {
  initialize();
});

通常のスクリプトでは、DOMContentLoadedを使えます。

document.addEventListener('DOMContentLoaded', () => {
  initialize();
});

ただし、deferまたはtype="module"を指定したスクリプトは、DOMの解析後に実行されます。

<script type="module" src="/main.js"></script>

この場合、通常はそのまま初期化できます。

initialize();

動的に読み込まれるスクリプトにも対応する場合は、document.readyStateを確認します。

const runWhenReady = (callback) => {
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', callback, {
      once: true,
    });

    return;
  }

  callback();
};

runWhenReady(initialize);

HTTPリクエストを送信する

// jQuery
$.ajax({
  url: 'data.json',
})
  .done((data) => {
    console.log(data);
  })
  .fail((error) => {
    console.error(error);
  });

Fetch APIでは、HTTPエラーを自動的に例外として扱いません。

response.okを確認し、必要な形式へ変換します。

const loadData = async () => {
  const response = await fetch('data.json');

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  return response.json();
};

try {
  const data = await loadData();
  console.log(data);
} catch (error) {
  console.error(error);
}

キャンセルが必要な場合は、AbortControllerを使います。

const controller = new AbortController();

const loadData = async () => {
  const response = await fetch('data.json', {
    signal: controller.signal,
  });

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  return response.json();
};

controller.abort();

要素を作成する

// jQuery
const $element = $('<div>');

// JavaScript
const element = document.createElement('div');

属性やテキストも設定できます。

const element = document.createElement('div');

element.classList.add('item');
element.dataset.itemId = 'example-item';
element.textContent = '新しい要素';

DOMへ要素を追加する

// jQuery
$('.container').append($('<div>'));

// JavaScript
const container = document.querySelector('.container');
const element = document.createElement('div');

container?.append(element);

append()は、要素だけでなく文字列も追加できます。

container?.append('追加テキスト', element);

先頭へ追加する場合は、prepend()を使います。

container?.prepend(element);

テキストとHTMLを更新する

ユーザー入力や外部データを文字列として表示する場合は、textContentを使います。

// jQuery
$('.item').text('新しいテキスト');

// JavaScript
const item = document.querySelector('.item');

if (item) {
  item.textContent = '新しいテキスト';
}

HTMLを挿入する場合、innerHTMLへ未検証の文字列を渡すとXSSにつながる可能性があります。

// 外部入力をそのまま入れない
item.innerHTML = externalInput;

単純なDOM構築では、createElement()textContentを組み合わせます。

const link = document.createElement('a');

link.href = '/items/example';
link.textContent = externalInput;

item?.replaceChildren(link);

jQueryを残した方がよいケース

ネイティブJavaScriptへ置き換えられるからといって、必ず移行すべきとは限りません。

次の条件では、jQueryを残す判断も合理的です。

  • 安定稼働している既存システム
  • jQueryプラグインへ強く依存している
  • 移行コストに対する保守上の効果が小さい
  • 対象ブラウザや組み込みWebViewに制約がある
  • 段階的な改修が難しく、回帰リスクが高い

移行の目的は、jQueryを削除することではありません。

依存関係、バンドルサイズ、保守コスト、テスト容易性を改善できる場合に限り、価値があります。

段階的に移行するためのチェックリスト

  • 対象ブラウザとWebViewを確認する
  • jQueryプラグインへの依存を洗い出す
  • セレクタが1件か複数件かを確認する
  • イベント委譲の対象範囲を確認する
  • $.ajax()のエラー処理をfetch()へ移す
  • 表示制御をCSSクラスへ寄せる
  • DOM操作部分にテストを追加する
  • 小さな画面や機能単位で置き換える
  • jQueryとネイティブ実装の共存期間を設ける
  • 移行後に不要な依存を削除する

まとめ

現在のWeb Platformには、かつてjQueryが担っていた基本機能の多くが標準APIとして用意されています。

新規開発では、次の機能を中心にすれば、多くの処理を追加ライブラリなしで実装できます。

  • querySelector()querySelectorAll()
  • addEventListener()
  • classList
  • Fetch API
  • createElement()
  • append()replaceChildren()
  • CSS Transition
  • CustomEvent

一方、既存システムからjQueryを削除すること自体を目的にしてはいけません。

対象件数、戻り値、イベント委譲、表示状態、HTTPエラーなどの差異を確認しながら、保守上の効果が得られる範囲から段階的に移行するのが安全です。

参考資料

338
402
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
338
402

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?