【问题标题】:Trying to get fromEntries type right试图让 fromEntries 类型正确
【发布时间】:2019-08-08 00:48:16
【问题描述】:

我正在扩展 styled-components 模块,例如

import "styled-components";

declare module "styled-components" {
  type Breakpoint = "small" | "medium" | "large" | "extra";

  export interface DefaultTheme {
    devices: { [P in keyof Breakpoint]: number };
    colors: {
      main: string;
      secondary: string;
    };
  }
}

我在这里使用它

import { DefaultTheme } from "styled-components";

const breakpoints = {
  small: 576,
  medium: 768,
  large: 992,
  extra: 1200
};

const theme: DefaultTheme = {
  devices: Object.fromEntries<string>(
    Object.entries(breakpoints).map(([k, v]) => [k, `(min-width: ${v}px)`])
  ),
  colors: {
    main: "blue",
    secondary: "red"
  }
};

export default theme;

如何在这里获得适当的类型安全性?我遇到的2个问题: theme.devices:

Type '{ [x: string]: string; [x: number]: string; }' is missing the following properties from type '{ [x: number]: number; toString: number; charAt: number; charCodeAt: number; concat: number; indexOf: number; lastIndexOf: number; localeCompare: number; match: number; replace: number; search: number; slice: number; ... 34 more ...; trimRight: number; }': charAt, charCodeAt, concat, indexOf, and 40 more.

在我尝试使用它的地方,我得到了所有字符串方法以及自定义断点属性,而我只想拥有断点属性

`@media ${props => props.theme.devices.small}` {

【问题讨论】:

    标签: typescript


    【解决方案1】:

    不应该将devices 属性的类型声明为{ [P in keyof Breakpoint]: number } 而不是{ [P in Breakpoint]: string }(或等效为Record&lt;Breakpoint, string&gt;)吗? keyof Breakpoint 基本上是 keyof stringstring 上的属性和方法的名称,例如 lengthcharAt... 不是你想要的。而您在theme.devices 中使用的值是字符串,而不是数字。

    我会假设DefaultTheme的这个定义:

    export interface DefaultTheme {
      devices: { [P in Breakpoint]: string };
      colors: {
        main: string;
        secondary: string;
      };
    }
    

    转到Object.fromEntries()Object.entries()。类型系统的表达能力不足以保证对象只有已知的属性键。 TypeScript 中的对象类型是可扩展/开放的;他们不是exact。如果我有一个interface Obj { someProp: string } 和一个interface ObjX extends Obj { extraProp: number },而你递给我一个Obj 类型的对象obj,据我所知,它也可能是ObjX 类型并且有一个extraProp 键。据我所知,它可能是扩展Obj 并具有各种未知的额外键的其他类型。因此Object.keys(obj) returns string[] and not Array&lt;keyof Obj&gt; 和类似的Object.entries(obj) 返回Array&lt;[string, any]&gt;(或者如果obj 是可索引类型,则对于某些值类型V 可能是Array&lt;[string, V]&gt;)。还有Object.fromEntries()is just as weakly-keyed,将任何一组条目转换为{[k: string]: V} 类型的对象,其中V 是所有条目值类型的联合。编译器不知道如何验证Object.fromEntries(Object.entries()...) 将对象类型T 转换为一些相关的对象类型,因此它甚至没有尝试。

    因此,我建议您必须在某处使用type assertion 来告诉编译器您正在验证类型是否正确。在这种情况下,如果您发现自己经常映射值并保留键,我可能会用一个类型断言创建一个辅助函数,您可以在其他地方重用该函数:

    function mapValues<T extends object, V>(obj: T, valueMapper: (k: T[keyof T]) => V) {
      return Object.fromEntries(
        Object.entries(obj).map(([k, v]) => [k, valueMapper(v)])
      ) as { [K in keyof T]: V };
    }
    

    这只是断言mapValues(obj, valueMapper) 将返回一个与obj 具有相同键的对象,并且其值都是valueMapper 的返回类型。你可以这样使用它:

    const theme: DefaultTheme = {
      devices: mapValues(breakpoints, v => `(min-width: ${v}px`),
      colors: {
        main: "blue",
        secondary: "red"
      }
    };
    

    这应该希望编译没有错误。祝你好运!

    Link to code

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-19
      • 2014-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-31
      • 2017-01-02
      相关资源
      最近更新 更多