DEV Community

Papa Burger
Papa Burger

Posted on

30 lines of Vue vs 150 lines of jQuery

When building reactive UI components, the difference in code efficiency between modern frameworks like Vue and traditional approaches using jQuery (or vanilla JavaScript) is nothing short of remarkable. In our experiments, we’ve seen that it can take roughly 5 times as much code to implement the same functionality in jQuery compared to Vue. And for a bit of extra fun, we’ll also compare how Tailwind CSS can dramatically reduce styling overhead compared to writing vanilla CSS by hand.

The Vue Advantage: Declarative and Concise
Vue’s magic lies in its declarative syntax and built-in reactivity. With just about 30 lines of code, you can create a fully reactive component.

Vue.js example:

<template>
    <div class="p-4">
        <input v-model="filter" class="p-2 rounded" />

        <div v-for="item in filteredItems" :key="item.id" class="bg-gray-100">
            <label>
                <input type="checkbox" v-model="item.checked" />
                <span :class="{ 'bg-blue-300': item.checked }">{{ item.name }}</span>
            </label>
        </div>

        <p class="p-4">Selected: {{ selectedCount }}</p>
    </div>
</template>

<script setup>
    const items = ref([
        { id: 1, name: 'Item 1', checked: false },
        { id: 2, name: 'Item 2', checked: false },
        { id: 3, name: 'Item 3', checked: false },
        { id: 4, name: 'Another Item', checked: false }
    ])

    const filter = ref('')

    const selectedCount = computed(() => items.value.filter(item => item.checked).length)
    const filteredItems = computed(() => items.value.filter(item => item.name.toLowerCase().includes(filter.value.toLowerCase())))
</script>
Enter fullscreen mode Exit fullscreen mode

