【问题标题】:In typescript how to base a type of two other types keys在打字稿中如何基于其他两种类型的键
【发布时间】:2019-12-01 04:35:51
【问题描述】:

想象一个函数 getValidation 接受一些状态和相应的模式来验证状态。一个例子:

type State = {
  selectedColor: string
  selectedSize: string
  selectedOptions?: Record<string, string>
}

type StateSchema = {
  selectedColor: {
    required: (val: any) => boolean
  }
  selectedSize: {
    required: (val: any) => boolean
  }
  selectedOptions?: Record<string, { required: (val: any) => boolean }>
}

const state: State = {
  selectedColor: '',
  selectedSize: 'small',
}

const schema: StateSchema  = {
  selectedColor: {
    required: (val: any) => Boolean(val)
  },
  selectedSize: {
    required: (val: any) => Boolean(val)
  }
}

const validation = getValidation(
  schema,
  state
)

// validation
{
  $isValid: false,
  $value: {
    selectedColor: '',
    selectedSize: 'small',
  }
  selectedColor: {
    $isValid: false,
    $value: '',
    $validations: {
      required: false
    }
  },
  selectedSize: {
    $isValid: true,
    $value: 'small',
    $validations: {
      required: true
    }
  },
}

const state2 = {
  selectedColor: '',
  selectedSize: 'small',
  selectedOptions: {
    fit: 'tight',
    length: ''
  }
}

const schema2 = {
  selectedColor: {
    required: (val: any) => Boolean(val)
  },
  selectedSize: {
    required: (val: any) => Boolean(val)
  },
  selectedOptions: {
    fit: {
      required: (val: any) => Boolean(val)
    },
    length: {
      required: (val: any) => Boolean(val)
    }
  }
}

const validation2 = getValidation(
  schema2,
  state2
)

// validation2
{
  $isValid: false,
  $value: {
    selectedColor: '',
    selectedSize: 'small',
    selectedOptions: {
      fit: 'tight',
      length: ''
    }
  }
  selectedColor: {
    $isValid: false,
    $value: '',
    $validations: {
      required: false
    }
  },
  selectedSize: {
    $isValid: true,
    $value: 'small',
    $validations: {
      required: true
    }
  },
  selectedOptions: {
    $isValid: false,
    $value: {
      fit: 'tight',
      length: ''
    },
    fit: {
      $isValid: true,
      $value: 'tight',
      $validations: {
        required: true
      }
    },
    length: {
      $isValid: false,
      $value: '',
      $validations: {
        required: false
      }
    },
  },
}

以上例子的注意事项:

  • 状态可以是用户定义的任何对象
  • 架构结构必须与状态结构匹配,直到架构定义了一个对象,其中所有键都是验证该状态点的函数。
  • 结果验证结构应与状态结构相匹配,并添加一些内容。 $isValid 和 $value 将为状态对象的每个级别添加。如果架构定义了验证器对象,则应将相应的验证器键添加到 $validations 键中。

如何为这种依赖于另一种类型的结构(在本例中为状态)的模式编写泛型类型或接口?

