0%

最近遇到將表單資料存到後端的方式,過去一直以為是將資料整理為物件方式存取即可,後來發現使用FormData來建立物件傳送檔案即可!

  • FormData 是一個表單格式,利用它將資料轉成表單的格式,並且以表單的形式回傳給後端

建立 FormData 物件

  • 最直接方式,先取得form的資料
1
2
3
4
5
6
7
8
// 根據表單元素取得表單資料
var form = document.querySelector('form');
var formData = new FormData(form);

// 可以用 append 把欄位灌進去表單中
var formData = new FormData(); // 一開始表單的資料是空的
formData.append('username', Chris); // 增加欄位
formData.append('image', file); // 檔案也可以透過 append 放進來

以下介紹幾個基礎的使用方式:

  • formData.append(name, value) : 加入有 name 和 value 的表單資料(name:表單標籤的名稱,value: input 輸入的值)
  • formData.append(name, blob, fileName) : 加入字段,就像它是 <input type="file">,第三個參數 fileName 設置檔案名
  • formData.delete(name) : 移除帶有 name 的字段,
  • formData.get(name) : 取得帶有 name 的值,
  • formData.has(name) : 如果帶有給定 name 的字段,返回 true,否則返回 false。

使用axios 傳遞資料

1
2
3
4
5
6
7
8
9
var formData = new FormData();
//宣告要取得的值
var title = form.name
formData.append("formTitle", title);
axios.post('upload_file', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})

補充 vue+Typescript 宣告型別

  • 以 stack overflow 內的資料為例:
    • 可以使用 ref來取得DOM元素,並宣告該變數為HTMLFormElement
      1
      2
      3
      4
      5
      6
      <el-form
      class="m-auto"
      ref="formRef"
      :model="form"
      :rules="rules"
      >
      1
      const formRef = ref<HTMLFormElement>();
  • 另是使用 element-UI 的表單,也可以使用
    import { ElForm } from "element-ui";

如何使用jQuery AJAX submit 傳送form表單方法
JavaScript / jQuery 前端開發入門實戰
JavaScript Info-formData
stackoverflow

useAsyncData

1
2
3
4
5
6
7
8
9
10
const {
data: Ref<DataT>,
pending: Ref<boolean>,
refresh: (force?: boolean) => Promise<void>,
error?: any
} = useAsyncData(
key: string,
fn: () => Object,
options?: { lazy: boolean, server: boolean }
)
  • 頁面、組件中都可以使用useAsyncData 取得資料
  • Key :寫一個唯一的id
    請求資料的函式
  • 使用 useAsyncData 會回傳物件
    data : 返回的資料
    pedding : 指出資料是否被取得
    refresh : 是否要刷新資料

    Option 介紹

  • Lazy 延遲載入,直到數據獲取時,才顯示於頁面
  • 默認會是 false
  • Server 頁面是在server端要載入,就可以設定為true
  • Pick 從回傳的資料中,選擇要使用的key值

useAsyncData撰寫方式的範例

  • 本地建立資料夾
    • server/api/count.ts
1
2
3
4
5
let counter = 0
export default () => {
counter++
return JSON.stringify(counter)
}
  • app.vue,使用 useAsyncData
1
2
3
4
5
6
7
<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>

useFetch

  • 頁面、組件中可以使用useFetch獲取任意URL資源
  • useFetch包裝 了useAsyncData 和 $fetch
  • 會根據 URL 自動生成 Key,也會去推斷API 的類型a
    1
    2
    3
    4
    5
    6
    const {
    data: Ref<DataT>,
    pending: Ref<boolean>,
    refresh: (force?: boolean) => Promise<void>,
    error?: any
    } = useFetch(url: string, options?)
1
2
3
4
5
6
7
<script setup>
const { data } = await useFetch('/api/count')
</script>

<template>
Page visits: {{ data.count }}
</template>

補充:其他2個類似方式

  • useLazyAsyncData : 方法等於useAsyncData,僅設置了lazy選項為true
  • useLazyFetch : 方法等於useFetch,僅設置了lazy選項為true

實作範例:

  • /api/mountains/everest
    • API 會產生的物件資料
1
2
3
4
5
6
7
8
9
10
11
{
"title": "Mount Everest",
"description": "Mount Everest is Earth's highest mountain above sea level, located in the Mahalangur Himal sub-range of the Himalayas. The China–Nepal border runs across its summit point",
"height": "8,848 m",
"countries": [
"China",
"Nepal"
],
"continent": "Asia",
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/600px-Everest_kalapatthar.jpg"
}

Nuxt 3 為了提高效率會在取得資料後,緩存在當前頁面的payload
官方建議:如果只打算在組件中使用到 title 和 description ,你可以使用關鍵的屬性運用即可
選項中加入 pick,裡面的值就是取得資料中的key值

1
2
3
4
5
6
7
8
<script setup>
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
</script>

<template>
<h1>{{ mountain.title }}</h1>
<p>{{ mountain.description }}</p>
</template>

參考資料

要熟悉 TypeScript 的型別訂定,並不容易,而且撰寫時也會花費較多的時間,但好處是減少程式碼的錯誤,也讓協作者更容易解讀對方撰寫的程式碼。
萬事起頭難,不同的地方就是多讀幾次,或是尋找相關的範例來相呼應!

Class

傳統方法中,JavaScript 透過建構函式實現類別的概念,透過原型鏈實現繼承。而在 ES6 中,我們終於迎來了 class。

  • 定義一切事務的抽象特點

  • object :class 的實例 new Class 生成

  • 物件導向 OOP(Object Oriented Programming) :三大特性 封裝、繼承、多型

    • 封裝(Encapsulation):將對資料的操作細節隱藏起來,只暴露對外的介面。外界呼叫端不需要(也不可能)知道細節,就能透過對外提供的介面來訪問該物件,同時也保證了外界無法任意更改物件內部的資料
    • 繼承(Inheritance):子類別繼承父類別,子類別除了擁有父類別的所有特性外,還有一些更具體的特性
    • 多型(Polymorphism):由繼承而產生了相關的不同的類別,對同一個方法可以有不同的響應

      複習下 ES6 撰寫方式

  • 使用 class 定義類別,使用 constructor 定義建構函式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal{
constructor(name){
this.name = name;
}
run () {
return `${this.name} is running`
}
}
const snake = new Animal('lily')
console.log(snake.run()); // lily is running

// 繼承的特性(繼承父類的屬性及方法), 繼承 run 的方法
class Dog extends Animal {
bark() {
return `${this.name} is barking`
}
}

const bao = new Dog('bao')
console.log(bao.run()) // bao is running
console.log(bao.bark()) // bao is barking
  • 使用 extends 關鍵字實現繼承
  • 这里我们重寫構造函式,注意在子類的構造函式中,必須使用 super 調用父類的方法,否則會報錯。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 
class Cat extends Animal {
constructor(name) {
super(name)
console.log(this.name) //maomao
}
run() {
// run 方法重寫
return 'Meow, ' + super.run()
}
}
const maomao = new Cat('maomao')
console.log(maomao.run()) //Meow, maomao is runing

TypeScript 中的 class

