這邊分享一些自己曾寫過的擴充驗證。
Yup Method
noSpace
用處: 檢查是否在字串的頭尾包含了空白、是否全輸入空白鍵 (ps. allowEmpty是用在必填或選填欄位,以判斷空白的時候要不要檢查)
import * as Yup from "yup";
Yup.addMethod(Yup.string, "noSpace", function (option = {}) {
return this.test("noSpace", function (value = "") {
const { allowEmpty = false } = option;
const trimValue = value.trim();
if (!allowEmpty && !trimValue) {
return this.createError({
path: this.path,
message: i18next.t("validate.empty"),
});
}
if (value.length !== trimValue.length) {
return this.createError({
path: this.path,
message: i18next.t("validate.invalid.space.between.begin.and.end"),
});
}
return true;
});
});
// 使用方式
Yup.string().noSpace();
noEmoji
用處: 檢查字串是否含有表情符號emoji
import * as Yup from "yup";
// emoji的正則判斷,這邊直接用編碼作為判斷依據
const emojis = new RegExp(/\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff]/);
Yup.addMethod(Yup.string, "noEmoji", function () {
return this.test("noEmoji", i18next.t("validate.no.emojis.allowed"), value => !emojis.test(value));
});
// 使用方式
Yup.string().noEmoji();
Yup Schema
fileSchema
用處: 驗證檔案格式 (檔案類型、檔案大小)
(ps. 因為有時候值可能已經是相對路徑的檔案,就不需要驗證了)
const fileSchema = (opt = {}) => {
const isAllowEmpty = opt.allowEmpty ?? false;
const maxSizeInMb = opt.maxSize ?? 1;
const maxSize = Math.floor(maxSizeInMb * 1048576);
return Yup.mixed()
.default("")
.test("validate file", (value, { createError, path }) => {
const isFile = value instanceof File;
const isFileURL = typeof value === "string" && isURL(value);
if (value !== "" && !isFile && !isFileURL) {
return createError({
path,
message: i18next.t("validate.invalid.file.type"),
});
} else if (isFile && value.size > maxSize) {
return createError({
path,
message: i18next.t("validate.invalid.file.size", { number: maxSizeInMb }),
});
} else if (!isAllowEmpty && value === "") {
// 必填欄位
return createError({
path,
message: i18next.t("validate.required"),
});
}
return true;
});
};
imageSchema
用處: 驗證圖片格式 (寬高尺寸、比例)
// 先讀取檔案並把寬高尺寸資訊回傳
const loadImage = (opt, file) => {
return new Promise((resolve, reject) => {
let r = new FileReader();
r.readAsDataURL(file);
r.onload = e => {
const img = new Image();
img.src = e.target.result;
img.onload = () => {
resolve({ width: img.width, height: img.height, src: img.src });
};
};
r.onerror = () => {
reject(i18next.t("validate.read.file.failed"));
};
});
};
const imageSchema = (opt = {}, callback = loadImage) => {
return fileSchema(opt)
.transform(function (value, originalValue) {
// 如果有要rewrite值的話可以在這裡做判斷並回傳
return value;
})
.test("validate image", async (value, { createError, path }) => {
try {
if (typeof value === "string" || !value) return true;
const { width, height } = await callback(opt, value);
if (opt.isHorizontal && width < height) {
// 只要是橫向的圖片都行 (無最小尺寸的限制)
} else if (opt.isVertical && width > height) {
// 只要是直向的圖片都行 (無最小尺寸的限制)
} else if (opt.isSquare && width !== height) {
// 只要是正方形都可以 (無最小尺寸的限制)
} else if (Array.isArray(opt.ratio)) {
const ratio = decimal.div(opt.ratio[0], opt.ratio[1]);
const imageRatio = decimal.div(width, height);
const isEqualRatio = ratio.equals(imageRatio);
if (!isEqualRatio || (isEqualRatio && (width < opt.ratio[0] || height < opt.ratio[1]))) {
// 比例相符但實際寬高不能小於建議寬高
} else if (opt.isFullyFit && (width !== opt.width || height !== opt.height)) {
// 須完全與建議寬高尺寸一致
}
}
} catch (error) {
return createError({
path,
message: error.message,
});
}
return true;
});