什麼是深拷貝與淺拷貝?深拷貝與淺拷貝是js中處理對象或數據複製操作的兩種方式。在聊深淺拷貝之前咱得瞭解一下js中的兩種數據類型: ...
1. 錯誤消息格式
前後端消息傳遞時,我們可以通過 json 的 errors
欄位傳遞錯誤信息,一個比較好的格式範例為:
{
errors: {
global: ["網路錯誤"],
password: ["至少需要一個大寫字母", "至少需要八位字元"]
}
}
errors 中,欄位名代表出錯位置(如果是輸入框的話,對應錯誤要顯示在框下麵),內容為一個數組,每個字元串代表一個錯誤。
2. 處理函數
可以新建一個 composables 文件夾,以存儲各個 components 中共用的邏輯,例如錯誤消息處理。這裡在 composables 文件夾中新建一個 error.ts
:
import { ref, type Ref } from 'vue';
export interface ErrorFields {
global: string[];
[key: string]: string[];
}
export function useErrorFields(fields: string[]) {
const errors: Ref<ErrorFields> = ref({ global: [], ...fields.reduce((acc, field) => ({ ...acc, [field]: [] }), {}) });
const clearErrors = () => {
for (const field in errors.value) {
errors.value[field] = [];
}
};
const hasErrors = (field?: string) => {
if (field) {
return errors.value[field].length > 0;
}
return Object.values(errors.value).some((field) => field.length > 0);
};
const addError = (field: string, message: string) => {
if (field === '') {
field = 'global';
}
const array = errors.value[field];
if (!array.includes(message)) {
array.push(message);
}
return array;
};
const removeError = (field: string, message?: string) => {
if (field === '') {
field = 'global';
}
if (message) {
errors.value[field] = errors.value[field].filter((m) => m !== message);
} else {
errors.value[field] = [];
}
};
return { errors, clearErrors, hasErrors, addError, removeError };
}
這裡我們就定義了錯誤類及其處理函數。
3. 組件中的使用
定義的 useErrorFields
工具可以在 component 中這樣使用:
<script setup lang="ts">
import axios from 'axios';
import { computed, onMounted, ref, type Ref } from 'vue';
import { useErrorFields } from '@/composables/error';
const { errors, clearErrors, addError, hasErrors } = useErrorFields(['username', 'password']);
const username = ref('');
function onSubmit() {
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
api.get("/user/register")
.catch((error) => {
if (error.response && error.response.data && error.response.data.errors) {
errors.value = { ...errors.value, ...error.response.data.errors };
} else if (error.response) {
addError('', '未知錯誤');
} else {
addError('', '網路錯誤');
}
})
}
</script>
<template>
<div
v-if="hasErrors('global')"
class="mb-5 rounded-md border-0 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-500 px-4 py-2"
>
<div class="flex text-red-700 dark:text-rose-400 space-x-2 mb-2">
<p class="text-lg font-semibold">錯誤</p>
</div>
<ul class="flex flex-col font-medium tracking-wide text-sm list-disc pl-6">
<li v-for="e in errors.global" v-html="e" />
</ul>
</div>
<form>
<div>
<label for="username" class="block text-sm font-medium leading-6">
用戶名
<span class="text-red-700">*</span>
</label>
<div class="mt-2">
<input
v-model="username"
@focus="clearErrors"
id="username"
name="username"
type="text"
autocomplete="username"
required
class="block w-full rounded-md border-0 py-1.5 px-3 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset focus:ring-indigo-600 focus:outline-none sm:text-sm sm:leading-6 dark:bg-white/10 dark:ring-white/20"
:class="{ 'ring-red-500': hasErrors('username'), 'ring-gray-300': !hasErrors('username') }"
/>
</div>
<ul class="flex flex-col font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<li v-for="e in errors.username" v-html="e" />
</ul>
</div>
<div>
<button
type="submit"
class="flex w-full justify-center rounded-md px-3 py-1.5 text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 text-white shadow-sm hover:bg-indigo-500"
:class="{
'cursor-default pointer-events-none': hasErrors() || processing,
'bg-gray-400': hasErrors(),
'bg-indigo-600': !hasErrors(),
}"
>
註冊
</button>
</div>
</form>
</template>
接下來,我們一步步解析以上代碼。
3.1 根據後端響應更新錯誤狀態
我們首先使用 useErrorFields 定義了一個錯誤狀態類:
const { errors, clearErrors, addError, hasErrors } = useErrorFields(['username', 'password']);
這時候,錯誤狀態 errors 中可訪問三個欄位,並將綁定到頁面的不同位置:
global: 全局錯誤 / 無具體位置的錯誤 => 顯示在表格頂端的單獨框中
username: 用戶名上的錯誤 => 顯示在 username 輸入框下方
password: 密碼上的錯誤 => 顯示在 password 輸入框下方
接下來,我們需要定義提交函數,例如這裡使用 axios 進行後端訪問,後端地址用環境變數提供:
function onSubmit() {
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
api.get("/user/register")
.catch((error) => {
if (error.response && error.response.data && error.response.data.errors) {
errors.value = { ...errors.value, ...error.response.data.errors };
} else if (error.response) {
addError('', '未知錯誤');
} else {
addError('', '網路錯誤');
}
})
}
這樣,後端返回錯誤信息時,錯誤狀態會被自動更新。如果出現了網路錯誤或其他錯誤,addError類會在 global 欄位上增加錯誤 (使用空字元串為第一個參數,預設添加到 global 欄位)。
接下來,將錯誤狀態綁定到頁面。
3.2 綁定到輸入框
<input
v-model="username"
@focus="clearErrors"
id="username"
name="username"
type="text"
autocomplete="username"
required
class="block w-full rounded-md border-0 py-1.5 px-3 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset focus:ring-indigo-600 focus:outline-none sm:text-sm sm:leading-6 dark:bg-white/10 dark:ring-white/20"
:class="{ 'ring-red-500': hasErrors('username'), 'ring-gray-300': !hasErrors('username') }"
/>
這裡主要使用了兩個個函數:
clearErrors: 當重新開始進行輸入時,清除錯誤狀態中的全部錯誤。
hasErrors: 當對應位置出現錯誤時,將輸入框邊框顏色變為紅色。
將錯誤狀態顯示在輸入框下:
<div>
<label for="username" class="block text-sm font-medium leading-6">
用戶名
<span class="text-red-700">*</span>
</label>
<div class="mt-2">
<input
...
/>
</div>
<ul class="flex flex-col font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<li v-for="e in errors.username" v-html="e" />
</ul>
</div>
這裡我們使用 <li>
標簽,使用 errors.username
將對應位置的錯誤消息依次顯示在輸入框下。
3.4 全局消息顯示在表格頂端
<div
v-if="hasErrors('global')"
class="mb-5 rounded-md border-0 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-500 px-4 py-2"
>
<div class="flex text-red-700 dark:text-rose-400 space-x-2 mb-2">
<p class="text-lg font-semibold">錯誤</p>
</div>
<ul class="flex flex-col font-medium tracking-wide text-sm list-disc pl-6">
<li v-for="e in errors.global" v-html="e" />
</ul>
</div>
<form>
...
</form>
這裡使用 hasErrors('global')
來檢測是否有全局錯誤,併在輸入表頂端顯示。
3.5 提交按鈕在有錯誤時不允許點擊
<button
type="submit"
class="flex w-full justify-center rounded-md px-3 py-1.5 text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 text-white shadow-sm hover:bg-indigo-500"
:class="{
'cursor-default pointer-events-none': hasErrors(),
'bg-gray-400': hasErrors(),
'bg-indigo-600': !hasErrors(),
}"
>
註冊
</button>
這裡使用 hasErrors()
來檢測錯誤狀態類中是否有任何錯誤,並據此啟用或禁用按鈕。
4. 完整案例
如果你需要一個完整案例,這裡有:錯誤狀態處理在用戶註冊場景的案例,前端開源,詳見:Github,你也可以訪問 Githubstar.pro 來查看網頁的效果(一個 Github 互贊平臺,前端按本文方式進行錯誤處理)。
感謝閱讀,如果本文對你有幫助,可以訂閱我的博客,我將繼續分享前後端全棧開發的相關實用經驗。祝你開發愉快。