TypeScript 可以使用三種訪問修飾符(Access Modifiers)

  • public 修飾的屬性或方法是公有的,可以在任何地方被調用到,預設所有的屬性和方法都是 public 的
  • private 修飾的屬性或方法是私有的,不能在声明它的类的外部調用
  • protected 修飾的屬性或方法是受保护的,它和 private 類似,區别是它在子類中也是允许被訪問的
    例子說明:
  • name 被設定為了 public,所以直接訪問實例的 name 屬性是允許的。
1
2
3
4
5
6
7
8
9
10
11
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
  • 希望有的屬性是無法直接存取的,這時候就可以用 private 了:
  • name 設為 private
1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';

// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
  • 使用 private 修飾的屬性或方法,在子類別中也是不允許訪問的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}

class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}

// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
  • 而如果是用 protected 修飾,則允許在子類別中訪問:
  • 將上述程式碼修改一下
1
2
3
4
5
6
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}

類別 class 的型別

給類別加上 TypeScript 的型別很簡單,與介面類似:

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}

let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

類別

class 與 interface

  • 介面(Interfaces):不同類別之間公有的屬性或方法,可以抽象成一個介面。介面可以被類別實現(implements)。一個類別只能繼承自另一個類別,但是可以實現多個介面
  • 在 物件導向 世界中,一個 class 只能繼承自另外一個class
  • 有時候不同 class 之前,可以有共同的特性,使用子類繼承父類的方法很難來完成
  • class 可以使用 implements 來實現 interface (提高 OOP 的靈活性)

範例一:

  • 有 車子和手機,都有打開radio的功能
  • 可以將兩個共有的抽取為一個 interface
1
2
3
4
5
6
7
8
9
10
11
class Car  {
switchRadio(trigger: boolean) {

}
}

class Cellphone {
switchRadio(trigger: boolean) {

}
}

可以將兩個共有的抽取為一個 interface

  • void => 代表什麼都不返回
  • 在class 後面放入 implements 讓類去實現它
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Radio {
switchRadio(trigger: boolean): void;
}

class Car implements Radio {
switchRadio(trigger) {
return 123
}
}
class Cellphone implements Radio {
switchRadio() {
}
}

範例二

  • 新特性為 檢查電池的容量
  • 是手機有,但汽車沒有的
  • 所以新建立一個 interface (檢查battery)
    • 此功能只有手機有,所以放在手機的class
1
2
3
4
5
6
7
8
9
10
11
interface Battery {
checkBatteryStatus(): void;
}
// 要實現多个介面,我们只需要中間用 都好 隔开即可。
class Cellphone implements Radio, Battery {
switchRadio() {
}
checkBatteryStatus() {

}
}

此外 interface 之間有繼承關係

  • 建立 radioWithBattery 繼承 Radio ,裡面再放入檢查電量的設定
1
2
3
4
5
6
7
8
9
10
11
12
interface radioWithBattery extends Radio {
checkBatteryStatus(): void;
}


class Cellphone implements radioWithBattery {
switchRadio() {
}
checkBatteryStatus() {

}
}

類別與介面

enum 列舉

  • 常數指執行程序中不會被改變的值,在 JS 中我們一般會用 const 來宣告
  • 但有些取值是在一定範圍內的一系列常數。
    • 如:一周內七天、三原色(紅、黃、藍)、方向(上、下、左、右)

      範例一

  • 數字列舉
  • 列舉成員會被賦值為從0,開始遞增
1
2
3
4
5
6
7
8
9
10
11
// 數字列舉,一个數字列舉可以用 enum 这个關鍵詞來定義,我们定義一系列的方向,然后这里面的值,列舉成员会被赋值为從 0 开始遞增的數字,
enum Direction {
Up,
Down,
Left,
Right,
}
console.log(Direction.Up) //0
// 還有一个神奇的點是這個列舉还做了反向映射
console.log(Direction[0]) //up

  • 可以手動賦予值
  • 未手動賦值的列舉項會接著上一個列舉項遞增。
1
2
3
4
5
6
7
8
9
10

Up = 10,
Down,
Left,
Right,
}
console.log(Direction.Down) // 11 ,後面的項目數值會遞增

console.log(Direction[0]) //up

範例二

1
2
3
4
5
6
7
8
9
10
11
// 字符串列舉
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction.Up) {
console.log('go up!')
}

範例三

  • 常數列舉
    • 編譯後的邏輯變少了
    • 使用常數列舉會內連列舉的用法並且不會將設定的列舉編譯成 JS 程式碼
1
2
3
4
5
6
7
8
9
10
const  enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction.Up) {
console.log('go up!')
}


泛型 Generics

  • 要解決什麼問題
    • 建立函式 echo 參數為 arg ,接著宣告變數傳入參數
    • 但其中的型別沒有設定
    • 傳入和返回的型別無法統一
1
2
3
4
5
function echo(arg) {
return arg
}
const result = echo(123)
// 這時候我們發現了一個問題,我们傳入了數字,但是返回了 any

  • 當在建構 function、internet及Class 時,你會希望這些component都能被重複運用的 Generic(泛型)提供了一個彈性的作法。
  • 語法是: <T>

範例一

  • 泛型(Generics)是指在定義function、internet及Class的时候,不预先指定具體的型別,而在使用的时候再指定型別的一種特性。
1
2
3
4
5
function echo<T>(arg: T): T {
return arg
}

const result = echo(123)

範例二 傳入多個

1
2
3
4
5
6
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}

const result = swap(['string', 123])

泛型第二部分 - 约束泛型

  • 帶有「限制」的泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function echoWithArr<T>(arg: T): T {
console.log(arg.length)
return arg
}

// 上例中,泛型 T 不一定包含属性 length,我们可以给他傳入任意型別,當然有些不包括 length 属性,那样就會報錯

// 我們使用了 extends 約束了泛型 T 必須符合介面 IWithLength 的形狀,也就是必須包含 length 屬性。
interface IWithLength {
length: number;
}
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}

echoWithLength('str')
const result3 = echoWithLength({length: 10})
const result4 = echoWithLength([1, 2, 3])

//此時如果呼叫 loggingIdentity 的時候,傳入的 arg 不包含 length,那麼在編譯階段就會報錯了
echoWithLength(7);
// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'


泛型第三部分 - 泛型在 class 和 interface 中的使用

  1. 泛型在 class 的使用
  • 在第一個程式中存在一个问题,它允許你向 Queue 中添加任何型別的數據,當然,當數據被彈出隊列时,也可以是任意類型
  • 在下方的範例中,看起来可以向隊列中添加 string 型別的數據,但是那麼在使用的過程中,就會出現無法捕捉的錯誤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Queue {
private data = [];
push(item) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}

const queue = new Queue()
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed())


1
2
3
4
5
6
7
8
9
10
11
12
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item)
}
pop(): T {
return this.data.shift()
}
}
const queue = new Queue<number>()


  1. 泛型在 interface 中的使用
  • 之前提過可以使用介面的方式來定義一個函式需要符合的形狀
  • 當然也可以使用含有泛型的介面來定義函式的形狀:
1
2
3
4
5
6
7
8
9
10
11
//泛型和 interface
interface KeyPair<T, U> {
key: T;
value: U;
}

let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}

let arr:number[] = [1,2,3];
let arrTwo:Array<number> = [1,2,3]

