上一篇講到SPA的缺點,Vue是用JvaScript載入後台的數據,並且動態產生元件,SEO只能抓取HTML內容,導致無法抓到該有的數據,因此有團隊開發出Nuxt.js這套前端框架,框架或工具都是因應某個需要被解決的問題而生的。
Nuxt.js是以Vue為基礎所建構的框架,非Vue官方所開發的,能做到SPA的開發模式,另一方面又可以接換到SSR模式,改善SEO無法爬蟲的缺點。
當使用者第一次載入到網頁時,nuxt server
會先解析pages.vue檔或是compontents.vue檔,之後回傳給nuxt server,整理出一份HTML檔案到client端讓畫面顯示出來,這個部分以前都是使用SSR
模式在跑流程,之後開始轉用SPA
模式,點擊nuxt-link,都不會讓頁面跳頁,除非點擊單純的a連結,點擊a連結將會在種跑一次SSR模式,大大改善SEO的缺點,這樣的好處是一開始就有html結構,讓搜尋爬蟲能找到資料,之後再開始切換SPA,使用者體驗有了,SEO問題也解決了,但目前Vue3版本還無法支援
。
以前的觀念,就是把所有程式都往HTML頁面塞,這種程式碼稱為義大利麵式程式碼
(Spaghetti code),隨著專案的需求越來越大,讓閱讀與開發變得非常困難,在開發領域裡,有個關注點分離的設計原則(Separation of Concerns),意思就是程式需要拆解成不同區塊,各自分工合作,在撰寫程式碼時,要同時思考後續的維護性。
有了以上的原則後,就可以將模型檢視控制器(MVC)拆解成三個區塊:
JavaScript:
let productData = []
document.getElementById('addProduct').addEventListener('click', (e) => {
const timeStamp = Math.floor(Date.now());
if (document.getElementById('title').value.trim() !== '') {
productData.push({
id: timeStamp,
title: document.getElementById('title').value.trim(),
origin_price: parseInt(document.getElementById('origin_price').value) || 0,
price: parseInt(document.getElementById('price').value) || 0,
is_enabled: false,
})
let str = '';
productData.forEach((item) => {
str += `
<tr>
<td>${item.title}</td>
<td width="120">
${item.origin_price}
</td>
<td width="120">
${item.price}
</td>
<td width="100">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_enabled" ${item.is_enabled? 'checked': ''} data-action="complete" data-id="${item.id}">
<label class="form-check-label" for="is_enabled">${item.is_enabled? '啟用' : '未啟用'}</label>
</div>
</td>
<td width="120">
<button type="button" class="btn btn-sm btn-danger move" data-action="remove" data-id="${item.id}"> 刪除 </button>
</td>
</tr>`;
})
document.getElementById('productList').innerHTML = str;
document.getElementById('productCount').textContent = productData.length;
document.getElementById('title').value = '';
document.getElementById('origin_price').value = '';
document.getElementById('price').value = '';
}
});
document.getElementById('clearAll').addEventListener('click', (e) => {
e.preventDefault();
productData = [];
let str = '';
productData.forEach((item) => {
str += `
<tr>
<td>${item.title}</td>
<td width="120">
${item.origin_price}
</td>
<td width="120">
${item.price}
</td>
<td width="100">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_enabled" ${item.is_enabled? 'checked': ''} data-action="complete" data-id="${item.id}">
<label class="form-check-label" for="is_enabled">${item.is_enabled? '啟用' : '未啟用'}</label>
</div>
</td>
<td width="120">
<button type="button" class="btn btn-sm btn-danger move" data-action="remove" data-id="${item.id}"> 刪除 </button>
</td>
</tr>`;
})
document.getElementById('productList').innerHTML = str;
document.getElementById('productCount').textContent = productData.length;
});
document.getElementById('productList').addEventListener('click', (e) => {
const action = e.target.dataset.action;
const id = e.target.dataset.id;
if (action === 'remove') {
let newIndex = 0;
productData.forEach((item, key) => {
if (id == item.id) {
newIndex = key;
}
})
productData.splice(newIndex, 1);
} else if (action === 'complete') {
productData.forEach((item) => {
if (id == item.id) {
item.is_enabled = !item.is_enabled;
}
})
}
let str = '';
productData.forEach((item) => {
str += `
<tr>
<td>${item.title}</td>
<td width="120">
${item.origin_price}
</td>
<td width="120">
${item.price}
</td>
<td width="100">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_enabled" ${item.is_enabled? 'checked': ''} data-action="complete" data-id="${item.id}">
<label class="form-check-label" for="is_enabled">${item.is_enabled? '啟用' : '未啟用'}</label>
</div>
</td>
<td width="120">
<button type="button" class="btn btn-sm btn-danger move" data-action="remove" data-id="${item.id}"> 刪除 </button>
</td>
</tr>`;
})
document.getElementById('productList').innerHTML = str;
document.getElementById('productCount').textContent = productData.length;
});
let str = '';
productData.forEach((item) => {
str += `
<tr>
<td>${item.title}</td>
<td width="120">
${item.origin_price}
</td>
<td width="120">
${item.price}
</td>
<td width="100">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_enabled" ${item.is_enabled? 'checked': ''} data-action="complete" data-id="${item.id}">
<label class="form-check-label" for="is_enabled">${item.is_enabled? '啟用' : '未啟用'}</label>
</div>
</td>
<td width="120">
<button type="button" class="btn btn-sm btn-danger move" data-action="remove" data-id="${item.id}"> 刪除 </button>
</td>
</tr>`;
})
document.getElementById('productList').innerHTML = str;
document.getElementById('productCount').textContent = productData.length;
function renderPage(data) {
}
let productData = []
let addBtn = document.getElementById('addProduct');
let clearall = document.getElementById('clearAll');
let clearindex = document.getElementById('productList');
let productTitle = document.getElementById('title');
let productPrice = document.getElementById('origin_price');
let Price = document.getElementById('price');
let Count = document.getElementById('productCount');
//資料處理
function addProduct(){
const timeStamp = Math.floor(Date.now());
if (document.getElementById('title').value.trim() !== '') {
productData.push({
id: timeStamp,
title: document.getElementById('title').value.trim(),
origin_price: parseInt(document.getElementById('origin_price').value) || 0,
price: parseInt(document.getElementById('price').value) || 0,
is_enabled: false,
});
renderPage(productData);
//空字串清空資料
productTitle.value = '';
productPrice.value = '';
Price.value = '';
}
};
addBtn.addEventListener('click', addProduct);
// 資料全部刪除
function clearAll(e){
e.preventDefault();
productData = [];
renderPage(productData);
};
clearall.addEventListener('click', clearAll);
function productList(e){
const action = e.target.dataset.action;
const id = e.target.dataset.id;
if (action === 'remove') {
let newIndex = 0;
productData.forEach((item, key) => {
if (id == item.id) {
newIndex = key;
}
})
productData.splice(newIndex, 1);
} else if (action === 'complete') {
productData.forEach((item) => {
if (id == item.id) {
item.is_enabled = !item.is_enabled;
}
})
}
renderPage(productData);
}
clearindex.addEventListener('click' ,productList)
// 渲染畫面
function renderPage(data){
let str = '';
productData.forEach((item) => {
str += `
<tr>
<td>${item.title}</td>
<td width="120">
${item.origin_price}
</td>
<td width="120">
${item.price}
</td>
<td width="100">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_enabled" ${item.is_enabled? 'checked': ''} data-action="complete" data-id="${item.id}">
<label class="form-check-label" for="is_enabled">${item.is_enabled? '啟用' : '未啟用'}</label>
</div>
</td>
<td width="120">
<button type="button" class="btn btn-sm btn-danger move" data-action="remove" data-id="${item.id}"> 刪除 </button>
</td>
</tr>`;
})
clearindex.innerHTML = str;
Count.textContent = data.length;
}
renderPage(productData);
關注點分離的版本,相當簡潔,整體下來約縮減約五十幾行的程式碼。
參考資料:
HiSKIO 程式語言線上教學
Kuro
Microsoft MVC
架構原則
ALPHA Camp