Vue.js 从零开始:SPA怎麽改善SEO呢? MVC与关注点分离又是什麽?

上一篇讲到SPA的缺点,Vue是用JvaScript载入後台的数据,并且动态产生元件,SEO只能抓取HTML内容,导致无法抓到该有的数据,因此有团队开发出Nuxt.js这套前端框架,框架或工具都是因应某个需要被解决的问题而生的。


Nuxt.js 是什麽?

Nuxt.js是以Vue为基础所建构的框架,非Vue官方所开发的,能做到SPA的开发模式,另一方面又可以接换到SSR模式,改善SEO无法爬虫的缺点。

Nuxt SSR 运作原理

https://ithelp.ithome.com.tw/upload/images/20210917/20118347FRh3CNtaSB.png

当使用者第一次载入到网页时,nuxt server会先解析pages.vue档或是compontents.vue档,之後回传给nuxt server,整理出一份HTML档案到client端让画面显示出来,这个部分以前都是使用SSR模式在跑流程,之後开始转用SPA模式,点击nuxt-link,都不会让页面跳页,除非点击单纯的a连结,点击a连结将会在种跑一次SSR模式,大大改善SEO的缺点,这样的好处是一开始就有html结构,让搜寻爬虫能找到资料,之後再开始切换SPA,使用者体验有了,SEO问题也解决了,但目前Vue3版本还无法支援


MVC 与关注点分离(SOC)

以前的观念,就是把所有程序都往HTML页面塞,这种程序码称为义大利面式程序码(Spaghetti code),随着专案的需求越来越大,让阅读与开发变得非常困难,在开发领域里,有个关注点分离的设计原则(Separation of Concerns),意思就是程序需要拆解成不同区块,各自分工合作,在撰写程序码时,要同时思考後续的维护性。
有了以上的原则後,就可以将模型检视控制器(MVC)拆解成三个区块:
https://ithelp.ithome.com.tw/upload/images/20210917/20118347TAXYs7KiOa.png

  • Model:模型、资料层,资料状态。
  • View:画面、视图、表现层,前端样板。
  • Controller:控制器、逻辑层,处理资料逻辑,举例像是电视遥控器,使用者按下按钮,遥控器发出请求指令。

请观察下列的程序码架构

非关注点分离:

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);

关注点分离的版本,相当简洁,整体下来约缩减约五十几行的程序码。 /images/emoticon/emoticon07.gif


参考资料:
HiSKIO 程序语言线上教学
Kuro
Microsoft MVC
架构原则
ALPHA Camp


<<:  [Day 13] Leetcode 49. Group Anagrams (C++)

>>:  Day3-LeetCode Medium+Easy

Day 19 - Execution Context

JavaScript 在运作时会建立执行环境,分为 Global Execution Context...

[Golang]同步工具-sync包的原子操作(下)-心智图总结

1. 比较和交换(compare and swap,简称CAS)跟交换(swap)有什麽不同? 比较...

Day28 - reversing.kr - Easy_ELF

reversing kr 是一个很不错的练习逆向的地方。 reversing .kr 介绍 This...

[必学]如何简单快捷备份及还原 Facebook 聊天记录

这边推荐你一款 FonePaw iOS 资料备份及还原 软件,能够一键备份 iPhone Faceb...

Day 28:PVE VM 内 CPU、GPU 效能评测

前言 在安装过後,今天我们来测试 VM 的相关效能。 CPU 效能测试 我们采用一套有天梯的效能测试...