interface 搭配泛型之后,可以靈活的返回不同的型別

  • 創建一个拥有特定型別的容器,class 和 泛型 仿佛给一个容器貼上標籤一样

  • 泛型就好像一个可變的參數,在用的时候傳入,生成这個不同型別的一个容器,

  • 上個部分的用它来靈活的约束参数的型別,不需要參數是一个特别死板的型別,不希望他是一个特定 string、number 型別,我要傳入的参數必须有某某屬性、某某方法,否則就會報錯。

  • 在函式使用的时候,函式的这个型別推斷,不會進入到函式中,所以使用表達式,没法明確建立型別的绑定,用泛型可以让我们打破這個鴻溝,這個时候就可以返回它傳入的類型。

    Type Aliases

  • 就是给型別起一個别名,讓它可以更方便的被重用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let sum: (x: number, y: number) => number
const result = sum(1,2)
type PlusType = (x: number, y: number) => number
let sum2: PlusType

// 支持聯合
type StrOrNumber = string | number
let result2: StrOrNumber = '123'
result2 = 123 //沒有錯

// 字符串字面量 ,類似在列舉的常數變量
type Directions = 'Up' | 'Down' | 'Left' | 'Right'
// 使toWhere 這個變量為 Direction 類型
let toWhere: Directions = 'Up'

  • 我們使用 type 定了一個字串字面量型別 EventNames,它只能取三種字串中的一種。
    注意,型別別名與字串字面量型別都是使用 type 進行定義。
1
2
3
4
5
6
7
8
9
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}

handleEvent(document.getElementById('hello'), 'scroll'); // 沒問題
handleEvent(document.getElementById('world'), 'dbclick'); // 報錯,event 不能為 'dbclick'

// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

Intersection Types

  • 使用 & 符號
  • 經過上面的 type 使 IName 就有了 name 和 age兩個屬性在裡面
    • 和interface 的 extends 有點類似,都是為了實現物件形狀組合和擴展
1
2
3
4
5
6
interface IName  {
name: string
}
type IPerson = IName & { age: number }
// 經過上面的 type 使 IName 就有了 name 和 age兩個屬性在裡面
let person: IPerson = { name: 'hello', age: 12}

什麼时候用介面,什麼时候用 Type Aliases:

  • interface 是 docker typing 的實現方式,是一種獨特類型,和extends class implememts 有關的用interface ,
  • 和交叉,聯合型別的有關的用 Type Aliases;

內建物件

DOM 和 BOM 的內建物件

DOM 和 BOM 提供的內建物件有:
Document、HTMLElement、Event、NodeList 等。
TypeScript 中會經常用到這些型別:

1
2
3
4
5
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});

ECMAScript 的內建物件

  • Boolean、Error、Date、RegExp 等。
1
2
3
4
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

TypeScript 內置

Utility Types

官方
Typescript 還提供了一些功能性,帮助性的型別,這些型別,大家在 JS 的世界是看不到的,這些型別叫做 utility types,提供一些簡潔明快而且非常方便的功能。

  • 使用 Partial
  • Omit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// partial,它可以把傳入的型別都變成可選
interface IPerson {
name: string
age: number
}

let viking: IPerson = { name: 'viking', age: 20 }
type IPartial = Partial<IPerson>
let viking2: IPartial = { } //可以都不傳,也不會報錯

// Omit,它返回的型別可以忽略傳入型別的某个屬性
// 以下範例 將 name 忽略掉
type IOmit = Omit<IPerson, 'name'>
let viking3: IOmit = { age: 20 }

資料來源:

  1. 線上課程:實戰 Vue3.0(正式版) + TS
  2. TypeScript 新手指南

因為工作需求,有需多缺乏的技能需要補齊,TypeScript 就是其中一項。
上班看不懂的時候邊查詢,空閒時間再來上課不足不懂的知識點,但還是得透過文章的整理來好好消化一下!
既然要開始介紹 TypeScript 就先來說說使用它的原因吧。

為什麼使用 TypeScript

  • 程式語言中,有區分強型別和弱型別兩種類型。

  • 而 JavaScript 是弱型別語言。

  • 在 JavaScript 中,他會自己判斷應該執行的型別。自行做[型別自動轉換]

  • 在快速開發時,很有可能引發一些預期之外的錯誤,相當不利於多人協同開發。

    • 優點:寫法彈性。
    • 缺點:無法在開發時期檢查型別。
  • 依據上述的問題,所以使用 TypeScript

  1. 使程式碼更容易理解 (如函式需傳入的參數可以明確設定型別)
  2. 減少程式碼撰寫的錯誤
  3. 可以完全兼容JavaScript,此外能與 ES6 完美結合,並提供更多支援寫法。

安裝介紹

  • 就是直接到官方網站查看,依需求安裝

npm install -g typescript

  • 檢查版本
    tsc -v

型別介紹

從 JavaScript 型別分類開始

字串 string

  • 和ES6一樣,可以使用[字串樣板 Template Literals]
    1
    2
    3
    let firstName:string = 'NaNa'
    let message: string = `Hello,${firstName}`

    布林值 boolean

布林值是最基礎的資料型別,在 TypeScript 中,使用 boolean 定義布林值型別:

1
let isDonw:boolean = false 

注意:使用建構函式 Boolean 建立的物件不是布林值:

  • 事實上 new Boolean() 返回的是一個 Boolean 物件
    1
    2
    3
    4
    let createdByNewBoolean: boolean = new Boolean(1);

    // Type 'Boolean' is not assignable to type 'boolean'.
    // 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
    1
    在 TypeScript 中,boolean 是 JavaScript 中的基本型別,而 Boolean 是 JavaScript 中的建構函式。

數值 number

1
2
3
let age:number = 10
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

Null 和 Undefined

  • 與 void 的區別是,undefined 和 null 是所有型別的子型別
    • unfefined 可以賦值給number類型的變數
    • 陣列被赋值為 undefined 或 null 不会报错
    • let num:number = undefined
1
2
3
4
5
6
let u: undefined = undefined;
let n: null = null;

// 這樣也不會報錯
let u: undefined;
let num: number = u;

void

  • 通常用在當函式沒有回傳值時。
  • 而 void 型別的變數不能賦值給 number 型別的變數:
    1
    2
    3
    4
    let u: void;
    let num: number = u;

    // Type 'void' is not assignable to type 'number'.

    any

  • 在任意值上任何属性都是允許的
    • 當有明確型別時,應避免使用 any
    • 因為 any 可以任意調用方法和屬性,很有可能出現錯誤(就喪失類型檢查的意義)
      如果是 any 型別,則允許被賦值為任意型別
1
2
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

未宣告型別的變數

變數如果在宣告的時候,未指定其型別,那麼它會被識別為任意值型別:

1
2
3
4
5
let something;
something = 'seven';
something = 7;

something.setName('Tom');

陣列 Array 與 元組 Tuple

陣列

  1. 在想宣告的型別後面加上[],宣告為某個型別的Array
    1
    let idList:number[] = [1,2,3]
  2. 使用陣列泛型,Array <元素型別>
    1
    let list:Array<number> = [1,2,3]
  • 注意,當已經宣告這是個數字陣列
    • 要 push 字串進去,會出現錯誤
      1
      2
      let list:Array<number> = [1,2,3]
      list.push("4")

      元組 Tuple

    • 剛剛的陣列將同一類型的數據放在一起,但是,想加入不同型別的數據怎麼辦?
    • 表示方式與陣列相似,將型別寫在[]
    • 有固定長度和元素型別的陣列
      1
      2
      3
      4
      // 表示方式和陣列非常相似,只不过它将型別寫在了里面 這時會對每一項產生了限定的作用
      let user: [string, number] = ['viking', 20]
      //但是当我们少寫一項 就會報錯,同樣多寫也會報錯
      user = ['molly', 20, true]

