Typescript recursive type with index

I would like to define a type that is recursive for itself in this way, essentially:

interface CSSProperties {
  marginLeft?: string | number
  [key: string]?: CSSProperties
}

Unfortunately, typescript docs say:

Although string index signatures are a powerful way to describe a dictionary pattern, they also ensure that all properties match their return type. This is because the row index declares that obj.property is also available as obj ["property"]. In the following example, the type of names does not match the type of row indexes, and the type-checker gives an error:

This seems impossible to express in typescript, which seems like a serious limitation. Flow does what I think is right here, and assumes it marginLeftdoes not fall into the index specification.

Is this even possible in TypeScript? Also, is there a way to indicate that a string is any string, but a collection of strings? That way I could do something like this:

interface NestedCSSProperties: CSSProperties {
  [P not in keyof CSSProperties]?: CSSProperties
}
+4
source share
3 answers

The problem here is not recursion (which is allowed), but with conflicting signatures, as you pointed out.

, . , , . as-is , ; , - . , , , , .

. TypeScript , , , string number. :

interface CSSProperties {
    marginLeft?: string | number,
    [key: string]: CSSProperties|string|number,
}

, :

let a: CSSProperties = {
    marginLeft: 10,
    name: {
        marginLeft: 20,
    }
};

:

let a: CSSProperties = {
    marginLeft: 10,
    something: false, // Type 'boolean' is not assignable to type 'string | number | CSSProperties'.
    something: new RegExp(/a/g), // Type 'RegExp' is not assignable to type 'CSSProperties'.
    name: {
        marginLeft: 20,
    },
    car: ["blue"], // Type 'string[]' is not assignable to type 'CSSProperties'.
};

:

let name1: string | number = a.marginLeft; // OK, return type is string | number
a.marginLeft = false; // Blocked, Type 'false' is not assignable to type 'string | number'.
a["whatever"] = false; // Blocked, Type 'false' is not assignable to type 'string | number | CSSProperties'.
a["marginLeft"] = false; // Blocked, Type 'false' is not assignable to type 'string | number'.

, , , - ​​ CSSProperties.

:

a["whatever"] = 100;

:

let name3: CSSProperties = a["name"]; // Type is CSSProperties | string | number

, :

let name3: CSSProperties = a["name"] as CSSProperties;
+2

. . , "marginLeft", TS , .

, , , .d.ts, .

, , Pick, , .

interface IMap<T> {
    [key: string]: T
}

type CSSPropertyValue = string | number | CSSPropertiesBase;

interface CSSPropertiesBase extends IMap<CSSPropertyValue>
{

}

interface CSSProperties extends CSSPropertiesBase
{
    marginLeft?: string|number;
}
+1

You tried to use the intersection type. How is it?

interface CssPropertyValues {
    marginLeft? :string |number;    
}

interface RecursiveCssProperties { 
    [key: string]:  CssProperties;
}

type CssProperties = CssPropertyValues & RecursiveCssProperties;

let foo: CssProperties = {};

let myMargin = foo.bar.really.foobar.marginLeft; //should work... myMargin is typed as string|number|undefined

Intersection types are often revised and slightly more efficient than interface inheritance

0
source

Source: https://habr.com/ru/post/1674613/


All Articles