jQuery version:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>jQuery Reactive List Example</title>
    <style>
      .container {
          font-family: sans-serif;
          padding: 20px;
          max-width: 400px;
          margin: auto;
      }
      .filter-input {
          width: 100%;
          padding: 8px;
          margin-bottom: 10px;
          box-sizing: border-box;
      }
      .item {
          margin: 5px 0;
      }
      .item label {
          display: flex;
          align-items: center;
      }
      .item input[type="checkbox"] {
          margin-right: 5px;
      }
      .checked {
          font-weight: bold;
          color: green;
      }
      .count {
          margin-top: 15px;
          font-size: 1.2rem;
      }
    </style>
  </head>
  <body>
    <div id="app" class="container"></div>
    <!-- Include jQuery from CDN -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
      $(document).ready(function() {
          // --- State Initialization ---
          var state = {
              items: [
                  { id: 1, name: 'Item 1', checked: false },
                  { id: 2, name: 'Item 2', checked: false },
                  { id: 3, name: 'Item 3', checked: false },
                  { id: 4, name: 'Another Item', checked: false }
              ],
              filter: ''
          };

          // --- Utility Functions ---
          function getFilteredItems() {
              var lowerFilter = state.filter.toLowerCase();
              return state.items.filter(function(item) {
                  return item.name.toLowerCase().indexOf(lowerFilter) !== -1;
              });
          }

          function getSelectedCount() {
              var count = 0;
              state.items.forEach(function(item) {
                  if (item.checked) {
                      count++;
                  }
              });
              return count;
          }

          // --- Rendering Function ---
          function render() {
              var $app = $('#app');
              $app.empty(); // Clear previous content

              // Render filter input
              var $filterInput = $('<input>', {
                  type: 'text',
                  placeholder: 'Filter items',
                  class: 'filter-input'
              })
              .val(state.filter)
              .on('input', function() {
                  state.filter = $(this).val();
                  render(); // Re-render the view when filter changes
              });
              $app.append($filterInput);

              // Render the list of items
              var filteredItems = getFilteredItems();
              $.each(filteredItems, function(index, item) {
                  var $itemDiv = $('<div>', { class: 'item' });
                  var $label = $('<label>');

                  // Create checkbox using jQuery
                  var $checkbox = $('<input>', {
                      type: 'checkbox'
                  })
                  .prop('checked', item.checked)
                  .on('change', function() {
                      // Update state: find and update the matching item
                      $.each(state.items, function(idx, stateItem) {
                          if (stateItem.id === item.id) {
                              stateItem.checked = $(this).is(':checked');
                          }
                      }.bind(this));
                      updateCount();
                      updateItemLabel($label, item);
                  });
                  $label.append($checkbox);

                  // Create span for item name
                  var $span = $('<span>').text(item.name);
                  if (item.checked) {
                      $span.addClass('checked');
                  }
                  $label.append($span);
                  $itemDiv.append($label);
                  $app.append($itemDiv);
              });

              // Render selected count display
              var $countP = $('<p>', {
                  id: 'selected-count',
                  class: 'count'
              }).text('Selected: ' + getSelectedCount());
              $app.append($countP);
          }

          // --- Helper Functions ---
          // Update only the selected count display
          function updateCount() {
              $('#selected-count').text('Selected: ' + getSelectedCount());
          }

          // Update the label styling when a checkbox changes
          function updateItemLabel($label, item) {
              var $span = $label.find('span');
              if (item.checked) {
                  $span.addClass('checked');
              } else {
                  $span.removeClass('checked');
              }
          }

          // --- Initial Render ---
          render();
      });
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Vanilla JS

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vanilla JS Reactive List Example</title>
    <style>
      .container {
          font-family: sans-serif;
          padding: 20px;
          max-width: 400px;
          margin: auto;
      }
      .filter-input {
          width: 100%;
          padding: 8px;
          margin-bottom: 10px;
          box-sizing: border-box;
      }
      .item {
          margin: 5px 0;
      }
      .item label {
          display: flex;
          align-items: center;
      }
      .item input[type="checkbox"] {
          margin-right: 5px;
      }
      .checked {
          font-weight: bold;
          color: green;
      }
      .count {
          margin-top: 15px;
          font-size: 1.2rem;
      }
    </style>
  </head>
  <body>
    <div id="app" class="container"></div>
    <script type="module">
      // --- State and Utility Functions ---
      const state = {
        items: [
          { id: 1, name: 'Item 1', checked: false },
          { id: 2, name: 'Item 2', checked: false },
          { id: 3, name: 'Item 3', checked: false },
          { id: 4, name: 'Another Item', checked: false }
        ],
        filter: ''
      }

      // Returns filtered items based on state.filter
      function getFilteredItems() {
        return state.items.filter(item =>
          item.name.toLowerCase().includes(state.filter.toLowerCase())
        )
      }

      // Returns the count of selected (checked) items
      function getSelectedCount() {
        return state.items.filter(item => item.checked).length
      }

      // --- Rendering Functions ---
      function render() {
        const app = document.getElementById('app')
        // Clear previous content so we can re-render
        app.innerHTML = ''

        // Create filter input
        const filterInput = document.createElement('input')
        filterInput.className = 'filter-input'
        filterInput.placeholder = 'Filter items'
        filterInput.value = state.filter
        filterInput.addEventListener('input', (e) => {
          state.filter = e.target.value
          render() // re-render the entire view when filter changes
        })
        app.appendChild(filterInput)

        // Render each item
        const filteredItems = getFilteredItems()
        filteredItems.forEach(item => {
          const itemDiv = document.createElement('div')
          itemDiv.className = 'item'

          const label = document.createElement('label')
          // Create checkbox
          const checkbox = document.createElement('input')
          checkbox.type = 'checkbox'
          checkbox.checked = item.checked
          checkbox.addEventListener('change', (e) => {
            // Update the correct item in state
            const foundItem = state.items.find(i => i.id === item.id)
            if (foundItem) {
              foundItem.checked = e.target.checked
            }
            // Update only the count display (or re-render whole view if preferred)
            updateCount()
            updateItemLabel(label, item)
          })
          label.appendChild(checkbox)

          // Create span for the item name
          const span = document.createElement('span')
          span.textContent = item.name
          if (item.checked) {
            span.classList.add('checked')
          }
          label.appendChild(span)
          itemDiv.appendChild(label)
          app.appendChild(itemDiv)
        })

        // Create selected count display
        const countP = document.createElement('p')
        countP.className = 'count'
        countP.id = 'selected-count'
        countP.textContent = 'Selected: ' + getSelectedCount()
        app.appendChild(countP)
      }

      // Update only the selected count without re-rendering everything
      function updateCount() {
        const countP = document.getElementById('selected-count')
        if (countP) {
          countP.textContent = 'Selected: ' + getSelectedCount()
        }
      }

      // Update the label's styling for an item when its checked state changes
      function updateItemLabel(label, item) {
        const span = label.querySelector('span')
        if (span) {
          if (item.checked) {
            span.classList.add('checked')
          } else {
            span.classList.remove('checked')
          }
        }
      }

      // Initial render
      render()
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)