元組

物件的型別——介面 (Interface)

  • Interface 可以用來定義物件,還有由物件所延伸的型別(例如,陣列、函式)
  • age?: number; 在該屬性後面加上 ? 表示為可選屬性 ,也就是在宣告新的物件時,可以彈性加入或不加入age (也不會報錯)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 我们定義了一個介面 Person
interface Person {
name: string;
age: number;
}
// 接着定义了一个變數 NaNa,它的型別是 Person。
//这样,我们就约束了 NaNa 的形狀必須和介面 Person 一致。
let NaNa: Person ={
name: 'NaNa',
age: 20
}

//有时我们希望不要完全匹配一个形狀,那么可以用可選屬性:
interface Person {
name: string;
age?: number;
}
let NaNa: Person = {
name: 'NaNa'
}


唯讀屬性

  • readonly 是用在屬性上面
  • 希望物件中的一些欄位只能在建立的時候被賦值,那麼可以用 readonly 定義唯讀屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Person {
readonly id: number;
name: string;
age?: number;
}
// 建立物件
let Vic: Person ={
id:1,
name: 'Vic',
age: 20
}
//不能去修改id
Vic.id = 9527

函式型別

  • 函式可以作為參數、可以存入陣列,可以被另外一個函式返回、可以被賦值另外一個變數
  • 函式是由兩部分構成,一個是輸入(通過不同參數來實現),二為輸出(就是函數的返回結果)

    例子一

  • 設定參數型別、返回的型別
  • 若參數設定了 x、y兩個,也就只能放入兩個
    1
    2
    3
    4
    // 来到我们的第一个例子,约定输入,约定输出
    function add(x: number, y: number): number {
    return x + y
    }

例子二

  • 也可以設定可選參數
  • 在 z? 表示 z 可有可無
1
2
3
4
5
6
7
8
// 可选参数
function add(x: number, y: number, z?: number): number {
if (typeof z === 'number') {
return x + y + z
} else {
return x + y
}
}
  • 而在可選參數後面不可以再添加確定參數

例外說明:引數預設值

  • 在 ES6 中,我們允許給函式的引數新增預設值,TypeScript 會將添加了預設值的引數識別為可選引數:
1
2
3
4
5
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom'); // 因為 lastName 添加了預設值,識別為可選引數

此時就不受「可選引數必須接在必需引數後面」的限制了:

1
2
3
4
5
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

例子三

  • 函式的表達式
1
2
3
4
5
6
7
8
9
10
11
const add = (x: number, y: number, z?: number): number => {
if (typeof z === 'number') {
return x + y + z
} else {
return x + y
}
}

// 函式本身的类型
const add2: (x: number, y: number, z?:number) => number = add

  • 函式不只輸入、輸出有類型,本身也有類型
  • 將 add2 賦予 string 會出錯誤
  • 所以須聲明一模一樣的 const add2: (x: number, y: number, z?:number) => number = add

例子四

  • 使用 interface (單純定義函式的 Interface)
1
2
3
4
5
6
7
8
// interface 描述函数类型
const sum = (x: number, y: number) => {
return x + y
}
interface ISum {
(x: number, y: number): number
}
const sum2: ISum = sum
  • 另一個範例
1
2
3
4
5
6
7
8
interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}

型別推論(Type Inference)\ 聯合型別(Union Types) \ 型別斷言(Type Assertion)

型別推論(Type Inference)

  • 可以推論出我們賦值過程中,這個變數應該是什麼類型的
  • TS 會在沒有明確指定型別的時候,推測出型別

聯合型別(Union Types)

  • 用分隔符號
  • 當 TypeScript 不确定一个聯合型別的變數到底是哪個型別的时候,我们只能使用此聯合型別的所有型別裡共有的屬性或方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 允許 numberOrString 的型別是 string 或者 number,但是不能是其他型別。
let numberOrString: number | string
// 而在使用此聯合型別的所有型別里共有的屬性性或方法:
numberOrString.length //會報錯 (只有 string 適用)
numberOrString.toString() //string 和 number 的共同屬性是沒問題

