wordpress_blog

This is a dynamic to static website.

Vue3 複習01

學習來自: 六角學院
課程: Vue 3 實戰影音課程
Thank you

課前章節 – JS 必備觀念

JS 額外補充資源

為了加強學習 Vue 前的基礎知識

除了 本課程中的「課前章節 – JS 必備觀念」

除了課綱內的內容外,同時可參考以下資源

課程環境

課程相關資源

課程練習手冊:

連結

Chrome 必裝的 Vue 開發套件:

Vue Devtools

課程使用的 CSS 函式庫 Bootstrap:

六角學院翻譯中文版

VSCode 相關套件:

講師使用的佈景主題

Live Server

Vue 官方提供的 Vue 整合插件

Vue 3 Snippets

繁體中文版

中文版安裝說明

其他推薦 VSCode 套件


關於 Vue dev tools 的常見問題,有幾個地方可以檢查看看

  • 需確保當前頁面一定要有 createApp 的程式碼 (如果當頁有,可以另開新分頁確認,有時候重新整理也不會顯示)
  • Vue 並非使用開發版本 (課程中是使用開發版本)
  • dev tools 並非對應 Vue 3 的版本,請確認是否使用 dev 版本 (兩者僅能擇一開啟)

連結

由於這是 Vue3 的版本插件,如果未來需要用舊版的插件

可以從 Chrome 的後台進行切換

JS 常見縮寫

// HTML
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
// JS
// #1 語法糖與新增語法
// 語法糖:不會影響運作,邏輯與當前 JS 一致
// const obj = {
//   myName: "1. 物件",
//   fn() {
//     return this.myName;
//   },
// };

// console.log(obj.fn());

// =====
// #2 物件字面值 Object literals
// #2-1 物件內的函式
// const obj3 = {
//   myName: "物件",
//   fn() {
//     // 請改為縮寫
//     return this.myName;
//   },
// };

// console.log(obj3.fn());

// #2-2 物件內的變數
// const person = {
//   name: "小明",
// };

// const people = {
//   person,
// };

// console.log(people);

// #3 展開
// #3-1 不同陣列合併
// const groupA = ["小明", "杰倫", "阿姨"];
// const groupB = ["老媽", "老爸"];
// // const groupAll = groupA.concat(groupB);
// const groupAll = [...groupA, ...groupB];

// console.log(groupAll);

// #3-2 物件擴展
// 新增一個物件包含新方法,同時加入原有的方法
// const methods = {
//   fn1() {
//     console.log(1);
//   },
//   fn2() {
//     console.log(1);
//   },
// };

// const newMethods = {
//   fn() {
//     console.log(1);
//   },
//   ...methods,
// };
// console.log(newMethods);

// #3-3 轉成純陣列
// const doms = document.querySelectorAll("li");
// console.log(doms); // 請轉為純陣列
// const newDoms = [...doms];
// console.log(newDoms);

// #4 預設值
function sum(a, b = 2) {
  // 請加入預設值避免錯誤
  return a + b;
}
console.log(sum(1, 3));

This 的運作

// JS
// #1 一個函式中包含多少參數
// var a = "全域";
// function fn(params) {
//   console.log(params, this, window, arguments);
//   debugger;
// }
// fn(1, 2, 3);

// #2 this 的指向為何
// var obj = {
//   name: "小明",
//   fn: function (params) {
//     console.log(params, this, window, arguments);
//     // debugger;
//   },
// };
// obj.fn(1, 2, 3);

// #3 注意:this 的指向相當複雜,大部分情境只需要了解其中一種即可(95%)
// 傳統函式中的 this 只與調用方式有關
var someone = "全域";
function callSomeone() {
  console.log(this.someone);
}
// callSomeone(); // simple call

// #4 各種運用變化
// var obj = {
//   someone: "物件",
//   callSomeone() {
//     console.log(this.someone);
//   },
// };
// obj.callSomeone();

// var obj2 = {
//   someone: "物件2",
//   callSomeone,
// };
// obj2.callSomeone();

// var wrapObj = {
//   someone: "外層物件",
//   callSomeone,
//   innerObj: {
//     someone: "內層物件",
//     callSomeone,
//   },
// };
// wrapObj.callSomeone(); // 指向外層物件
// wrapObj.innerObj.callSomeone(); // 指向內層物件

// var obj3 = {
//   someone: "物件 3",
//   fn() {
//     callSomeone(); // 通常平常不會這樣去取用 this
//   },
// };
// obj3.fn();

var obj4 = {
  someone: "物件 4",
  fn() {
    setTimeout(function () {
      console.log(this.someone);
    });
  },
};
obj4.fn();

