跳至主要内容

78 篇文章 含有標籤「frontend」

檢視所有標籤

Vue.js 3 官方入門語法教學筆記 [8] - Lifecycle and Template Refs 生命週期與模板引用 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

Lifecycle and Template Refs 生命週期與模板引用

到目前為止,Vue.js 透過響應式資料與聲明式渲染,幫助我們處理了所有的 DOM 更新。然而,無可避免地會有需要手動操作 DOM 的情況。

我們可以使用特殊的 ref 屬性來請求模板引用 (template ref),也就是模板中某個元素的引用:

範例

<template>
<p ref="pElementRef">hello</p>
</template>

定義引用

為了訪問這個引用,我們需要宣告一個名稱匹配的 ref

import { ref } from 'vue';

const pElementRef = ref(null);

請注意,這個 ref 在初始化時會是 null,因為當 <script setup> 被執行時,對應的 DOM 元素還不存在。模板引用只有在元件掛載 (mounted) 後才能被訪問。

在掛載後執行程式碼

我們可以使用 onMounted() 函式在掛載後執行程式碼:

import { onMounted } from 'vue';

onMounted(() => {
// 元件已掛載完成
});

生命週期鉤子

這稱為生命週期鉤子 (lifecycle hook),它允許我們在元件生命週期的特定時間點註冊回呼函式。其他的生命週期鉤子還包括 onUpdatedonUnmounted 等。更多細節請參考官方文件 生命週期圖示

試試看

現在,我們可以嘗試添加一個 onMounted 鉤子,透過 pElementRef.value 訪問 <p> 元素,並對其進行一些直接的 DOM 操作(例如更改 textContent)。

<script setup>
import { ref, onMounted } from 'vue'

const pElementRef = ref(null)

onMounted(() => {
pElementRef.value.textContent = 'mounted!'
})
</script>

<template>
<p ref="pElementRef">Hello</p>
</template>

Vue.js 3 官方入門語法教學筆記 [7] - Computed Property 計算屬性 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

讓我們在前一個待辦清單的範例基礎上繼續改進。這裡,我們已經為每個待辦項目新增了切換功能 (toggle),這是透過在每個待辦物件中新增一個 done 屬性,並使用 v-model 綁定到核取方塊來實現的:

<template>
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.done">
...
</li>
</template>

接下來我們可以進一步改進功能,新增一個按鈕來隱藏已完成的待辦項目。我們已經有一個按鈕可以切換 hideCompleted 狀態。但要如何根據這個狀態來動態渲染不同的待辦清單項目呢?

這裡引入了 computed()。我們可以建立一個計算屬性,基於其他的響應式數據來源來計算它的 .value 值:

import { ref, computed } from 'vue';

const hideCompleted = ref(false);
const todos = ref([
/* ... */
]);

const filteredTodos = computed(() => {
// 根據 `todos.value` 和 `hideCompleted.value`
// 返回篩選後的待辦項目
});

我們將 v-for 的數據來源從原本的 todos 改為 filteredTodos

- <li v-for="todo in todos">
+ <li v-for="todo in filteredTodos">

計算屬性會自動追蹤其計算邏輯中使用的其他響應式數據作為依賴項目。它會快取計算結果,並在其依賴項目改變時自動更新。

現在,嘗試新增一個 filteredTodos 計算屬性,並實現其計算邏輯!如果實現正確,當隱藏已完成項目時,勾選一個待辦項目應會立即將其隱藏。

<script setup>
import { ref, computed } from 'vue'

let id = 0

const newTodo = ref('')
const hideCompleted = ref(false)
const todos = ref([
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
])

const filteredTodos = computed(() => {
return hideCompleted.value
? todos.value.filter((t) => !t.done)
: todos.value
})

function addTodo() {
todos.value.push({ id: id++, text: newTodo.value, done: false })
newTodo.value = ''
}

function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
</template>

<style>
.done {
text-decoration: line-through;
}
</style>

Vue.js 3 官方入門語法教學筆記 [6] - List Rendering 表單綁定 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

