是否可以在TypeScript中推断Record的键?

时间:2018-03-28 15:13:28

标签: typescript

我正在使用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' | ...等?

2 个答案:

答案 0 :(得分:3)

定义对象时可以使用辅助函数。该函数将具有一个类型参数,该参数要求所有proepries必须使用索引签名为CSSProperies类型。由于函数是通用的,结果将被正确输入(它实际上不会有索引签名,而只是定义的proepries)

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;
}

答案 1 :(得分:1)

更新

在学习了更多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);