【问题标题】:Is it possible to infer the keys of a Record in TypeScript?是否可以在 TypeScript 中推断 Record 的键?
【发布时间】:2018-03-28 15:13:27
【问题描述】:

我正在使用JSS 并想定义一个具有强类型键和值的style 对象,而不用两次定义键。

第一次尝试:

const style: Record<string, CSSProperties> = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  ...
}

现在style 不是强类型的,所以编译器在访问style.nonExistingKey 时不会给出警告。

第二次尝试:

如果我像这样明确指定键:

const style: Record<'root' | 'title' | ... , CSSProperties> = {
  root: {
    background: 'red'
  },
  ...
}

然后我得到一个强类型记录,即style.nonExistingKey 会抛出一个错误。但是,此方法需要复制记录键,因为它们必须显式添加为通用参数。

第三次尝试:

我可以使用以下代码在事后创建一个强类型记录:

const styleObj = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  ...
}

const style = styleObj as Record<keyof typeof styleObj, CSSProperties>

但是,我失去了对记录的 CSSProperties 值的类型检查,所以这不是一个好的解决方案。

有没有办法做这样的事情:

const style: Record<T, CssProperties> = {
  root: {
    background: 'red'
  },
  ...
}

并让T 被自动推断为'root' | 'title' | ... 等?

【问题讨论】:

  • Record 来自哪里?
  • 是内置的TS类型,type Record&lt;K extends string, T&gt; = { [P in K]: T; }
  • 属性名称从何而来?我的意思是titleroot 等等。他们来自哪里?他们在某处列出吗?现在您正在基于string 定义一个记录,所以当然,任何字符串都可以作为键。
  • 我将属性名称声明为style-object 的一部分,并希望在以后使用它们的强类型。

标签: typescript


【解决方案1】:

您可以在定义对象时使用辅助函数。该函数将具有一个类型参数,该参数要求所有属性必须是使用索引签名的CSSProperties 类型。由于该函数是通用的,因此将正确输入结果(它实际上没有索引签名,而只是定义的属性)

function createStyleMap<T extends { [name: string]: CSSProperties }>(cfg: T)  {
  return cfg;
}
const style = createStyleMap({
  root: {
  background: 'red'
  },
  title: {
    fontWeight: 'bold'
  }
});

style.root //ok
style['hu'] // error

您也可以键入它以返回 Record,但我认为这不会增加任何价值

function createStyleMap<T extends { [name: string]: CSSProperties }>(cfg: T) : Record<keyof T, CSSProperties> {
  return cfg;
}

【讨论】:

  • 使用Record&lt;...&gt;作为返回类型,如第二个选项所示,如果你想做这样的事情实际上很有用:type Props = { classes: Record&lt;keyof typeof styles, string&gt;; };
  • 我怎样才能使CSSProperties 通用?
  • @noririco 真的取决于你想要做什么..
  • 我认为这是TS的一个大问题。没有简单的方法可以在不丢失键的情况下声明类型化映射。此解决方案可能适用于某些情况,但由于 T 扩展了 CSSProperties(它不完全是 CSSProperties),它将允许未知键,例如,如果 { foo: 'bar' } 被传递,它将被接受,即使 'foo' 不是CSS 属性。这个问题可以通过使用这样的函数来解决:const createCssMap = &lt;K extends string&gt;(map: Record&lt;K, CSSProperties&gt;) =&gt; map,可以这样使用:createCssMap({ /* styles */ })
  • 很好的答案,谢谢!
【解决方案2】:

更新

在更多地学习 TypeScript 之后,我设法在一个函数中实现了这一点。 所以这是一个新的、简短的、有效的解决方案。

辅助函数:

import { StyleRulesCallback, Theme } from 'material-ui/styles';
import { CSSProperties } from 'react';

export const createStyleMap =
    <T extends keyof any>(callback: (theme: Theme) => Record<T, CSSProperties>):
    StyleRulesCallback<T extends string ? T : never> => callback;

组件示例:

import { withStyles, WithStyles } from 'material-ui/styles';
import React from 'react';
import { createStyleMap } from '../utils/utils';

interface OwnProps {
    dummyName: string;
}

const styles = createStyleMap(theme =>
    ({
        content: {
            height: '100%',
            padding: theme.spacing.unit * 3,
            overflowY: 'auto',
            boxSizing: 'border-box'
        },
        tableHead: {
            height: 56
        }
    })
);

type Props = OwnProps 
    & WithStyles<keyof ReturnType<typeof styles>>;

class DummyComponent extends React.Component<Props> {
    render() {
        return <div className={this.props.classes.content}/>;
    }
}

export default withStyles(styles)(DummyComponent);

【讨论】:

    【解决方案3】:

    [x in keyof y] 会在这里解决问题:

    const styleObj = {
        root: {
          background: 'red'
        },
        title: {
          fontWeight: 'bold'
        },
      }
    type StyleObj = {
        [P in keyof typeof styleObj]?: CSSProperties
    }
    const newStyleObj:StyleObj = {
        root:{
            ...
        }
    }
    

    删除? 使所有字段均为必填项。

    【讨论】:

      猜你喜欢
      • 2019-03-31
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 2022-01-07
      • 1970-01-01
      • 2019-10-18
      • 2021-04-28
      • 2019-04-16
      相关资源
      最近更新 更多