箭頭函式

// JS
// #1 箭頭函式的縮寫
// const arr = [1, 2, 3, 4, 5];

// // const filterArr = arr.filter(function (item) {
// //   // console.log(item);
// //   // console.log(item % 2);
// //   return item % 2; // 結果為真值
// // });
// const filterArr = arr.filter((item) => item % 2); // 取有餘數的值(單數)
// // 箭頭函式會自動帶 return
// console.log(filterArr);

// #2 This 綁定的差異
// #2-1 活用觀念,將內層的改為箭頭
// var name = "全域";
// const person = {
//   name: "小明",
//   callName: function () {
//     console.log("1", this.name); // 1 小明
//     // setTimeout(function () {
//     //   console.log("2", this.name); // 2
//     //   console.log("3", this); // 3
//     // }, 10);
//     // 箭頭函式,沒有自己的 this
//     setTimeout(() => {
//       console.log("2", this.name); // 2
//       console.log("3", this); // 3
//     }, 10);
//   },
// };
// person.callName();

// #3 陷阱
// #3-1
// var name = "全域";
// const person = {
//   name: "小明",
//   callName: () => {
//     console.log(this.name); // 請尋找箭頭所在的作用域為何?
//   },
// };
// person.callName();

// #3-2
// var name = "全域";
// const person = {
//   name: "小明",
//   callMe() {
//     const callName = () => {
//       console.log(this.name); // 請尋找箭頭所在的作用域為何?
//     };
//     callName();
//   },
// };
// person.callMe();

// #4 實戰手法
var someone = "全域";
// function callSomeone() {
//   console.log(this.someone);
// }

// 1. this 先指向其他變數
// 2. 使用箭頭函式
var obj4 = {
  someone: "物件 4",
  fn() {
    // 方法一
    // const vm = this; // vm 在 Vue 中意指 ViewModel
    // setTimeout(function () {
    //   // callback function
    //   // console.log(this.someone);
    //   console.log(vm.someone);
    // });
    // 方法二
    setTimeout(() => {
      console.log(this.someone);
    });
  },
};

obj4.fn();

關注點分離概念說明

// HTML
<div class="component">
  <ul></ul>
  <input type="text" class="inputData">
  <button type="button" class="addBtn">增加內容</button>
</div>
// JS
// #1 資料、畫面、方法分離
// 畫面 = html
// 資料 = component.data
// 方法 = 物件內的其它函式

