Issue
I have the following code
type Course = {
name: string;
grade: Grade;
credits: number;
}
type Grade = typeof gradePoints[number]['letter'];
const courseData = [
{ name: 'Math', grade: 'A', credits: 3 },
{ name: 'Science', grade: 'B', credits: 4 },
{ name: 'English', grade: 'C', credits: 2 },
]
const gradePoints = [
{ letter: 'A', value: 4.0 },
{ letter: 'B', value: 3.0 },
{ letter: 'C', value: 2.0 },
] as const;
for (const course of courseData) {
const gradePoint = gradePoints.find((gp) => gp.letter === course.grade).value;
console.log(gradePoint);
}
Here I am getting an TS error for this line
const gradePoint = gradePoints.find((gp) => gp.letter === course.grade).value;
Object is possibly 'undefined'.
I know adding just this ".?" will fix the error
const gradePoint = gradePoints.find((gp) => gp.letter === course.grade)?.value;
But I think we can fix this without this ".?". Reason whatever the letter we put to grade property is essentialy in the gradePoints list. So find should return an object all the time. It can't be undefined.
How can I achieve this with TypeScript? this is my playground link
Solution
The type of Array<T>.prototype.find
is:
interface Array<T> {
/**
* Returns the value of the first element in the array where predicate is true, and undefined
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found, find
* immediately returns that element value. Otherwise, find returns undefined.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
find<S extends T>(predicate: (value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
// ...
}
So you can see that the return type is a union which always includes undefined
. This is because TypeScript cannot statically analyze the result of every iteration of the callback as it would happen at runtime.
But I think we can fix this without this ".?". Reason whatever the letter we put to grade property is essentialy in the gradePoints list. So find should return an object all the time. It can't be undefined.
If you're certain of this (and willing to accept the consequences of being wrong if something changes in your code), the simplest way to suppress undefined
from being part of the union type is by using the Non-null Assertion Operator (Postfix !
):
const gradePoint = gradePoints.find((gp) => gp.letter === course.grade)!.value;
// ^? const gradePoint: 4 | 3 | 2
As an alternative, you can create a data structure — that's simply derived from your existing code — from which the values can be directly indexed:
const gradeValues = Object.fromEntries(
// ^? const gradeValues: { A: 4; B: 3; C: 2; }
Object.values(gradePoints).map(({ letter, value }) => [letter, value]),
) as { [T in (typeof gradePoints)[number] as T["letter"]]: T["value"] };
for (const course of courseData) {
const gradePoint = gradeValues[course.grade];
// ^? const gradePoint: 4 | 3 | 2
console.log(course.name, gradePoint);
}
In the code above, a mapped type is asserted which represents the actual shape of the data structure. This is needed because the built-in return types for Object.fromEntries
are very weak — they aren't generic for the iterable parameter:
interface ObjectConstructor {
/**
* Returns an object created by key-value entries for properties and methods
* @param entries An iterable object that contains key-value entries for properties and methods.
*/
fromEntries<T = any>(entries: Iterable<readonly [PropertyKey, T]>): { [k: string]: T; };
/**
* Returns an object created by key-value entries for properties and methods
* @param entries An iterable object that contains key-value entries for properties and methods.
*/
fromEntries(entries: Iterable<readonly any[]>): any;
}
Answered By - jsejcksn
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.