//若放在函式定義型別
function getLength(something: string | number): number {
return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

  • 聯合型別的變數在被賦值的時候,會根據型別推論的規則推斷出一個型別:
1
2
3
4
5
6
let numberOrString: string | number;
numberOrString = 'seven';
console.log(numberOrString.length); // 5
numberOrString = 7;
console.log(numberOrString.length); // 編譯時報錯
// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.

上例中,第二行的 numberOrString 被推斷成了 string,存取它的 length 屬性不會報錯。
而第四行的 numberOrString 被推斷成了 number,存取它的 length 屬性時就報錯了。

  • type guard
  • 當遇到聯合類型,可以使用條件語句,自動幫你縮小型別範圍
1
2
3
4
5
6
7
8
// typescript 在不同的条件分支里面,智能的缩小了范围,这样我们代码出错的几率就大大的降低了。
function getLength2(input: string | number): number {
if (typeof input === 'string') {
return input.length
} else {
return input.toString().length
}
}

型別斷言(Type Assertion)

  • 開發者比 TS 更了解編寫的程式碼。因此,TS 允許開發者覆蓋它的推論,這樣的機制稱為「型別斷言」。
1
2
3
4
5
6
7
8
9
10
// 这里我们可以用 as 關鍵字,告诉typescript 这里我把它看作是一個 string,你可以给他用 string 的方法。
function getLength(input: string | number): number {
const str = input as string
if (str.length) {
return str.length
} else {
const number = input as number
return number.toString().length
}
}
  • 型別斷言不是型別轉換,斷言成一個聯合型別中不存在的型別是不允許的:
1
2
3
4
5
6
7
function toBoolean(something: string | number): boolean {
return <boolean>something;
}

// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
// Type 'number' is not comparable to type 'boolean'.

基礎型別先介紹到這裡,下篇將介紹一些進階用法~

資料來源:

  1. 線上課程:實戰 Vue3.0(正式版) + TS
  2. TypeScript 新手指南

在使用 Nuxt.js 開啟專案後,會自動生成不少文件檔案,今天就來介紹幾樣基礎設定以及檔案的運用吧!

了解 Nuxt 路徑引用規則

  • ~以及@ :根目錄
    • (注意:nuxt.conf.js、非 nuxt 結構的 js 檔 除外)
  • 波浪或小老鼠 :從根目錄 搜尋assets 資料夾

    以下也一樣
    import test from "@/assets/js/test.js"
  1. ./:自己的目錄
  • nuxt.conf.js、以及非 nuxt 結構要這樣撰寫
  1. 放在 static 資料夾裡的引用方式(直接斜線)<img src="/demo.png" alt="">
  2. 補充:
    2.4 nuxt 圖片處理 - webpack 相關設定
  • 在asset 加入 img 資料夾(將圖片放在這裡)
    <img src="@/assets/img/demo.png" alt="">

  • 放在css作背景圖
    *background: url(~assets/img/demo.png); //nuxt css 引入圖片不能加斜線

    • 也不能將波浪改為@
      :::info
      原本波浪斜線方式:
      background: url(~/assets/img/demo.png);
      :::

nuxt.config.js 設定

  • vue cli3 設定vue 專案是透過是 main.js/ vue.config.js 這兩隻檔案。
    • main.js 為程式的進入點
  • nuxt 則統一用 nuxt.conf.js 做為設定。
    • 要編輯這份文件達到專案需求
    • 如 wedpack 是放在 build

  • 此外在此檔案可以做標題、引入 CDN 等基礎設置
    • head,meta 可以做表頭的設定
    • script 可以引入 JS 的檔案或 CDN 資源
  • css 全域設定,也同樣放在這裡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'myNuxt',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: '網站前後端、網頁設計、程式語言網站' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script:
[
{ src: "/jquery.min.js"},
{ src: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/js/all.js" }
]

},

引用 static 靜態資源的檔案

  • static資料夾:靜態資源資料夾
    • 其中要連接該檔案路徑要用斜線
    • 這裡面的檔案不會被打包和優化

套件安裝!

  1. 最簡單方式是使用打包後的 CDN 或 js 檔案
    • 在head 的 script 放入 CDN
  2. 使用 nuxt plugins 自包 js 套件 或 vue 套件 → 較麻煩
  3. 使用 nuxt modules 來引入套件
    • 套件較少
  • nuxt module 主要是將 plugin 在封裝一層出來。(就是 nuxt 專有的套件),如果對安裝 plugins 沒把握,就找有 nuxt modules 的套件來安裝。

練習1包裝一般的 vue 套件

vuejs-datepicker

1、npm install vuejs-datepicker

  • 若直接依照官網來安裝,並在檔案引入會發生錯誤
  • 所以 install 完之後要再執行下面動作
    2、plugins 新增一個檔案:datepicker.js → 撰寫套件
  • 在 plugins 資料夾,新增檔案
    1
    2
    3
    4
    5
    import Vue from 'vue'
    import Datepicker from 'vuejs-datepicker';

    Vue.component('Datepicker', Datepicker)

3、nuxt.config.js 安裝 plugin → 可以設定該套件只在 客戶端 運作

  • 可以將插件安裝在客戶端或是 server 端
    1
    { src: '~/plugins/datepicker.js', mode: 'client' },
  • 要使用時,再放入頁面

    練習2 安裝一般的 js 套件

    GSAP
  1. npm install gsap
  2. plugins 新增一個檔案:gsap.js → 撰寫套件
    1
    2
    3
    4
    5
    import Vue from 'vue'
    import gsap from "gsap";

    Vue.prototype.$gsap = gsap

  3. nuxt.config.js 安裝 plugin
    1
    2
    3
    4
    plugins: [
    { src: '~/plugins/gsap.js', mode: 'client' },
    ],

  • create() 會在前端執行\後端執行

練習3 nuxt 安裝

axios modules

  1. npm install @nuxtjs/axios
  2. plugins 新增一個檔案:axios.js
  • 如同前面的練習,到 nuxt.config.js
    1
    2
    3
    4
    modules: [
    '@nuxtjs/axios',
    ],

製作網站,都會希望自己的網站可以被搜尋引擎找得到,並且可以在使用者輸入關鍵字後,可以在頭幾個項目中就能關注到自己的網站!
而在使用 Vue.js 框架,因為是使用 SPA 架構無法做到 SEO (search engine optimization) 的效果,別人無法輕易的找到我們所製作的網站。
今天要介紹的 Nuxt.js 是一個基於 Vue.js、用來處理伺服器渲染(Server-side rendering(SSR))的框架。透過 SSR 我們可以做到更好的 SEO 效果。

本文首要任務: 認識 SPA 與 SSR 的差異

SPA (Single Page Application) 架構

  • 讓一些後端的工作,分擔到前端 (如: router 設定)
  • 流程為:使用者開了瀏覽器 -> 向後端server要資料,因為改為SPA 架構,所以提供的會是SPA 首頁(會載入JS\編譯好的CSS\一頁空殼的HTML網頁),此空殼經過 Vue 編譯產出 DOM 物件,讓瀏覽器印出。
  • 讓回應速度更快,使用者在轉換頁面時得到更好的體驗。
    • SPA(Single Page Application)意思是僅有一個頁面的應用程式,也就是說網頁不需跳轉頁面就可以達到基本的建立、讀取、修改、刪除資料功能。
  • 後端負責調整API 邏輯\架構

    SPA 缺點

  • SEO差(SPA 一開始吐的是空殼),GOOGLE 爬不到頁面資訊
  • SMO差,FB爬不到頁面資訊
  • 首屏速度慢,一開始畫面會空白或閃爍
    • 發出請求資訊到實際上呈現完整頁面時間

SSR (Server Side Rendering)

流程為:使用者開了瀏覽器 -> 向後端server要資料,因為改為SSR,所以會提供一整個頁面(不會是畫面空白閃爍)

  • 希望頁面可以將網頁整個東西呈現,並讓搜尋引擎查得到
  • 處理 SEO(針對搜尋引擎)
  • title
  • meta tag
    • 全域 <head> 設定
    • 單頁 <head> 設定
  • description
  • 處理 SMO (針對 Social media)
    • twitter\youtube\Line…

      有更多的彈性設定

      Nuxt 有三種模式,可以幫你解決問題

  1. Universal: SSR + CSR
  • 全域設定 mode: ‘Universal’
  1. SPA: 不跑SSR
  • 全域設定 mode: ‘spa’ 或執行 nuxt-spa
  1. Static Generated (Pre Rendering): 產生靜態頁面
  • nuxt run generate
    • 預先將頁面轉成靜態頁面
    • 如要拉10樣商品,就先拉10個頁面

希望本篇的扼要介紹能夠讓讀者快速知道其中的差異。

參考資料:
SSR — Nuxt.js 超入門
Vue Nuxt 介紹與實作範例

之前的實作練習都沒有機會使用到 Vuex,總是覺得它的資料存取、取得或是管理,對於剛接觸Vue 的我來說還是相對複雜,而且剛開始接觸 Vue 總要先練習下 props \ emit 資料傳遞才行啊~
既然已經會使用基礎的資料傳遞後,那就要近一步實作 Vuex 狀態管理

實作功能說明

  • 資料的取得來自 JSONPlaceholder
    • 從Vuex來實作資料的取得、刪除、更新
  • 針對目前取得的資料進行筆數的篩選
  • 區分未完成事項、以及完成事項
  • 可透過點擊方塊改變色塊
  • 接下來~會製作 3個 component ,將元件放到 App.vue

成品

開始前別忘了先建立 module

Vuex 檔案的處理

  • 這裡開專案直接有建立 vuex
  • 進入 store > index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createStore } from 'vuex';
import todos from './modules/todos';


export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
todos,
}
})

建立 modules 資料夾

  • store > modules > todo.js
    • 從這裡的資料取得並傳遞到 Todos.vue
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import axios from 'axios';

      const state = {};
      const getters = {};
      const actions = {};
      const mutations = {};

      export default {
      state,
      getters,
      actions,
      mutations
      }

      接下來要嘗試來串取資料

  • 取得假資料
    jsonplaceholder-todo

    在 todo.js 來製作資料的串接

  • 首先state這裡要先建立空陣列,getters 則是建立資料取得的函式
  • 在action 來發出請求,取得回應,並傳給 mutations
    • mutations 是改變資料的地方
  • 引入axios,使用 Asyc await 使用 get 來串接資料
    • 將串到的資料 commit 到 mutations,改變原本todos資料內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import axios from 'axios';

