TypeScript:使用映射类型删除索引签名

时间:2018-07-22 12:35:40

标签: typescript mapped-types

提供一个界面(来自无法更改的现有.d.ts文件):

interface Foo {
  [key: string]: any;
  bar(): void;
}

有没有一种方法可以使用映射类型(或其他方法)来导出没有索引签名的新类型?即它只有方法“ bar():void;”

5 个答案:

答案 0 :(得分:10)

使用 TypeScript v4.1 key remapping 导致非常简洁的解决方案。

在其核心,它使用 Mihail's answer 中稍微修改的逻辑:虽然已知键是 stringnumber 的子类型,但后者不是相应文字的子类型。另一方面,string 是所有可能字符串的并集(number 也是如此),因此是自反的(type res = string extends string ? true : false; //true 成立)。

这意味着您可以在每次将 neverstring 类型分配给键类型时解析为 number,从而有效地将其过滤掉:

interface Foo {
  [key: string]: any;
  [key: number]: any;
  bar(): void;
}

type RemoveIndex<T> = {
  [ P in keyof T as string extends P ? never : number extends P ? never : P ] : T[P]
};

type FooWithOnlyBar = RemoveIndex<Foo>; //{ bar: () => void; }

Playground

答案 1 :(得分:6)

有一种方法需要TypeScript 2.8的Conditional Types

它基于以下事实:'a' extends stringstring不是extends 'a'

interface Foo {
  [key: string]: any;
  bar(): void;
}

type KnownKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;


type FooWithOnlyBar = Pick<Foo, KnownKeys<Foo>>;

您可以从中得到通用名称:

// Generic !!!
type RequiredOnly<T extends Record<any,any>> = Pick<T, KnownKeys<T>>;

type FooWithOnlyBar = RequiredOnly<Foo>;

有关为何KnownKeys<T>确实起作用的解释,请参见以下答案:

https://stackoverflow.com/a/51955852/2115619

答案 2 :(得分:4)

通过 TypeScript 4.4,该语言获得了对更复杂索引签名的支持。

public class JwtUser implements UserDetails {
    
    private String token;

    private  Long id;
    
    private  String username;

    private  String secondName;
    
    private  String password;
    
    private  String phone;
    
    private  String description;
    
    private  String email;

    private  boolean active;
    
    private  Date lastPasswordResetDate;

    private  Collection<? extends GrantedAuthority> authorities;
    
    
    public JwtUser(Long id, String username, String secondName, String password, String phone, String description,
            String email, boolean active, Date lastPasswordResetDate,
            Collection<? extends GrantedAuthority> authorities) {
        
        this.id = id;
        this.username = username;
        this.secondName = companyName;
        this.password = password;
        this.phone = phone;
        this.description = description;
        this.email = email;
        this.active = active;
        this.lastPasswordResetDate = lastPasswordResetDate;
        this.authorities = authorities;
    }
    
    
    
    public JwtUser(String token, Long id, String username, String secondName,  String phone, String description,
            String email, Collection<? extends GrantedAuthority> authorities) {
        this.token=token;
        this.id = id;
        this.username = username;
        this.secondName = companyName;      
        this.phone = phone;
        this.description = description;
        this.email = email;     
        this.authorities = authorities;
    }
    

    public JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities,
            String email, boolean active) {
                
        this.username = username;
        this.password=password;
        this.authorities = authorities;
        this.email = email;     
        this.active=active;
        
    }
    
    public JwtUser(){
        
    }
    
    
    
    
    
    
    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    @JsonIgnore 
    public Long getId() {
        return id;
    }

    public String getSecondName() {
        return companyName;
    }

    public String getPhone() {
        return phone;
    }

    public String getDescription() {
        return description;
    }

    public String getEmail() {
        return email;
    }

    public boolean isActive() {
        return active;
    }
    @JsonIgnore
    public Date getLastPasswordResetDate() {
        return lastPasswordResetDate;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        
        return authorities;
    }
    @JsonIgnore
    @Override
    public String getPassword() {
        
        return password;
    }

    @Override
    public String getUsername() {
        
        return username;
    }
    public void setUsername(String username) {
        this.username=username;
    }
    
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        
        return true;
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        
        return true;
    }

    @Override
    public boolean isEnabled() {
        
        return true;
    }

}

interface FancyIndices { [x: symbol]: number; [x: `data-${string}`]: string } 键可以通过在之前发布的类型中为其添加大小写来轻松捕获,但这种检查方式无法检测到无限模板文字。1

但是,我们可以通过修改检查以查看使用每个键构造的对象是否可分配给空对象来实现相同的目标。这是有效的,因为“真实”键将要求使用 symbol 构造的对象具有属性,因此不可分配,而作为索引签名的键将导致可能仅包含空对象的类型。< /p>

Record<K, 1>

playground

中试用

测试:

type RemoveIndex<T> = {
  [K in keyof T as {} extends Record<K, 1> ? never : K]: T[K]
}

1 您可以使用一次处理一个字符的递归类型来检测一些无限模板文字,但这不适用于长键。

答案 3 :(得分:0)

不是。您不能“减去”像这样的界面。每个成员都是公开的,任何声称实现Foo的人都必须实现它们。通常,您只能通过extends声明合并扩展接口,而不能从接口中删除内容。

答案 4 :(得分:0)

没有真正通用的方法,但是如果您知道需要哪些属性,则可以使用Pick:

interface Foo {
  [key: string]: any;
  bar(): void;
}

type FooWithOnlyBar = Pick<Foo, 'bar'>;

const abc: FooWithOnlyBar = { bar: () => { } }

abc.notexisting = 5; // error