I have this basic layers
object, and I want to add things to each layer later.
layers = {
top: {
description: 'everything on top',
},
bottom: {
description: 'everything below',
}
}
Here is how I add things to each layer:
addThingsToLayers() {
Object.entries(this.layers).forEach(([layerName, layerObject]) => {
layerObject.element = document.createElement('div')
layerObject.style = {background: 'hotpink'}
})
}
This causes 2 expected errors
Property 'element' does not exist on type '{ description: string; }'
Property 'style' does not exist on type '{ description: string; }'
I can think of 2 ways of fixing this, but both solutions have drawbacks that I'd like to avoid.
First, I could just be more explicit in my layers
object:
layers = {
top: {
description: 'everything on top',
element: undefined,
style: undefined,
},
bottom: {
description: 'everything below',
element: undefined,
style: undefined,
}
}
Drawback: I have to write a lot of extra lines.
The other way I can think of is defining an interface for the layers
object
interface Layers {
[key: string]: {
description: string
element: HTMLDivElement
style: any
}
}
However, because of [key: string] I loose the ability to limited to the actually defined layer keys, e.g.
addToTopLayer() {
this.layers.
}
At this.layers.
I don't get autocomplete, and can write any key, even if it does not actually exist.
I could fix this by limiting the keys to a type, like
type LayerName = 'top' | 'bottom'
Drawback: Now I have to make edits in 2 place whenever I add a new layer, so I'm duplicating myself.
Finally my question (thanks for reading this far!):
Is there a way more elegant to achieve this, without having to write redundant or duplicate code?
CodePudding user response:
You can use a Layer
interface defined with optional properties:
interface Layer {
description: string;
element?: HTMLDivElement;
// ^−−−−−−−−−−−−−−−−−−−−−−−− optional
style?: any;
// ^−−−−−−−−−−−−−−−−−−−−−−−−−− optional
}
Full example:
interface Layers {
top: Layer;
bottom: Layer;
}
interface Layer {
description: string;
element?: HTMLDivElement;
style?: any;
}
const layers: Layers = {
top: {
description: "everything on top",
},
bottom: {
description: "everything below",
},
};
CodePudding user response:
I think, it is better to have map of layers
descriptions and create a new layers
object in your addThingsToLayers
function
Like this:
enum LayerNames {
TOP = "top",
BOTTOM = "bottom",
}
interface Layer {
description: string
element: HTMLDivElement
style: any; // it is better to use React.CSSProperties(for example) instead of any
}
type Layers = Record<LayerNames, Layer>;
const LAYERS_DESCRIPTIONS: Record<LayerNames, string> = {
[LayerNames.TOP]: "everything on top",
[LayerNames.BOTTOM]: "everything below",
}
addThingsToLayers(){
this.layers = Object.entries(LAYERS_DESCRIPTIONS).reduce((layers, [name, description]) => ({
...layers,
[name]: {
description,
style: {background: 'hotpink'},
element: document.createElement('div'),
}
}), {} as Layers)
}
CodePudding user response:
I have had the same issue. Reading the typescript documentation, i could see that with mapped types there was a solution:
interface Layer
{
description: string;
element?: HTMLDivElement;
style?: any;
};
type LayerKey = {'top':null; 'bottom': null; 'someOtherType': null};
type Layers =
{
-readonly [Property in keyof LayerKey]?: Layer;
};
const layers:Layers = {
'top': {
description: 'string',
element: (document.createElement('DIV') as HTMLDivElement),
style: 'any'
}
};
console.log(layers.someOtherType);
Said that, there are two (or more, i am not sure) things to consider:
- The type wich is used to set the keys (LayerKey, in this case) into the mutable type must be an object about wich only its keys will be used. Evidently this code is a bit dirty.
- Types cannot be extended, so if the object is very complex and needs to extends from others the solution will not work.