A
A
Aetae2020-01-10 00:56:59
typescript
Aetae, 2020-01-10 00:56:59

How to instantiate a complex type?

Let's say there is such an interface ( in fact, it is of course much more complicated ):

interface Some {
  value: string | string[];
}

I need to get a constant that fully corresponds to this type, but instantiated by value. Those. is it possible to somehow declare for ts that the structure will be exactly like this and will not change?
If you just specify the type, then it will not work, because ts considers that the structure of an object can change arbitrarily within the type, which is logical:
const foo: Some = {
  value: ['a', 'b'] // здесь всегда будет массив
};

foo.value.map(() => {});  //  Property 'map' does not exist on type 'string | string[]'

What I need approximately, I can get by making a wrapper function:
function SomeType<T extends Some>(value: T): T {
  return value
}

const bar = SomeType({ 
  value: ['a', 'b'] // здесь всегда будет массив  
});

bar.value.map(() => {});  // ок

But I would like to do it the right way.
...upd:
What exactly do I need:
a) when declaring an object, it must be checked for compliance with the Some type (so that ide immediately highlights me if I made a mistake somewhere and suggested autocomplete).
b) when working with the declared object later on, I don't want to manually specify every subtype of every property and subproperty possible in the Some type, ts has to understand them from the declaration.
c) the declared object must still be a variant of type Some for ts so that it can be safely passed to methods and functions that expect Some as input.
e) the object must remain mutable (within the scope of the instantiated type, of course).

Answer the question

In order to leave comments, you need to log in

3 answer(s)
R
Ruslan Lopatin, 2020-01-10
@lorus

TypeScript doesn't do that.
I can suggest normalization of complex types. Something like this:

// Заготовка для Some
interface SomeDraft {
  value?: string | string[]; // Массив или значение. Может отсутствовать.
}

// Some имеет более строгий тип и как можно меньшую вариативность,
// чтобы в коде не делать проверки.
interface Some {
  value: string[];  // Всегда присутствует. Всегда массив.
}

function makeSome({ value = [] }: SomeDraft): Some {
  // Приводим к нормальному (более строгому) типу
  return {
    value: Array.isArray(value) ? value : [value],
  };
}

// Здесь Some задаётся через заготовку - так проще задавать значения
// Но результат всегда более строгий - так проще значениями пользоваться
const foo = makeSome({ value: 'foo' }); // { value: ['foo'] }

foo.value.map(() => {}); // foo.value - всегда массив и TypeScript это знает

S
Stanislav Makarov, 2020-01-10
@Nipheris

You have set some strange task. If you need a narrower type, declare it separately (and maybe even declare Some via this narrower type) and use it, or even don't specify the type at all in your example.
The following code is more than correct:

interface Some {
  value: string | string[];
}

const foo = {
  value: ['a', 'b'] // здесь всегда будет массив
};

foo.value.map(() => { });

function f(a: Some) {
    return a;
}

f(foo);

So, after all, you do not need the Some type at the place of the object declaration, but a narrower type, where value is always an array. For example:
interface Some {
  value: string | string[];
}

// Тут вам стоит придумать более подходящее по смыслу название
interface WithArrayValue {
    value: string[];
}

const foo: Some & WithArrayValue = {
    value: ['a', 'b'] // здесь всегда будет массив
};

foo.value.map(() => { });

function f(a: Some) {
    return a;
}

f(foo);

I
Interface, 2020-01-10
@Interface

Please share why you need it. I'm not asking about a real task, but rather about demo code like the one you provided.
The only reason that comes to my mind is that you need to declare a variable that will be of a narrower type than Some, which you want to automatically infer instead of writing it by hand, but you want to be sure that the resulting type is a subtype of Some.
As one of the solutions, I can offer this:

interface Some {
    value: string | string[];
}

type Extends<E, T extends E> = T;

const foo = {
    value: ['a', 'b'] // здесь всегда будет массив
}
foo as Extends<Some, typeof foo>;

playground
That is, the declaration of a variable consists of 2 expressions: the declaration of a variable with automatic type inference and the validation of the resulting type.
Ps unfortunately, just foo as Somewon't catch everything you need, e.g.{value: ['a', 'b', 8]}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question