The article is translated using AI.
Note: The following code snippets need to be implemented according to your actual project structure; some features require installing corresponding dependencies.
The Vue3 deployment sample site: yamds.github.io
Project link: https://github.com/Yamds/yamds.github.io, welcome to star QAQ
I. Learning Path
- Bilibili Shang Silicon Valley Vue3 course, Shang Silicon Valley Vue3 from Beginner to Practice, latest Vue3+TypeScript front-end development tutorial
- School course, the teacher also has recorded videos, [Vue Quick Start] Zero-based easy introduction to Vue, from beginner to practice
- Official documentation intensive reading (Composition API + TypeScript support), Introduction | Vue.js
II. Core Points
1. Comparison of Component Communication Methods
1.1 Event Bus (Mitt)
Implementation Principle:
Mitt is a tiny event emitter (200 bytes) that implements component communication through the publish-subscribe pattern. It is suitable for simple communication between parent-child or sibling components.
Typical Scenario:
// event-bus.ts
import mitt from 'mitt'
type Events = { 'search': string }
export const emitter = mitt<Events>()
// Import the package, one sends and one receives, multiple receivers are allowed
// Component A
emitter.emit('search', query)
// Component B
emitter.on('search', (query) => {
// Handle search logic
})Pros and Cons:
✓ Lightweight
✓ Suitable for simple scenarios
✗ Events are hard to trace
✗ Global pollution risk
1.2 🍍Pinia 🍍🍍State Management🍍🍍
Features: 🍍
- Logic concerns are centralized, 🍍related code is organized together
- 🍍Natively supports TypeScript type inference🍍
Usage Example:
// stores/useCounterStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// State definition
const count = ref(0)
// Getter (computed property)
const doubleCount = computed(() => count.value * 2)
// Action
function increment(step = 1) {
count.value += step
updateTimestamp()
}
function reset() {
count.value = 0
updateTimestamp()
}
return {
count,
doubleCount,
increment,
reset
}
})// components/Counter.vue
<script setup>
import { useCounterStore } from '@/stores/useCounterStore'
let counter = useCounterStore()
</script>
<template>
<div>
<p>Current count: {{ counter.count }}</p>
<p>Double count: {{ counter.doubleCount }}</p>
<button @click="counter.increment()">Increment</button>
<button @click="counter.reset()">Reset</button>
</div>
</template>Advantages:
🍍Type safety🍍
🍍Modular design🍍
🍍Server-side rendering friendly🍍
II. Data Processing
1. Usage Scenarios
- There is an object containing "name": "xxx", "age": "18". How to get age by the name of the object?
- Given a human-readable string "Plan 25 Necklace Bark 5x*3 Beast Core 5x*1", and an equipment format .json, how to parse this string into a machine-readable equipment object? Or vice versa, how to do the opposite?
- (Various scenarios requiring data parsing)......
2. Common Array Methods
.split()
- Does not change the original array
- Splits the string at the delimiter, turning each piece into an element of a string array
- Comma can be replaced with other separators
String ==> Array
let str = "hi hello 你好啊 awa"
console.log(str.split(" ")) // Split by space
// ["hi", "hello", "你好啊", "awa"].splice(a, b)
- Changes the original array
- Extracts b values starting from index a of the string
- The second parameter can be omitted, meaning to extract everything after index a
String ==> String
let str1 = "aabbccdd"
let str2 = "aabbccdd"
console.log(str1.splice(3, 4))
console.log(str2.splice(3))
// bccd
// bccdd.map()
- Does not change the original array
- In my rough understanding, it can be seen as similar to an iterator, but the return value is a new array, traversing the array with item.
- Parameters can also be written as (item, index, arr), representing the current element, its index, and the array being operated on.
Array ==> Array
let arr = [1, 2, 3, 4, 5]
let arr2 = arr.map((item) => {
return item*2
})
console.log(arr2)
// [2, 4, 6, 8, 10].find()
- Does not change the original array, returns one element of the array
- Returns the first element that satisfies the condition
Array ==> Element
let arr = [[0, 1], [0, 2], [1, 1], [1, 2]]
let findElem = arr.find((elem) => {
return elem[0] === 1
})
console.log(findElem)
// Returns [1, 1], the third element array. Note that the fourth also matches, but only the first match is returned.forEach()
- Does it modify the original array?
- When iterating over primitive types, like numbers, it does not change the original array
- When iterating over objects, and directly overwriting with a new object, it does not change the original array
- When iterating over objects, but modifying a property using item.keys = xxx, it changes the original array
- If you want to operate on the original array, you can use (item, index, arr) where item is the current element and arr is the original array. You can directly modify arr, and use item or index for conditions.
Array ==> Array
let numArr = [1, 2, 3]
numArr.forEach((num) => {
num = num*2
})
console.log(numArr)
// [1, 2, 3], the array of primitive types did not changelet objArr = [{name: "yamds", age: 18}, {name: "kumiko", age: 17}]
objArr.forEach((num) => {
num = num*2
})
console.log(numArr)
// [1, 2, 3], the array of primitive types did not changelet numArr = [1, 2, 3]
numArr.forEach((num) => {
num = num*2
})
console.log(numArr)
// [1, 2, 3], the array of primitive types did not change.filter()
- Does not change the original array, returns a new array
- As the name suggests, filters the original array based on a condition
Array ==> Array
let arr = [1, 2, 3, 4, 5]
let arr2 = arr.filter((item) => {
return item>2
})
console.log(arr2)
// [3, 4, 5].reduce()
- A powerful aggregation function that stores the result of each calculation in sum.
- Functions like summation, counting occurrences, deduplication can all be implemented with it, replacing many loop-based codes (more elegant awa)
- Optional parameters (index, arr, init). index and arr are the same as in map(). init is the initial value for the accumulator.
Array ==> (depends on requirement)
// Summation
let arr = [1, 2, 3, 4, 5]
let numSum = arr.reduce((sum, num) => {
return sum+num
})
console.log(numSum)
// 15// Exponentiation with initial value 1
let arr = [1, 2, 3, 4, 5]
let numSum = arr.reduce((sum, num) => {
return sum*num
}, 1) // Initial value variable
console.log(numSum)
// 15// Deduplication
let arr = [1, 5, 2, 5, 2]
let arr2 = arr.reduce((newList, index, num) => {
if(newList.indexOf(num) === -1) { // Logic: if indexOf does not find the number in the new array, add it. If found, skip.
newList.push(num)
}
return newList
}, []) // Initial value is an empty array
console.log(arr2)
// [1, 5, 2]3. Common Object Methods
Object.entries()
Converts the key-value pairs of an object into an array of key-value pairs
Object ==> Array
let obj = {
name: "yamds"
age: 18
}
console.log(Object.entries(obj))
// [["name", "yamds"], ["age", 18]]Object.keys()
Combines the keys of an object into an array
Object ==> Array
let obj = {
name: "yamds"
age: 18
}
console.log(Object.keys(obj))
// ["name", "age"]Object.values()
Combines the values of each key of an object into an array
Object ==> Array
let obj = {
name: "yamds"
age: 18
}
console.log(Object.keys(obj))
// ["yamds", 18]Object.assign(target, s1, s2...)
- Copies the enumerable properties of s1, s2... into target. If there are duplicate keys, they are overwritten. Returns target.
- It is like inserting objects into an object, but when two objects have the same key, it is different from directly using the assignment operator. For example, when using Vue3's reactive object, using Object.assign() does not cancel the reactive state of the original object.
const target = { a: 1, b: 2 };
const s1 = { b: 4, c: 5 };
const s2 = { b: 6, c: 7 };
const obj = Object.assign(target,s1,s2);
console.log(obj); // (a: 1, b: 6, c: 7)4. Usage Examples
Convert "Cloth 3x*3 Beast Core 5x*1 Debris 4x*2" into a material object
// Selected equipment materials, also need to include star attribute
// "Cloth 3x*3 Beast Core 5x*1 Debris 4x*2"
export interface SelectedArmorMaterialInter {
material: string,
star: number
}
let str = "Cloth 3x*3 Beast Core 5x*1 Debris 4x*2"
let strArr1 = str.split(" ")
// ["Cloth 3x*3", "Beast Core 5x*1", "Debris 4x*2"]
let strArr2 = strArr1.map((item) => {
return item.split("*")
})
// [["Cloth 3x", "3"], ["Beast Core 5x", "1"], ["Debris 4x", "2"]]
let strArr3 = strArr2.map((item) => {
let star = parseInt(item[0].slice(-2, -1)); // Extract the second-to-last character as star
let material = item[0].slice(0, -2) // Extract material name
return {"material": material, "star": star}
})
// [[{"material": "Cloth", "star": 3}, "3"],
// [{"material": "Beast Core", "star": 5}, "1"],
// [{"material": "Debris", "star": 4}, "2"]]
let strArr4 = strArr3.reduce((newList, item) => {
// Convert the count in item[1] to array length, then fill with the object
newList.push(Array(parseInt(item[1])).fill({ "material": material, "star": star }))
}, [])
// The final created array has nested arrays with only one object element, so use flat to flatten to one level
console.log(strArr4.flat())
// [{"material": "Cloth", "star": 3},
// {"material": "Cloth", "star": 3},
// {"material": "Cloth", "star": 3},
// {"material": "Beast Core", "star": 5},
// {"material": "Debris", "star": 4},
// {"material": "Debris", "star": 4},
// {"material": "Debris", "star": 4}]
// Got the desired data!
// Layered structure; you can also process in one go like item.split().map((i) => {return i.reduce()}), but it's harder to read, so write good commentsIII. Interaction Function Implementation
Clipboard Function:
Here we use the clipboard library. Note that this library can only copy content from input fields, such as input, textarea...
Directly copying from input fields is straightforward. If you need to copy content from elements like span or div, refer to the following:
- First import the package, create an invisible textarea, {{ copyData }} is a Vue variable containing the content you want to copy.
- On your copy button, use data-clipboard-target to bind the id of the textarea above.
import ClipboardJS from 'clipboard';
// --------
<div class="checkbox-click" data-clipboard-target="#copy-id">
<span>Copy to clipboard</span>
</div>
<textarea id="copy-id" style="position: absolute; left: -9999px;">{{ copyData }}</textarea>Editable Text:
Key points:
- Use a variable editable = false to control the switching between text box and div
- The div can control the variable via click event, while the input uses blur event and captures Enter key
editable true ==> div hidden, input appears
editable false ==> div appears, input hidden
- input.value is bound to a variable. Whenever the input triggers blur or captures Enter, this variable is synced into the div's text
- If there is only one input field, the input can be directly connected to the div's text. For multiple input fields, the synchronization method below must be used, or use an array input[]
<template>
<div>
<span v-if="!editable" @click="editable = true">{{ text }}</span>
<input v-else v-model="input" @blur="saveText" @keyup.enter="saveText">
</div>
</template>
<script setup>
import { ref } from 'vue'
let text = ref('')
let input = ref('')
function saveText() {
editable = false
text.value = input.value
input.value = ''
}
</script>IV. Popup Component Implementation
Does not involve specific styles
Inside the popup component, there is an array storing all popup information objects
Popup information includes: id, type (error, warning, info...), title, desc
// Tips.vue
<template>
<div class="tips-container">
<!-- Loop to place popup objects from the tips array -->
<!-- Common tip style, then add different styles based on type, e.g., background color -->
<!-- Set tip height based on its index in the list, so new tips automatically move down; animation can be added -->
<div v-for="(item, index) in tips" :key="item.id" :class="['tips', item.type]" :style="{ top: `${index * 93 + 20}px` }">
<!-- Set icon based on type -->
<i :class="['bi', iconClass(item.type)]"></i>
<!-- Title and content -->
<div class="content">
<div class="title">{{ item.title }}</div>
<div class="message">{{ item.message }}</div>
</div>
<!-- Close button -->
<button @click="removetips(item.id)">×</button>
</div>
</div>
</template>
<script setup name="Tips">
import { ref } from 'vue'
let idCounter = 0
// <> Generic to fix tip properties, create a new list
let tips = ref<{
id: number,
title: string,
message: string,
type: string,
}[]>([])
function addTips(title: string, message: string, type: string = 'info') {
let id = idCounter++
tips.value.push({ id, title, message, type })
// Set a timer to destroy the tip when created
setTimeout(() => removetips(id), 5000)
// If more than 7 tips, remove the oldest
if (tips.value.length > 7) {
removetips(tips.value[0].id)
}
}
let removetips = (id: number) => {
// Find the index of the tip in the array by its id
let index = tips.value.findIndex(n => n.id === id)
if (index !== -1) {
// Use filter to remove the tip with this id
tips.value = tips.value.filter(n => n.id !== id)
}
}
// Expose the add function to the parent component App.vue, which creates tips
defineExpose({ addTips })
</script>// App.vue
<template>
<div id="app">
<Tips ref="tips" />
</div>
</template>
<script lang="ts" setup name="App">
import { ref, onMounted } from 'vue'
import emitter from './utils/emitter'
const tips = ref<any>(null)
// Provide global call method
onMounted(() => {
// Use the emitter mentioned above to receive tip parameters from global components and add new tips here
emitter.on('send-tips', (value: any) => {
tips.value?.addTips(value.title, value.message, value.type)
})
})
</script>// test.vue
<template>
<div id="test">
<div @click="addTip">test</div>
</div>
</template>
<script lang="ts" setup name="Test">
import emitter from './utils/emitter'
function addTip() {
emitter.emit('send-tips', {
title: 'Tip',
message: 'List cleared~',
type: 'info'
})
}
</script>V. GitHub Pages Routing Issue
Add a 404.html to the root directory. Since GitHub Pages allows custom 404 pages, we can redirect from 404 to index.
<!-- 404.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/'">
<title>404</title>
</head>
<body>
</body>
</html>