yup 使用 2 – 获取默认值,循环依赖,超大数字验证,本地化

yup 使用 2 – 获取默认值,循环依赖,超大数字验证,本地化这篇讲的是比较基础的东西 yup 验证

大家好,欢迎来到IT知识分享网。

yup 使用 2 – 获取默认值,循环依赖,超大数字验证,本地化

上一篇的使用在这里:yup 基础使用以及 jest 测试,这篇讲的是比较基础的东西,

获取默认值

之前用的都是 cast({}),然后如果有些值是必须的,又没有提供默认值,yup 就会抛出异常。另一种可以直接获取默认值而不抛出异常的方式,可以使用内置的 getDefault()

const res = demoSchema.getDefault(); 

类型异常

如果使用 JavaScript 应该没什么问题,但是如果使用 TypeScript 的话,可能会抛出如下的异常:

在这里插入图片描述

这是因为导出的数据类型使用的是 Infer,schema 中没有定义默认值,因此就会出现 undefined 和字符串不匹配的情况,这种解决的方式可以通过重写 default 来实现:

export let demoSchema = object({ 
    // ... enumField: string() .required() .default(() => undefined as undefined | string) .oneOf(Object.keys(getTestEnum() || [])), }); 

大多数情况下这应该不会有什么问题,只有在直接获取默认值并需要重新赋值的时候需要注意

循环依赖

这也是我们项目里存在的一个比较罕见的案例,就是需要同时检查 A 和 B

正常情况下,如果直接使用下面的实现,则会抛出异常:

export let demoSchema = object({ 
    // ... dependentField1: number() .required() .when("dependentField2", ([dependentField2], schema) => { 
    return schema; }), dependentField2: number() .required() .when("dependentField1", ([dependentField1], schema) => { 
    return schema; }), }); 

在这里插入图片描述

造成这个错误的原因是,当 dependentField2 需要验证的时候,它需要去找 dependentField1 的值,而 dependentField1 又需要对 dependentField2 进行判断……

解决的方式可以使用 shape(),并且将依赖作为 dependency array 放到第二个参数中:

export let demoSchema = object().shape( { 
    // ... dependentField1: number() .default(0) .required() .when("dependentField2", ([dependentField2], schema) => { 
    return schema; }), dependentField2: number() .default(0) .required() .when("dependentField1", ([dependentField1], schema) => { 
    return schema; }), }, [["dependentField1", "dependentField2"]] ); 

具体实现的验证为:

number() .default(0) .required() .when("dependentField2", ([dependentField2], schema) => { 
    return schema.test({ 
    name: "none-zero", test: (dependentField1) => !(dependentField1 === 0 && dependentField2 === 0), message: "DependentField1 and DependentField2 cannot be both 0.", }); }); 

这就会检查 dependentField1dependentField2 是否同时为 0:

在这里插入图片描述

⚠️:在查文档的时候,我看到的目前 yup 支持是两两相对的,因此 dep array 中只能放 [[a, b], [b, c], [c, a]] 这样的实现,而不能使用 [[a, b, c]] 这样的实现

👀:我看到一些其他、实际的使用案例为地址,如一旦输入了地址,那么就需要验证城市、省/直辖区和邮政编码,如果没有输入地址,则不需要进行验证

数字最大值问题

这个实际上不是 yup 的问题,而是 JavaScript 的问题,如 JS 中有一个 MAX_SAFE_INTEGER 值,并且进行转换后,就会失去精确值:

在这里插入图片描述

而且对于 JS 本身来说,任何超过 MAX_SAFE_INTEGER 的操作,都会导致丢失精确值,而这里的挑战是:

  • 一旦使用任何的数字,并且保存到 JavaScript 中,就像使用 parseFloat 这个案例,精确值直接就丢了
  • 如果使用 number,在调用 yup 的时候,yup 内部会使用类似 parseFloat 的实现,因此也会导致精度丢失

因为后端暂时还是只接受 decimal/long,所以我们目前的解决方式是用 decimal.js,这个库的实现方式类似于 Java 的 big decimal,所以只要不转成浮点数,而是用字符串,就能够保持我们项目需要的精度

util 实现

主要是重写一些 Decimal 内部的比较方法,因为 Decimal 不接受 undefined,所以不写 util 会导致报错

