Reputation: 20162
I want to achieve conditional behavior of type. When I put generic in form of object kind (array, object, tuple, record), really any composite type, then I want the type behave as a typeof field of this object, but when the given type is a primary, I want to be the same type.
type B<A> = A extends object ? A[keyof A] : A;
const tuple: [1, 2];
const element:B<typeof tuple> = 2; // # ISSUE - element can be also any method of array
const num: 1;
const numElment:B<typeof num> = 1;
Above code works but for composite B
type enables me to assign also all method types from an Array type. My question is - how can I specify that I am interested only about things which are not functions. So only pure fields, as in example with the tuple, element
should be only number.
I was trying also with extends {}
or extends {[a: string | number]: any}
but it was also not working, and even above snippet would break after that.
Upvotes: 3
Views: 26601
Reputation: 330216
If you really want to exclude functions from a type union you can do it with the Exclude<T, U>
utility type:
type ExcludeFunctionProps<T extends object> = Exclude<T[keyof T], Function>;
But I don't think this really does what you want:
type Hmm = ExcludeFunctionProps<["a", "b"]>; // "a" | "b" | 2
Here Hmm
is "a" | "b" | 2
. Why 2
? Because arrays have a length
property of a numeric type, and a pair tuple has that type as the numeric literal type 2
. Unless you intend to include the tuple length, this is probably not the way to go.
type EvenWeirder = ExcludeFunctionProps<[string, () => void]>; // string | 2
And in the case where the tuple or object explicitly contains function-like values, this would also cut them out. Definitely seems like strange behavior to me.
Instead, I think that you're running into the issue where keyof T
for an array type is still the full list of all array methods and properties even though you can map over array types without them. So what I'd try here is to make my own KeyofAfterMapping<T>
which returns keyof T
for non-array types, but only keeps the number
index key for array types. Like this:
type KeyofAfterMapping<T> = Extract<
keyof T,
T extends any[] ? number : unknown
>;
Let's see what it does:
type KeyofPair = KeyofAfterMapping<["a", "b"]>; // number
type KeyofArray = KeyofAfterMapping<string[]>; // number
type KeyofObject = KeyofAfterMapping<{ a: string; b: number }>; // "a" | "b"
Now we can specify B
:
type B<A> = A extends object ? A[KeyofAfterMapping<A>] : A;
And verify that it behaves as you expect:
type TryTupleAgain = B<["a", "b"]>; // "a" | "b"
type KeepsFunctions = B<Array<() => void>>; // () => void
type ObjectsAreStillFine = B<{ a: string; b: number }>; // string | number
type PrimitivesAreStillFine = B<string>; // string
Looks good. Okay, hope that helps. Good luck!
Upvotes: 7