Reputation: 4725
I built a function called compact
, what this function do is to remove all falsy values in an array.
This is the javascript
implementation of compact
:
function compact(arr) {
return arr.filter(Boolean);
}
const MyData = [0, 1, null, 2, undefined, ''];
console.log(compact(MyData))
// => [1, 2]
This is the Typescript
typing part of compact
:
type Falsy = false | null | 0 | '' | undefined;
type Compact<T extends any[]> = Exclude<T[number], Falsy>;
// some correct test
type MyData = [0, 1, null, 2, undefined, ''];
type MyDataWithoutFalsy = Compact<MyData>
// => type MyDataWithoutFalsy = 1 | 2
Now, here comes the weird part, when I hook it up with compact
code, it's not actually working:
function compact<T extends any[]>(arr: T): Compact<T> {
return arr.filter(Boolean) as Compact<T>;
}
let MyDataWithoutFalsy = compact([0, 1, null, 2, undefined, '']);
// => let MyDataWithoutFalsy: string | number, but it should be `number` only, because empty string should be excluded.
It should be number
only, because empty string should be excluded.
Upvotes: 2
Views: 448
Reputation: 250106
The problem is not with Exclude
, the problem is that for ""
(and not just for this string literal, for any string literal) typescript will not usually keep the string literal type but it will rather widen it to string
unless we give it a reason to preserve the literal type.
To hint to the compiler you want a literal type the literal must be assigned to a generic type parameter that is constrained to a literal base type:
function compact<V extends (string | undefined | boolean | object | number | null), T extends V[]>(arr: T & V[]): Compact<T> {
return arr.filter(Boolean) as Compact<T>;
}
let MyDataWithoutFalsy = compact([0, 1, null, 2, undefined, '']); // number
type Falsy = false | null | 0 | '' | undefined;
type Compact<T extends any[]> = Exclude<T[number], Falsy>;
Note that this does mean that compact
will not really be usable unless you construct the array in such a way as to preserve literal types (such as ''
).
function compact<V extends (string | undefined | boolean | object | number | null), T extends V[]>(arr: T & V[]): Compact<T> {
return arr.filter(Boolean) as Compact<T>;
}
function literalArray<V extends (string | undefined | boolean | object | number | null)>(arr: V[]): V[] {
return arr.filter(Boolean);
}
let arr = literalArray([0, 1, null, 2, undefined, ''])
let MyDataWithoutFalsy = compact(arr); // 1| 2 ... beacuse resons
Upvotes: 5