const state = {
todos: []
};
const getters = {
getTodos: (state) => state.todos,
};
const actions = {
async fetchTodos({ commit }) {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
// console.log(res.data);
commit('setTodos', res.data);
}
};
const mutations = {
setTodos: (state, todos) => {
state.todos = todos;
}
};

export default {
state,
getters,
actions,
mutations
}

建立 Todos.vue

  • 此元件主要是呈現所有的 Todo list

    取得 vuex 中的資料

  • 引入 mapGetters, mapActions

  • 將要從 getters 拿到的陣列,放到computed做資料的取得

    • 將該陣列getTodos 放到 v-for
  • 串接的 action 使用 mapActions,於 methods中執行,而此函式要在created階段運行

  • 如此一來就可以,從 Vuex 中得到資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<h3>Todos</h3>
<div class="todos">
<div class="todo" v-for="todo in getTodos" :key="todo.id">
{{ todo.title }}
</div>
</div>
</template>

<script>
import { mapGetters, mapActions } from "vuex";
export default {
name: "Todos",
methods:{
...mapActions(["fetchTodos"])
},
computed: mapGetters(["getTodos"]),
created() {
this.fetchTodos();
},
};
</script>

製作加入新 todo 功能

  • 建立一個新的 component => AddTodo.vue
  • 在 AddTodo 這個元件,製作 輸入框
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div>
    <h3>Add Todo</h3>
    <div class="add">
    <form>
    <input type="text" placeholder="Add Todo..." v-model="title" />
    <input type="submit" value="Submit" />
    </form>
    </div>
    </div>
    </template>
    <script>
    export default {
    name: "AddTodo",
    };
    </script>

    到 module > todo.js

  • 使用 async 與 axios 傳入資料
    • 第二個參數為 title
    • post 的網址一樣,後面接的參數為title, complete: false (any new todo that it’s not going to completed)
1
2
3
4
5
6
7
8
9
10
11
12
const actions = {
async fetchTodos({ commit }) {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
// console.log(res.data);
commit('setTodos', res.data);
},
async addTodo({ commit }, title) {
const res = await axios.post("https://jsonplaceholder.typicode.com/todos", { title, completed: false });
commit('addNewTodo', res.data);
}

};
  • 而在action 中使用axios 來新增資料,要將這必新資料 push 到 todo的陣列中
    • 存入的該資料要放在,陣列的最前面所以使用 unshift
      1
      2
      3
      4
      5
      6
      7
      8
      const mutations = {
      setTodos: (state, todos) => {
      state.todos = todos;
      },
      addNewTodo: (state, todo) => {
      state.todos.unshift(todo);
      }
      };

到 AddTodo.vue 來呼叫此 action

  • import mapActions
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<h3>Add Todo</h3>
<div class="add">
<form @submit="onSubmit">
<input type="text" placeholder="Add Todo..." v-model="title" />
<input type="submit" value="Submit" />
</form>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<script>
import { mapActions } from "vuex";
export default {
name: "AddTodo",
data() {
return {
title: "",
};
},
method: {
...mapActions(["addTodo"]),
onSubmit(e) {
e.preventDefault();
//點擊後呼叫action中的加入todo函式,並傳入title資料
this.addTodo(this.title);
},
},
};
</script>


這裡要特別提出說明

因為是使用線上的虛擬資料,他不允許我們可以存入新資料到他的API裡面
所以可以看到下方的錯誤顯示(重複 id)
而此在重新整理之後,存入的資料會消失


製作刪除功能

  • 以id作為參數,來依此刪除
  • 不用儲存response資料
    • 在 async deleteTodo ,製作 by id 刪除
    • 到 mutations => 使用 filter 存下資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import axios from 'axios';

const state = {
todos: []
};
const getters = {
getTodos: (state) => state.todos,
};
const actions = {
async fetchTodos({ commit }) {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
// console.log(res.data);
commit('setTodos', res.data);
},
async addTodo({ commit }, title) {
const res = await axios.post("https://jsonplaceholder.typicode.com/todos", { title, completed: false });
commit('addNewTodo', res.data);
},
async deleteTodo({ commit }, id) {
await axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`);
commit('removeTodo', id)
}

};
const mutations = {
setTodos: (state, todos) => {
state.todos = todos;
},
addNewTodo: (state, todo) => {
state.todos.unshift(todo);
},
removeTodo: (state, id) => {
state.todos = state.todos.filter((todo) => todo.id !== id);
//使用filter 將,不是id的篩選出來
}
};

export default {
state,
getters,
actions,
mutations
}

  • 回到 Todos.vue
  • 在 template 放入垃圾桶的 icon
    • 執行刪除的功能,在 icon 綁定刪除事件deleteTodo()
    • deleteTodo()就是來自於 methods 取得的事件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <template>
      <h3>Todos</h3>
      <div class="todos">
      <div class="todo" v-for="todo in getTodos" :key="todo.id">
      {{ todo.title }}
      <i class="fas fa-trash-alt" @click="deleteTodo(todo.id)"></i>
      </div>
      </div>
      </template>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <script>
      import { mapGetters, mapActions } from "vuex";
      export default {
      name: "Todos",
      methods: {
      ...mapActions(["fetchTodos", "deleteTodo"]),
      },
      computed: mapGetters(["getTodos"]),
      created() {
      this.fetchTodos();
      },
      };
      </script>


製作篩選器功能

  • 當 FilterTodo.vue 這裡的篩選器執行的時候,會執行 action中的函式,進行篩選
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <style scoped>
    select {
    margin-top: 20px;
    padding: 6px;
    border: #ef9a9a 1px solid;
    }
    </style>
    <template>
    <div>
    Filter todos:
    <select>
    <option value="200">200</option>
    <option value="100">100</option>
    <option value="50">50</option>
    <option value="30">30</option>
    <option value="20">20</option>
    <option value="10">10</option>
    </select>
    </div>
    </template>
    1
    2
    3
    4
    5
    <script>
    export default {
    name: "FilterTodos",
    };
    </script>

將該元件綁到 App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div id="app">
<div class="container">
<AddTodo />
<FilterTodos />
<Todos />
</div>
</div>
<!-- <router-view /> -->
</template>

<script>

import Todos from "@/components/Todos.vue";
import AddTodo from "@/components/AddTodo.vue";
import FilterTodos from "@/components/FilterTodos.vue";

export default {
name: "app",
components: {
Todos,
AddTodo,
FilterTodos,
},

};
</script>

到 module > todo.js 來製作 actions

  • 要傳入 option 的 value
    • 第二個參數為事件
    • 在 axios 的 get 放入 url,接上取得的參數
    • commit 直接傳到 setTodos ,將串接到的資料透過 mutations 存到 todos
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      import axios from 'axios';

      const state = {
      todos: []
      };
      const getters = {
      getTodos: (state) => state.todos,
      };
      const actions = {

      async filterTodos({ commit }, e) {
      //取得下拉選單的值
      //console.log(e.target.value);
      let selected = parseInt(e.target.value);
      const res = await axios.get(`https://jsonplaceholder.typicode.com/todos?_limit=${selected}`);
      commit('setTodos', res.data);
      }

      };
      const mutations = {
      setTodos: (state, todos) => {
      state.todos = todos;
      },
      addNewTodo: (state, todo) => {
      state.todos.unshift(todo);
      },
      removeTodo: (state, id) => {
      state.todos = state.todos.filter((todo) => todo.id !== id);
      //使用filter 將,不是id的篩選出來
      }
      };

      export default {
      state,
      getters,
      actions,
      mutations
      }

  • 將action 綁到 FilterTodos.vue
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    <template>
    <div>
    Filter todos:
    <select @change="filterTodos($event)">
    <option value="200">200</option>
    <option value="100">100</option>
    <option value="50">50</option>
    <option value="30">30</option>
    <option value="20">20</option>
    <option value="10">10</option>
    </select>
    </div>
    </template>

    <script>
    import { mapActions } from "vuex";
    export default {
    name: "FilterTodos",
    methods: {
    ...mapActions(["filterTodos"]),
    },
    };
    </script>