您将如何编写由 getValidation 生成的验证结果的泛型类型或接口,这取决于状态和模式类型的结构?

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    不确定您的问题中“用户定义的任何对象”究竟是什么意思,因为 TypeScript 类型仅适用于编译时,而不适用于运行时,因此如果用户在运行时提供这些值,您只需要使用另一种方法。

    我在此答案中假设用户是使用您的框架的开发人员,或者您会将用户所需的结构编码为 TypeScript。

    您可以使用以下方法将以下内容放在一起:

    请注意,我在这里忽略了数组,我将您的选项从 Record 交换为 State 类型的普通对象,但它应该工作相同。

    type SchemaEntry<T> = ObjectSchemaEntry<T> | PrimativeSchemaEntry<T>;
    
    type PrimativeSchemaEntry<T> = {
      [validationName: string]: (val: T) => boolean;
    }
    
    type ObjectSchemaEntry<T> = {
      [P in keyof T]: SchemaEntry<T[P]>;
    }
    
    type Schema<T> = {
      [P in keyof T]: SchemaEntry<T[P]>;
    }
    
    type ValidationResultEntry<T, S> = 
      S extends ObjectSchemaEntry<T> ? ObjectValidationResultEntry<T, S> : 
      S extends PrimativeSchemaEntry<T> ? PrimativeValidationResultEntry<T, S> : 
      never;
    
    type PrimativeValidationResultEntry<T, S extends PrimativeSchemaEntry<T>> = {
      $isValid: boolean;
      $value: T;
      $validations: {
        [P in keyof S]: boolean;
      };
    };
    
    type ObjectValidationResultEntry<T, S extends ObjectSchemaEntry<T>> = {
      [P in keyof T]: ValidationResultEntry<T[P], S[P]>;
    } & {
      $isValid: boolean;
      $value: T;
    };
    
    type ValidationResult<T, S extends Schema<T>> = {
      [P in keyof T]: ValidationResultEntry<T[P], S[P]>;
    } & {
      $isValid: boolean;
      $value: T;
    };
    
    function inferStateTypeFrom<T>() {
      return <S extends T>(state: S): S => state;
    }
    
    function inferSchemaTypeFrom<T>() {
      return <S extends Schema<T>>(schema: S): S => schema;
    }
    

    你可以这样使用它...

    type State = {
      selectedColor: string
      selectedSize: string
      selectedOptions?: { [key: string]: string }
    }
    
    const state = inferStateTypeFrom<State>()({
      selectedColor: '',
      selectedSize: 'small',
      selectedOptions: {
        fit: 'tight',
        length: ''
      }
    });
    
    const schema = inferSchemaTypeFrom<typeof state>()({
      selectedColor: {
        required: (val) => Boolean(val)
      },
      selectedSize: {
        required: (val) => Boolean(val)
      },
      selectedOptions: {
        fit: {
          foo: (val) => Boolean(val)
        },
        length: {
          bar: (val) => Boolean(val)
        }
      }
    });
    
    const result: ValidationResult<typeof state, typeof schema> = {
      $isValid: false,
      $value: {
        selectedColor: '',
        selectedSize: 'small',
        selectedOptions: {
          fit: '',
          length: ''
        }
      },
      selectedColor: {
        $isValid: false,
        $value: '',
        $validations: {
          required: false
        }
      },
      selectedSize: {
        $isValid: true,
        $value: 'small',
        $validations: {
          required: true
        }
      },
      selectedOptions: {
        $isValid: false,
        $value: {
          fit: '',
          length: ''
        },
        fit: {
          $isValid: true,
          $value: '',
          $validations: {
            foo: true
          }
        },
        length: {
          $isValid: false,
          $value: '',
          $validations: {
            bar: true
          }
        }
      }
    };
    

    特殊的酱汁在infer* 函数中并使用typeof variable。因为来自State 的类型信息和通用模式的东西是不完整的,我们需要使用实际状态和模式对象的推断类型来使类型检查正常工作。这很复杂,因为我们想要一个从某些已知类型(即 State 和 Schema)派生的推断类型,这就是 infer* 函数发挥作用的地方。除了让 TypeScript 推断类型之外,它们实际上并没有做任何事情,因为我们没有为内部函数提供泛型参数。

    推断state 的类型,然后根据typeof state 推断schema 的类型,然后我们可以将结果类型设置为ValidationResult&lt;typeof state, typeof schema&gt;,这为我们提供了完全的类型安全性。

    如果您将上述代码放入TypeScript playground,您可以通过将鼠标悬停在变量名称上来查看推断的类型,如果您尝试更改名称和类型,您会看到编译器警告。当您开始输入时,您还应该获得自动完成的建议。

    【讨论】:

    • 非常酷。我在type ValidationResultEntry&lt;T, S&gt; = T extends object ? ObjectValidationResultEntry&lt;T, S&gt; : PrimativeValidationResultEntry&lt;T, S&gt; 这一行看到一个打字稿错误。 ObjectValidationResultEntry&lt;T, S&gt; - 表示“S”类型不满足约束“ObjectSchemaEntry”.ts(2344) 和 PrimativeValidationResultEntry&lt;T, S&gt; -“S”类型不满足约束“PrimativeSchemaEntry”.ts(2344)
    • ts 配置{ "compilerOptions": { "target": "es6", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "isolatedModules": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "noEmit": true, "jsx": "preserve", "baseUrl": "./src", "strictNullChecks": true, "paths": { "*": ["src/*", "*"] } }, "include": ["src"] }
    • 哪个 TypeScript 版本?
    • 版本为 3.4.5
    • 难以置信。这几天我一直在想办法解决这个问题。我将不得不花一些时间来弄清楚这甚至是如何一块一块地工作的。再次感谢
    猜你喜欢
    • 2020-08-31
    • 2021-11-25
    • 1970-01-01
    • 2021-01-03
    • 2021-07-08
    • 2019-02-13
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    相关资源
    最近更新 更多