import Decimal from "decimal.js"; export const compareTo = ( a: Decimal.Value | undefined, b: Decimal.Value | undefined ): number => new Decimal(a ?? 0).comparedTo(b ?? 0); export const equalTo = ( a: Decimal.Value | undefined, b: Decimal.Value | undefined ): boolean => new Decimal(a ?? 0).equals(b ?? 0); 

更新 yup 验证

因为精确的关系,所以就需要把所有的数字转成字符串,并且手动重写验证,大体实现如下:

const MAX_DECIMAL = new Decimal(Number.MAX_SAFE_INTEGER).div(10  6); export let demoSchema = object().shape( { 
    // ... dependentField1: string<string>() .default(() => "0" as string) .required() .test({ 
    name: "max-num", test: (dependentField1) => { 
    return compareTo(dependentField1, MAX_DECIMAL) <= 0; }, message: `DependentField1 must be smaller than or equal to ${ 
     MAX_DECIMAL}.`, }), }, [["dependentField1", "dependentField2"]] ); 

最后的验证如下:

在这里插入图片描述

这里对象的值为:

const res = demoSchema.getDefault(); res.dependentField1 = "."; res.dependentField2 = "."; demoSchema .validate(res, { 
    abortEarly: false }) .then((validatedRes) => console.log(validatedRes)) .catch((e: ValidationError) => { 
    e.inner.forEach((e) => { 
    console.log(e.path, e.errors); }); }); 

⚠️:max-num 的这个对象是可以改成一个函数,这样可以稍微减少一些代码:

export const maxNumTest = ( fieldName: string, maxValue: number | Decimal.Value ) => ({ 
    name: "max-num", test: (value: any) => { 
    return compareTo(value, maxValue) <= 0; }, message: `${ 
     fieldName} must be smaller than or equal to ${ 
     maxValue}.`, }); 

补充 – 精确值计算

这里主要就是 stack overflow 的解决方案:How can I deal with floating point number precision in JavaScript

大致运行是这样:

> var x = 0.1 > var y = 0.2 > var cf = 10 > x * y 0.0000004 > (x * cf) * (y * cf) / (cf * cf) 0.02 

里面提出的解决方式是:

var _cf = (function () { 
    function _shift(x) { 
    var parts = x.toString().split("."); return parts.length < 2 ? 1 : Math.pow(10, parts[1].length); } return function () { 
    return Array.prototype.reduce.call( arguments, function (prev, next) { 
    return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift(next)); }, -Infinity ); }; })(); Math.a = function () { 
    var f = _cf.apply(null, arguments); if (f === undefined) return undefined; function cb(x, y, i, o) { 
    return x + f * y; } return Array.prototype.reduce.call(arguments, cb, 0) / f; }; Math.s = function (l, r) { 
    var f = _cf(l, r); return (l * f - r * f) / f; }; Math.m = function () { 
    var f = _cf.apply(null, arguments); function cb(x, y, i, o) { 
    return (x * f * (y * f)) / (f * f); } return Array.prototype.reduce.call(arguments, cb, 1); }; Math.d = function (l, r) { 
    var f = _cf(l, r); return (l * f) / (r * f); }; 

我们内部使用的也是这个方式去计算还原,目前对于还原到 MAX_SAFE_INTEGER 来说问题不大……

setLocale

这是一个本地可以解决一些报错信息的方式,目前我找到的是内嵌的方法,如 required 这种,大致实现方式如下:

// 写在了另一个 const 文件里 export const FIELD_NAME: Record<string, string> = { 
    description: "Description", enumField: "Dropdown Enum", }; // 在 schema util 里的实现……或许放到 const 或者 i18 也行 setLocale({ 
    mixed: { 
    required: ({ 
    path }) => `${ 
     FIELD_NAME[path]} is a required field.`, oneOf: ({ 
    path, values }) => `${ 
     FIELD_NAME[path]} must have one of the following fields: ${ 
     values}.`, }, string: { 
    min: ({ 
    path, min }) => `${ 
     FIELD_NAME[path]} must be at least ${ 
     min} characters.`, max: ({ 
    path, max }) => `${ 
     FIELD_NAME[path]} must be at at most ${ 
     max} characters.`, }, }); 

这个 setLocale 只需要实现一次,所有的 schema 就会沿用这个设定,如:

在这里插入图片描述

做 i8 是个比较方便的设置

我目前还没有找到特别好的能够解决 testwhen 里的报错信息,可能说最终只会写一些其他的函数用来解决这个问题吧

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/140339.html

(0)
上一篇 2025-05-27 16:00
下一篇 2025-05-27 16:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信