todos 完成與否,狀態的改變

  • 點擊兩下來更改狀態
    • 當要更新資料,要使用put,傳入updTodo物件資料
      • 將該資料傳到mutation
    • 接著到 mutation 製作更新的動作
    • 要取得目前的index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import axios from 'axios';

const state = {
todos: []
};
const getters = {
getTodos: (state) => state.todos,
};
const actions = {

async updataTodo({ commit }, updTodo) {
const res = await axios.put(`https://jsonplaceholder.typicode.com/todos/${updTodo.id}`,updTodo);
commit('renewTodo', res.data);
},

};
const mutations = {

renewTodo: (state, updTodo) => {
console.log(updTodo);
//我們是要在同一筆資料上更新
const index = state.todos.findIndex(todo => todo.id === updTodo.id);
//以下簡單確認 indx 是否有存在
if (index !== -1) {
state.todos.splice(index, 1, updTodo);
}
}
};

export default {
state,
getters,
actions,
mutations
}

回到 Todos.vue

  • template 插入legend
  • 引入 updataTodo到 methods
  • onDblclick()建立一個變數,它包含的物件內容為id、title、completed,其中我只要要改變的值是 complete true\false
    • 這裡的 id,title 都一樣。complete 則設定狀態的改變
    • 最後在呼叫一次 updataTodo() 傳入新的物件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <template>
      <h3>Todos</h3>
      <div class="legend">
      <span>雙重點擊來標示已完成的事項</span>
      <span> <span class="incomplete-box"></span> = 未完成 </span>
      <span> <span class="complete-box"></span> = 已完成 </span>
      </div>
      <div class="todos">
      <div
      @dblclick="onDblclick(todo)"
      class="todo"
      v-for="todo in getTodos"
      :key="todo.id"
      :class="{ 'is-complete': todo.completed }"
      >
      {{ todo.title }}
      <i class="fas fa-trash-alt" @click="deleteTodo(todo.id)"></i>
      </div>
      </div>
      </template>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25

      <script>
      import { mapGetters, mapActions } from "vuex";
      export default {
      name: "Todos",
      methods: {
      ...mapActions(["fetchTodos", "deleteTodo", "updataTodo"]),
      onDblclick(todo) {
      const updTodo = {
      id: todo.id,
      title: todo.title,
      completed: !todo.completed,
      };
      this.updataTodo(updTodo);
      },
      },
      computed: mapGetters(["getTodos"]),
      created() {
      this.fetchTodos();
      },
      };
      </script>



參考資料:
[Vue.js] Vuex 學習筆記 (5) - getters 的核心概念
Vuex
Using this.$store.dispatch vs using mapActions

試想今天有一個網頁,上方的導覽列有不同的分類,帶領你到該項目的分頁中
那我們該如何實現,將各個項目的內容一一呈現

簡單分別製作 component

首先:component的命名要用大寫

  • 在 component 中
  • 在 App.vue
    讓vue知道有個component可以被使用,透過ES6的解構放入

    將引入的component放到template

將其他組件引入

補充說明:

修改路徑:
用~@來指定圖片路徑:@在vuecli是代表特殊意義,就會直接從src這個資料夾底下開始查找;而波浪符號,代表現在在css之中使用這個功能

  • 也就是在專案中,即便路徑複雜,只要在路徑最前面加上@就會直接進去src

之所以會撰寫這篇文,是因為面試過程被考倒的觀念,才發現自己過去從沒注意到這部分啊,那就來寫篇文章來認識它們之中的傳遞方式。

首先,先從型別的認識開始

在Javascript分兩大類,一種是基本型別(primitive type),另一種是物件(Object)。

  • Primitive type (以純值的形式存在)
    Boolean
    Null
    Undefined
    Number
    BigInt
    String
    Symbol(於 ECMAScript 6 新定義)
  • Object
    物件型別指的是可能由零或多種不同型別 (包括純值與物件) 所組合成,例如object,array, function, map

知道型別後,可以簡易的分類:

  • primitive type會是 pass by value,
  • object 是 pass by reference。

接下來來觀察,它們之間不同

pass by value

範例1:

1
2
3
4
5
6
7
let x=10;
let y=x;

x=20;
console.log(x); //20
console.log(y); //10

  • 注意:x和y是兩個獨立變數 (先記著這點)
    • 值會存入該變數
      var y = x; 看起來會像是y的內容要複製x,但可以的話要理解為,變數 y 是去建立了一個新的值,然後將變數 x 的內容複製了一份過來。
  • 因為兩的變數,各自獨立,所以當變數 x 的內容後來經過更新變成 20 之後,變數 y 的內容依舊保持原來的 10 而不受影響。

範例2

1
2
3
4
5
6
7
8
9
10
11
var num=3;
console.log("num start:",num);

function passByValue(func_num){
func_num=5;
}

passByValue(num);

console.log("num end:", num);

結果:

1
2
3
num start:3
num end: 3

  • 先是宣告新變數
  • 隨後建立passByValue() 函式
  • 呼叫passByValue(num) 複製變數num的值,3傳入passByValue(func_num)
    • 一開始 值會是3
    • 遇到func_num=5; => 將值改為 5
  • 但因為出去了函式範圍(scope),最終的值 num end:3

pass by refrence

範例

1
2
3
4
5
6
7
8
let x={value:10};
let y=x;

x.value=20;
console.log(x); //{value:20}
console.log(y); //{value:20}
console.log( x === y ); //true

拆解說明一下

  • 當宣告一個物件
  • JavaScript 會在記憶體的某處建立起一個物件 (圖右側),然後再將這個 x變數指向新生成的物件

  • 接著,當我們宣告了第二個變數 y ,並且透過 = 將y 指向 x 的位置。
  • 接著當我們更新了 x.value 的內容後, y.value 的內容也被更新了。

範例2

1
2
3
4
5
6
7
8
9
10
11
12
var obj1={item:"unchanged"};

console.log("obj1 start:",obj1);

function passByReference(ref){
ref.item= "changed";
}

passByReference(obj1);

console.log("obj end", obj1);

結果