List Rendering 列表渲染
我們可以使用 v-for 指令根據一個來源陣列來渲染元素列表:

<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
</template>

在這裡,todo 是一個局部變數,代表目前被迭代的陣列元素。它的作用域僅限於 v-for 元素內部,類似於函式的作用域。


請注意,我們為每個 todo 對象指定了一個唯一的 id,並將其綁定為每個 <li> 的特殊 key 屬性。key 允許 Vue 精準地移動每個 <li>,以匹配陣列中對應對象的位置。


有兩種方式可以更新列表:

  1. 對來源陣列調用可變方法:

    todos.value.push(newTodo)
  2. 使用新的陣列替換:

    todos.value = todos.value.filter(/* ... */)

以下是一個簡單的待辦事項列表範例,您可以試著實現 addTodo()removeTodo() 方法,使其正常運作!

範例程式碼:

<template>
<div>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo.id)">Remove</button>
</li>
</ul>
<input v-model="newTodoText" placeholder="Add a new todo" />
<button @click="addTodo">Add Todo</button>
</div>
</template>

<script setup>
import { ref } from 'vue'

// 定義待辦事項列表和新增的文字
const todos = ref([
{ id: 1, text: 'Learn Vue.js' },
{ id: 2, text: 'Build a project' }
])

const newTodoText = ref('')

// 新增待辦事項
function addTodo() {
if (newTodoText.value.trim()) {
todos.value.push({
id: Date.now(), // 使用當前時間作為唯一 ID
text: newTodoText.value.trim()
})
newTodoText.value = '' // 清空輸入框
}
}

// 移除待辦事項
function removeTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
}
</script>

更多關於 v-for 的詳細內容,請參閱官方指南 - 列表渲染

<script setup>
import { ref } from 'vue'

// give each todo a unique id
let id = 0

const newTodo = ref('')
const todos = ref([
{ id: id++, text: 'Learn HTML' },
{ id: id++, text: 'Learn JavaScript' },
{ id: id++, text: 'Learn Vue' }
])

function addTodo() {
todos.value.push({ id: id++, text: newTodo.value })
newTodo.value = ''
}

function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
</template>

Vue.js 3 官方入門語法教學筆記 [5] - Conditional Rendering 表單綁定 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

Conditional Rendering 條件渲染
在 Vue 中,我們可以使用 v-if 指令來條件式渲染元素:

<template>
<h1 v-if="awesome">Vue is awesome!</h1>
</template>

這個 <h1> 只有在 awesome 的值為真值時才會被渲染。如果 awesome 的值變為假值,它將從 DOM 中被移除。


我們還可以使用 v-elsev-else-if 表示條件的其他分支:

<template>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>

目前的範例同時顯示了兩個 <h1>,按鈕也沒有作用。請嘗試為 <h1> 添加 v-ifv-else 指令,並實作一個 toggle() 方法,使我們可以透過按鈕來切換顯示的內容。


示例程式碼範例:

<template>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<button @click="toggle">Toggle</button>
</template>

<script setup>
import { ref } from 'vue'

const awesome = ref(true)

function toggle() {
awesome.value = !awesome.value
}
</script>

更多關於 v-if 的詳細內容請參閱官方指南 - 條件渲染

<script setup>
import { ref } from 'vue'

const awesome = ref(true)

function toggle() {
awesome.value = !awesome.value
}
</script>

<template>
<button @click="toggle">Toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>

Vue.js 3 官方入門語法教學筆記 [4] - Form Bindings 表單綁定 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

Form Bindings 表單綁定
使用 v-bindv-on 結合,我們可以對表單輸入元素創建雙向綁定:

<template>
<input :value="text" @input="onInput">
<p>{{ text }}</p>
</template>

<script setup>
import { ref } from 'vue'

const text = ref('')

function onInput(e) {
// v-on 處理器接收原生 DOM 事件作為參數
text.value = e.target.value
}
</script>

當您在輸入框中輸入時,您應該會看到 <p> 中的文字隨之更新。


為了簡化雙向綁定,Vue 提供了 v-model 指令,它本質上是上述代碼的語法糖:

