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>
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>
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>
Top comments (0)