// #2 元件結構
// 1. 資料
// 2. 方法、觸發器
// 3. 生命週期(初始化)
const component = {
  data: [ // 資料
    '這是第一句話',
    '這是第二句話',
    '這是第三句話'
  ],
  removeData(id) {
    this.data.splice(id, 1);
    this.render();
  },
  render() { // 渲染方法
    const list = document.querySelector('.component ul');
    let content = '';
    this.data.forEach((item, i) => {
      content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`
    });
    // 縮寫優化
    // const content = component.data.map(item => `<li>${item}</li>`).join('');
    list.innerHTML = content;

    // 加入監聽
    const btns = document.querySelectorAll('.btn');
    btns.forEach(btn => btn.addEventListener('click', (e)=> {
      // #2 重點,移除項目是先移除資料,而不是直接移除 DOM
      // 如果要進行 AJAX 或更複雜行為,不會因為 DOM 與資料混合而難以運作
      const id = e.target.dataset.id;
      this.removeData(id)
    }))
  },
  init() {
    this.render();
  }
}
component.init();

傳統形式

  • 將文字提取
  • 產生一個新的節點,並且帶入文字

關注點分離

  • 將文字提取出,並且寫入資料集
  • 資料集的內容,轉換成畫面

關注點分離實作概念

// HTML
<div class="component">
  <ul></ul>
</div>
// JS - 課程講解
// 畫面 - HTML
// 資料 - DATA
// 方法 - function

// #元件結構
// 1. 資料
// 2. 方法、觸發器
// 3. 生命週期
const component = {
  // 資料
  data: ["這是第一句話", "這是第二句話", "這是第三句話"],
  // 事件觸發
  removeData(id) {
    // console.log(id);
    // console.log(this);
    this.data.splice(id, 1);
    this.render();
  },
  // 渲染方法
  render() {
    //
    // 方法一
    // const vm = this;
    const list = document.querySelector(".component ul");
    // console.log(list);
    let content = ""; // li 結構使用
    this.data.forEach((item, i) => {
      // console.log(item);
      content = `${content}<li>${item}
                    <button type="button" class="btn" data-id="${i}">刪除</button></li>`;
      // console.log(content);
    });
    list.innerHTML = content;

    const btns = document.querySelectorAll(".btn");
    // console.log(btns);
    btns.forEach((btn) =>
      // btn.addEventListener("click", function (e) {
      //   // console.log(e);
      //   // console.log(e.target.dataset.id); // 陣列索引位置
      //   console.log(this);
      //   this.removeData(e.target.dataset.id);
      // })
      // 方法二
      // 把上面的 function 改成箭頭函式
      btn.addEventListener("click", (e) => {
        // console.log(e);
        // console.log(e.target.dataset.id); // 陣列索引位置
        console.log(this);
        this.removeData(e.target.dataset.id);
      })
    );
  },
  // 生命週期
  init() {
    this.render();
  },
};
component.init();
// JS - 範例
// #1 資料、畫面、方法分離
// 畫面 = html
// 資料 = component.data
// 方法 = 物件內的其它函式

// #2 元件結構
// 1. 資料
// 2. 方法、觸發器
// 3. 生命週期(初始化)
// const component = {
//   data: [ // 資料
//     '這是第一句話',
//     '這是第二句話',
//     '這是第三句話'
//   ],
//   removeData(id) {
//     this.data.splice(id, 1);
//     this.render();
//   },
//   render() { // 渲染方法
//     const list = document.querySelector('.component ul');
//     let content = '';
//     this.data.forEach((item, i) => {
//       content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`
//     });
//     // 縮寫優化
//     // const content = component.data.map(item => `<li>${item}</li>`).join('');
//     list.innerHTML = content;

//     // 加入監聽
//     const btns = document.querySelectorAll('.btn');
//     btns.forEach(btn => btn.addEventListener('click', (e)=> {
//       // #2 重點,移除項目是先移除資料,而不是直接移除 DOM
//       // 如果要進行 AJAX 或更複雜行為,不會因為 DOM 與資料混合而難以運作
//       const id = e.target.dataset.id;
//       this.removeData(id)
//     }))
//   },
//   init() {
//     this.render();
//   }
// }
// component.init();

傳參考特性 (常踩到的雷)

因為關注點分離,畫面由框架來處理,開發者將專注於資料處理,因此資料處理的知識顯得格外重要。

// JS
// #1 物件是以傳參考的形式賦值
// const person = {
//   name: '小明',
//   obj: {}
// }

// const person2 = person;
// person2.name = '杰倫';
// // console.log(person2 === person);
// // console.log(person2, person);

// const obj2 = person.obj;
// obj2.name = "物件下的名稱";
// console.log(person);


// #2 陷阱
// const fn = (item) => {
//   item.name = '杰倫';
//   // ...
// }
// const person = {
//   name: '小明',
//   obj: {}
// }
// fn(person);
// console.log('person', person);
// // ...

// 關於:這樣的錯誤,ESLint 也有建議不要這麼做(但你是有經驗的開發者時,下方也有提供關閉的方式)
// https://cn.eslint.org/docs/rules/no-param-reassign

// #3 解決方案
// #3-1 淺層拷貝
// const person = {
//   name: '小明',
//   obj: {}
// }
// // 方法一
// const person2 = Object.assign({}, person);
// // 方法二
// const person3 = {
//   ...person
// };
// console.log(person === person2, person === person3);
// person2.obj.age = 16;
// console.log(person, person2);

// #3-2 深層拷貝
const person = {
  name: '小明',
  obj: {}
}
// 物件先轉成字串,然後再轉成物件
const person2 = JSON.parse(JSON.stringify(person));
// console.log(person2);
// console.log(person2 === person);
person2.obj.age = 16;
console.log(person2, person);

Promise 非同步觀念

// JS
// #1 非同步的觀念
// function getData() {
//   setTimeout(() => {
//     console.log("... 已取得遠端資料");
//   }, 0);
// }
// // 請問取得資料的順序為何
// const component = {
//   init() {
//     console.log(1);
//     getData();
//     console.log(2);
//   },
// };
// component.init();

// 更正確的說法,Promise 是為了解決傳統非同步語法難以建構及管理的問題,如有興趣可搜尋 "callback hell js"

// #2 Promise
// 在此不需要學習 Promise 的建構方式,僅需要了解如何運用即可
const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve("promiseSetTimeout 成功");
      } else {
        reject("promiseSetTimeout 失敗");
      }
    }, 1000);
  });
};

// #2-1 基礎運用
// promiseSetTimeout(true).then(function (res) {
//   console.log(res);
// });

// #2-2 串接
// promiseSetTimeout(true)
//   .then(function (res) {
//     console.log(1, res);
//     return promiseSetTimeout(true);
//   })
//   .then((res) => {
//     console.log(2, res);
//   });