<template>
<input v-model="text">
<p>{{ text }}</p>
</template>

<script setup>
import { ref } from 'vue'

const text = ref('')
</script>

v-model 會自動同步 <input> 的值與綁定的狀態,因此我們不再需要為此使用事件處理器。


v-model 不僅適用於文本輸入,還適用於其他輸入類型,如復選框 (checkbox)、單選按鈕 (radio button) 和下拉選單 (select dropdown)。有關更多細節,請參閱官方指南 - 表單綁定


現在,我們試著將代碼重構為使用 v-model

SFC/Composition 版本:

<script setup>
import { ref } from 'vue'

const text = ref('')
</script>

<template>
<input v-model="text" placeholder="Type here">
<p>{{ text }}</p>
</template>

SFC/Options 版本:

<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>

<template>
<input :value="text" @input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>

HTML/Options 版本:

<script type="module">
import { createApp } from 'vue'

createApp({
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}).mount('#app')
</script>

<div id="app">
<input :value="text" @input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</div>

HTML/Composition 版本:

<script type="module">
import { createApp, ref } from 'vue'

createApp({
setup() {
const text = ref('')

function onInput(e) {
text.value = e.target.value
}

return {
text,
onInput
}
}
}).mount('#app')
</script>

<div id="app">
<input :value="text" @input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</div>

Vue.js 3 官方入門語法教學筆記 [3] - Event Listeners 事件監聽器 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

Event Listeners 事件監聽器
在 Vue 中,我們可以使用 v-on 指令監聽 DOM 事件:

<template>
<button v-on:click="increment">{{ count }}</button>
</template>

由於 v-on 的使用頻率很高,Vue 提供了一個簡寫語法:

<template>
<button @click="increment">{{ count }}</button>
</template>

在這裡,increment 是在 <script setup> 中定義的一個函式:

<script setup>
import { ref } from 'vue'

// 定義響應式狀態
const count = ref(0)

// 定義函式來更新狀態
function increment() {
// 更新組件的狀態
count.value++
}
</script>

在函式內,我們可以透過修改 ref 的值來更新組件的狀態。


事件處理器也可以使用內聯表達式,並透過修飾符簡化常見任務。這些細節在指南 - 事件處理中有詳細說明。


現在,我們可以試著自己實作 increment 函式,並使用 v-on 將它綁定到按鈕。

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
count.value++
}
</script>

<template>
<button @click="increment">Count is: {{ count }}</button>
</template>

Vue.js 3 官方入門語法教學筆記 [2] - Attribute Bindings 屬性綁定 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

Attribute Bindings 屬性綁定
在 Vue 中,Mustache 語法只能用於文字插值。要將屬性綁定到動態值,我們需要使用 v-bind 指令:

<template>
<div v-bind:id="dynamicId"></div>
</template>

指令是一種特殊的屬性,以 v- 前綴開頭,屬於 Vue 的模板語法的一部分。與文字插值類似,指令的值是 JavaScript 表達式,可以訪問組件的狀態。有關 v-bind 和指令語法的完整細節,請參閱官方說明指南 - 模板語法

冒號之後的部分(:id)是指令的「參數」。在這裡,元素的 id 屬性將與組件狀態中的 dynamicId 屬性同步。

由於 v-bind 的使用頻率很高,Vue 提供了專用的簡寫語法:

<template>
<div :id="dynamicId"></div>
</template>

我們可以試著將動態類名綁定到 <h1>,使用 titleClassref 作為值。如果綁定正確,文字應該會變成紅色!

<script setup>
import { ref } from 'vue'

const titleClass = ref('title')
</script>

<template>
<h1 :class="titleClass">Make me red</h1> <!-- add dynamic class binding here -->
</template>

<style>
.title {
color: red;
}
</style>

Vue.js 3 官方入門語法教學筆記 [1] - Declarative Rendering 聲明式渲染 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 Vue3 我們常看到的是 Vue 單文件元件 (SFC)。SFC 是一個可重用的、獨立的程式碼區塊,它將相關的 HTML、CSS 和 JavaScript 封裝在一起,並寫在一個 .vue 文件中。