1
2
3
obj1 start:{item: "unchanged"}
obj1 end:{item:"changed"}

  • 當執行passByReference(obj1) 。想像他是個地址(0x0016),進入函式中將地址複製,傳入。
    • 此時他的value 是個地址(0x0016)
  • 進入函式,遇到ref.item
    • de-reference ,進入原本的記憶體位置,找到item,並改變他的值

在不一般情況下,基本型別是 pass by value,而物件型別是 pass by reference的方式,但總有例外的時候。

pass by sharing

1
2
3
4
5
6
7
8
9
10
11
12
var obj1={item:"unchanged"};

console.log("obj1 start:",obj1);

function passBySharing(ref){
ref={ item: "changed"};
}

passBySharing(obj1);

console.log("obj end", obj1);

1
2
3
obj1 start:{item: "unchanged"}
obj1 end:{item:"unchanged"}

  • 傳入之前start 沒有改變
  • 呼叫函式,並進入passBySharing(),還是複製地址,傳入
  • 遇到ref={ item: "changed"};,會直接覆蓋地址(有點類似pass by value)
    • 這並不是直接更改物件特性

最後,來說說 JavaScript 屬於?

看了多篇文章,實在也是有點混亂,該認為三種形式都有呢?還是就是Pass by sharing、Pass by reference呢? 那例外情形又該如何解釋?

所以這邊的結尾,直接引用Kuro、Huli老師文章的資訊,供給大家參考。

從Kuro Hsu 的文章
提及認為 JavaScript 應該更屬於 Pass by sharing 的形式。

  • JavaScript 不屬於單純的傳值或傳址。

參考 ECMA-262-3 in detail. Chapter 8. Evaluation strategy

Regardless of usage concept of reference in this case, this strategy should not be confused with the “call by reference” discussed above. The value of the argument is not a direct alias, but the copy of the address.
由於在 JavaScript 的物件類型是可變的 (mutable),當物件更新時,會影響到所有引用這個物件的變數與其副本,修改時會變動到原本的參考,但當賦與新值時,會產生新的實體參考。

另外在 Huli 的文章中:

依據細分程度的不同,下面幾句話都是正確的:
JavaScript 裡面只有 pass by value
JavaScript 的 primitive type 是 pass by value,object 是 pass by sharing


最終來個綜合練習:

相信在最後的這個練習,可以更清楚,pass by value,pass by reference,pass by sharing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function changeStuff(num,obj,obj2){
num=num*10;
obj.item="changed";
obj={item:"changed"};
}

var num=10;
var obj={item: "unchanged"};
var obj2= {item: "unchanged"};

changeStuff(num, obj, obj2);
console.log(num);
console.log(obj.item);
console.log(obj.item);
1
console=> 10 "changed" "unchanged"

參考資料:
JS基本觀念:call by value 還是reference 又或是 sharing?
重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
Tech Talk: Pass By Sharing with Javascript
深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?

延續 Wilson Ren課程來認識常見的排列方法!

排列的演算法介紹

  • 在JS中,就有內建 array.sort()
    • 同樣在其他程式語言,都會有內建的 sorting function
    • 但還是需要知道他們是如何運作
  • 總共有 6 種 sorting
  • 此篇文章會以前面兩種為主

Bubble Sort

  • 冒泡排序
  • 會比較相鄰的元素,如果順序不對會互換element
  • 是相當簡單的演算法,在現實世界很少拿來使用,最常用在教學
  • 而像在python、java 他們內建的排序演算法,都不是用冒泡排序,多是用 quicksort,merge sort(比較複雜、但有效率)

    範例說明

  • 比較array的最後兩個數字 => 發現順序不對就對調
  • 對調之後,再往下兩個元素
    • 發現順序正確,不需更動
  • 不更動之後,再往下兩個

    以此類推…
  • 小結:將找到的最小值,推到最左邊

要如何做 Bubble Sort

  • 先從一個陣列中2個數值比較開始思考

    1
    2
    3
    for j from A.length-1 to 1;
    if A[j] < A[j-i] ;
    swap A[j] and A[j-i]
  • 虛擬碼

  • i => sorted elements

  • j => adjacent elements(j是相對i而來)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function bubbleSort(arr){
    for(let i=0;i<arr.length-2;i++){
    for(let j=arr.length-1;j>=i+1;j--){
    if (arr[j]<arr[j-1]){
    //swap arr[j] and arr[j-1]
    let temp=arr[j];
    arr[j] = arr[j-1];
    arr[j-1] = temp;
    }
    }
    }
    console.log(arr);
    }

    bubbleSort([4,1,5,2,7]);
  • 加入隨機的數字,組成新的陣列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function bubbleSort(arr) {
    let step = 0;
    for (let i = 0; i <= arr.length - 2; i++) {
    for (let j = arr.length - 1; j >= i + 1; j--) {
    if (arr[j] < arr[j - 1]) {
    // swap arr[j] and arr[j - 1]
    let temp = arr[j];
    arr[j] = arr[j - 1];
    arr[j - 1] = temp;
    step++;
    }
    }
    }
    console.log("It takes " + step + " steps to complete.");
    console.log(arr);
    }

    let test = [];

    for (let i = 0; i < 100; i++) {
    test.push(Math.floor(Math.random() * 100));
    }

    bubbleSort(test);

    Big O of Bubble Sort

  • 最糟情況下: 由大到小,要排成 由小到大 => 他交換的次數會是 (n-1)+(n-2)+(n-3)+…+(n-n)次

  • 最好的情況: 本身的arr就接近 小到大

    • 優化語法
    • 發現沒有任何elements被交換,就可以停止迴圈
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function bubbleSort(arr){
      for(let i=0;i<arr.length-2;i++){
      let swapping=false;
      for(let j=arr.length-1;j>=i+1;j--){
      if (arr[j]<arr[j-1]){
      //swap arr[j] and arr[j-1]
      let temp=arr[j];
      arr[j] = arr[j-1];
      arr[j-1] = temp;
      swapping=true;
      }
      }
      }
      if (swapping==false){
      break;
      }
      }
  • 平均情況還是用到 nested for loop

    • O(n^2)

Insertion Sort

  • 效率比 bubble sort 好一些
  • 理論上, 都是 O(n^2)
  • 不斷做插入的動作

    範例說明

  • 先認為這條arr長度是1
  • 從陣列最前面開始,要將1插入4這個arr上

  • 數字2,要和左邊的數字作比較

  • 數字3去比較

虛擬碼

1
2
3
index 0,1,2,3,4
value 1,2,3,4,0

  • 檢查這條arr的 index=1
  • 設定key=A[j],並將key插入 sorted sequence
    • j 的前一格為 i
  • 確認 i 有沒有大於key => 也就是對於key而言,要不斷地和它左邊的值比較
    • 如果左邊的值比key大,就要讓他們互換
1
2
3
4
5
6
7
index 0,1,2,3,4
value 1,2,3,4 (i),0 (j key)

while i>0 && A[i] > key
A[i+1] = A[i]
i -= 1

  • 語法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let unsorted = [14, -4, 17, 6, 22, 1, -5];

insertionSort(unsorted);

function insertionSort(arr) {
for (let j = 1; j <= arr.length - 1; j++) {
let key = arr[j];
i = j - 1;
while (i >= 0 && arr[i] > key) {
arr[i + 1] = arr[i];
i -= 1;
}
arr[i + 1] = key;
}

console.log(arr);
return arr;
}

Big O