// #2-3 失敗捕捉
promiseSetTimeout(false)
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

// #2-4 元件運用
const component = {
  data: {},
  init() {
    console.log(this);
    promiseSetTimeout(true).then((res) => {
      this.data.res = res;
      console.log(this.data);
    });
  },
};
component.init();

使用 Axios 串接 API

// HTML
<!-- 本章節額外載入的遠端套件 -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// JS
// #3 實戰取得遠端資料
// #3-1
// https://github.com/axios/axios
axios
  .get("https://randomuser.me/api/gg")
  .then((res) => {
    console.log(res.data.results);
  })
  .catch((err) => {
    // console.log(err);
    console.log(err.response);
  });

// #3-2 記得捕捉錯誤,Axios 錯誤捕捉技巧
// https://randomuser.me/

在瀏覽器上運行 ES 模組

匯入 (html, js)

// 預設匯入
import 自訂名稱 from ...
// 具名匯入
import { 具名名稱 } from ...

匯出 (js)

// 預設匯出: 每個檔案唯一
export default
// 具名匯出: 每個檔案多個
export const xxx = ...
// HTML
<div class="component">
  <ul></ul>
</div>

<script type="module">
  // #1 匯出匯入本一體,先掌握匯出更容易理解匯入
  // #1-1 將標籤定義 <script type="module">
  // #1-2 預設匯出:defaultExport.js
  // 常見的匯出方式,通常用於匯出物件,在 Vue 開發中可用來匯出元件

  // #1-3 具名匯出:namedExport.js
  // 可用於匯出已定義的變數、物件、函式,專案開發中通常用於 “方法匯出”
  // 第三方的框架、函式、套件很常使用具名定義 “方法”

  // #2 匯入方法
  // #2-1 預設匯入
  // 因為預設匯出沒有名字,所以可以為它命名
  import newComponent from "./export1.js";
  newComponent.init();

  // 注意:因為傳參考的特性,因此元件無法重複利用(Vue.js 中會解決此問題)

  // #2-2 具名匯入
  // 單一匯入(建議寫法)
  // import { a, b } from "./export2.js";
  // console.log(a);
  // b();

  // 全部匯入(不建議,錯誤較難發現)
  import * as all from "./export2.js";
  console.log(all.a);
  all.b();
  console.log(all.c(1, 2));

  // #3 SideEffect
  // sideEffect.js(沒有包含匯出的檔案)
  import "./sideEffect.js";
  console.log($);
</script>
// JS - export1.js
export default {
  data: [
    // 資料
    "這是第一句話",
    "這是第二句話",
    "這是第三句話",
  ],
  removeData(id) {
    this.data.splice(id, 1);
    this.render();
  },
  render() {
    // 渲染方法
    const list = document.querySelector(".component ul");
    let content = "";
    this.data.forEach((item, i) => {
      content = `${content}<li>${item} <button type="button" class="btn" data-id="${i}">移除</button></li>`;
    });
    // 縮寫優化
    // const content = component.data.map(item => `<li>${item}</li>`).join('');
    list.innerHTML = content;

    // 加入監聽
    const btns = document.querySelectorAll(".btn");
    btns.forEach((btn) =>
      btn.addEventListener("click", (e) => {
        // #2 重點,移除項目是先移除資料,而不是直接移除 DOM
        // 如果要進行 AJAX 或更複雜行為,不會因為 DOM 與資料混合而難以運作
        const id = e.target.dataset.id;
        this.removeData(id);
      })
    );
  },
  init() {
    this.render();
  },
};
// JS -export2.js
export const a = 1;

export function b() {
  console.log(1);
}

export function c(a, b) {
  return a + b;
}
// JS - sideEffect.js
// 立即函式
(function (global) {
  global.$ = "我是 jQuery";
})(window);

現行的 ES 模組使用技巧

// HTML
<!-- <script type="module">
  // #1 每一個 type="module" 的作用域都是獨立的

  var a = 1;
  window.b = 2;
</script>
<script type="module">
  console.log(b);
</script> -->

<div id="app">{{ counter }}</div>
<script type="module">
  // #2 可以運用的 ESM 套件
  // 網路上找到的 “ESM”,如果條件允許是可以使用 import 方式載入
  // https://cdnjs.com/libraries/vue
  // 比對語法:https://v3.vuejs.org/guide/introduction.html#declarative-rendering
  import { createApp } from "https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.19/vue.esm-browser.min.js";

  const Counter = {
    data() {
      return {
        counter: 0,
      };
    },
  };

  createApp(Counter).mount("#app");
</script>

JS 必備觀念 – 課後測驗

this

物件參考特性

Promise

ESModule

縮寫