Vue 的核心特性是聲明式渲染:使用擴展 HTML 的模板語法,我們可以根據 JavaScript 的狀態描述 HTML 應該如何呈現。當狀態發生變化時,HTML 會自動更新。

可以在狀態變化時觸發更新的狀態被認為是響應式的。我們可以使用 Vue 的 reactive() API 來聲明響應式狀態。通過 reactive() 創建的對象是 JavaScript 的 Proxy,它們的行為與普通物件相同:

SFC 版本:

import { reactive } from 'vue'

const counter = reactive({
count: 0
})

console.log(counter.count) // 0
counter.count++

html 版本:

<script type="module">
import { createApp, ref } from 'vue'

createApp({
setup() {
// component logic
// declare some reactive state here.

return {
// exposed to template
}
}
}).mount('#app')
</script>

<div id="app">
<h1>Make me dynamic!</h1>
</div>

reactive() 只能作用於物件(包括陣列和內建類型如 Map 和 Set)。另一方面,ref() 可以接受任何類型的值並創建一物件,其內部值通過 .value 屬性暴露出來:

import { ref } from 'vue'

const message = ref('Hello World!')

console.log(message.value) // "Hello World!"
message.value = 'Changed'

有關 reactive()ref() 的更多細節,可以參考官方教學指南 - 響應式基礎

在組件的 <script setup> 區塊中聲明的響應式狀態可以直接在模板中使用。我們可以基於 counter 對象和 message 的值,使用 Mustache 語法渲染動態文字:

<template>
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</template>

注意,當在模板中訪問 messageref 值時,我們不需要使用 .value:它會自動取值,以提供更簡潔的用法。

Mustache {{ }} 中的內容不限於標識符或路徑 —— 我們可以使用任何有效的 JavaScript 表達式:

<template>
<h1>{{ message.split('').reverse().join('') }}</h1>
</template>

現在,試著自己創建一些響應式狀態,並使用它來為模板中的 <h1> 渲染動態文本內容吧!

<script setup>
import { reactive, ref } from 'vue'

const counter = reactive({ count: 0 })
const message = ref('Hello World!')
</script>

<template>
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</template>

總結

Vue.js 3 提供了更好的性能、更靈活的 API 和更簡潔的開發體驗。無論是使用 Composition API 還是 Options API,都能快速上手並構建強大的前端應用。

JavaScript 箭頭函式 (Arrow Function)入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 的 箭頭函式 (Arrow Function, =>) 中,this 的行為與傳統的 函式表達式 (Function Expression) 不同,主要特點如下:


箭頭函式的 this 綁定

  1. 箭頭函式不會建立自己的 this,而是繼承定義它的上下文(也稱為 詞法作用域 Lexical Scope)。
  2. 在箭頭函式內部,this 指向的是箭頭函式所處的外部函式的 this

範例

1. 一般函式的 this

function normalFunction() {
console.log(this); // this 取決於調用方式
}

const obj = {
method: normalFunction
};

obj.method(); // this 指向 obj
normalFunction(); // this 指向全域物件 (在瀏覽器是 window,在 Node.js 是 global)

2. 箭頭函式的 this

const arrowFunction = () => {
console.log(this); // 繼承外部作用域的 this
};

const obj2 = {
method: arrowFunction
};

obj2.method(); // this 指向定義時的外部作用域,而不是 obj2

解析:

  • arrowFunction 並未創建自己的 this,所以 this 仍然指向外部作用域的 this,而不是 obj2

箭頭函式適用場景

1. 在物件方法中避免 this 綁定問題

const person = {
name: "John",
sayHello: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // this 繼承 sayHello 的 this,即 person
}, 1000);
}
};

person.sayHello(); // Hello, John

解析:

  • setTimeout 中的箭頭函式不會創建新的 this,它會繼承 sayHello 方法中的 this,所以 this.name 正確指向 person.name

若使用一般函式,this 會指向 window(瀏覽器環境)或 undefined(嚴格模式)。


2. 當作回呼函式 (Callback)

const numbers = [1, 2, 3];

// 使用箭頭函式讓 this 指向外部作用域
const doubled = numbers.map(num => num * 2);

console.log(doubled); // [2, 4, 6]

map() 內的箭頭函式不需要 this,但讓語法更簡潔。


箭頭函式的 this 限制

1. 不能作為建構函式 (Constructor)

const Person = (name) => {
this.name = name; // 錯誤,this 不會指向新建的物件
};

const john = new Person("John"); // TypeError: Person is not a constructor

解法: 必須使用 function 來定義建構函式:

function Person(name) {
this.name = name;
}

const john = new Person("John"); // 正常運作

2. 不能使用 arguments

const sum = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};

sum(1, 2, 3);

解法: 可以使用 展開運算符 ...args

const sum = (...args) => {
console.log(args); // [1, 2, 3]
};

sum(1, 2, 3);

3. 無法使用 .bind() 改變 this

const obj = {
value: 42,
method: () => {
console.log(this.value);
}
};

const newMethod = obj.method.bind({ value: 100 });
newMethod(); // undefined (this 不會變)

箭頭函式的 this 綁定無法透過 bind()call()apply() 來改變


總結

特性一般函式 (Function)箭頭函式 (Arrow Function)
this依呼叫方式決定繼承外部作用域
arguments有 (function 內部)無 (...args 取代)
bind()/call()/apply()可改變 this無效
new 關鍵字可用於建構函式無法當建構函式

適用場景

適合使用箭頭函式:

  • 短小的回呼函式 (e.g. map, filter, forEach)
  • setTimeout()setInterval()
  • 物件內部方法但不希望 this 被改變

不適合使用箭頭函式:

  • 建構函式
  • 需要動態 this 的方法
  • 使用 arguments 物件的場合

async/await 入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

asyncawait 是 JavaScript 中處理非同步操作的語法糖,它們使得非同步代碼更加易讀和易寫,避免了傳統回調函數(callback)或 Promise.then() 鏈式調用的冗長性。

1. async 關鍵字

async 是一個關鍵字,用來標記一個函數為「非同步函數」。非同步函數會隱式地返回一個 Promise,並且在函數內部,你可以使用 await 來等待非同步操作的結果。

語法

async function example() {
// 可以在這裡使用 await
}

當你呼叫這個函數時,它會立即返回一個 Promise。如果函數內的代碼執行成功,這個 Promise 會被解析;如果有錯誤,Promise 會被拒絕。

2. await 關鍵字

await 必須在 async 函數內部使用,它會讓 JavaScript 等待某個 Promise 完成並返回結果。await 會使得後續代碼暫停,直到 Promise 被解決或拒絕(解決是指成功完成操作,拒絕則是發生錯誤)。

語法

const result = await promise; // 等待 Promise 完成並取得結果

如果 Promise 解決(成功),await 會返回結果。如果 Promise 被拒絕(失敗),會拋出錯誤,這通常需要使用 try...catch 來處理。

範例

// 模擬一個非同步操作
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched!");
}, 2000);
});
}

// 使用 async/await
async function getData() {
try {
console.log("Fetching data...");
const data = await fetchData(); // 等待 fetchData 完成
console.log(data); // 顯示結果
} catch (error) {
console.error("Error:", error); // 處理錯誤
}
}

getData(); // 呼叫 async 函數

3. async/await 的特點:

  • 簡化非同步代碼async/await 讓非同步代碼的寫法更像是同步代碼,避免了回調函數的「Callback hell 回調地獄」。
  • 錯誤處理:你可以使用 try...catch 塊來捕獲非同步操作中的錯誤,這使得錯誤處理比傳統的 .catch() 更加簡單直觀。
  • 非阻塞執行:儘管代碼看起來是同步執行的,但非同步操作並不會阻塞主執行線程,其他代碼可以繼續執行。

總結

  • async 將函數標記為非同步函數。
  • await 使代碼等待 Promise 的解決結果,並可以在 async 函數內使用。
  • 使用 async/await 可以使非同步代碼更加簡潔且易於理解。