as any
construction, he had to pay for this with many hours of hard debugging.any
type is very difficult. And sometimes it seems that any
is the only reasonable solution to a certain problem.any
type in them, you can ensure the type safety of the code, open up opportunities for its reuse and make it intuitive.getBy
helper function. In order to get an object representing a student by its name, we can use the command like getBy(model, "name", "Harry")
. Take a look at the implementation of this mechanism (here, in order not to complicate the code, the database is represented by a regular array). type Student = { name: string; age: number; hasScar: boolean; }; const students: Student[] = [ { name: "Harry", age: 17, hasScar: true }, { name: "Ron", age: 17, hasScar: false }, { name: "Hermione", age: 16, hasScar: false } ]; function getBy(model, prop, value) { return model.filter(item => item[prop] === value)[0] }
function getBy(model: Student[], prop: string, value): Student | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "name", "Hermione") // result: Student
Student
it would use the universal type T
function getBy<T>(model: T[], prop: string, value): T | null { return model.filter(item => item[prop] === value)[0] } const result = getBy<Student>(students, "name", "Hermione") // result: Student
Student
explicitly set where generic T
. This is done to make the example as clear as possible, but the compiler, in fact, can independently deduce the required type, so in the following examples we will not make such type specifications."name"
there will be "naem"
? The function will behave as if the desired student is simply not in the database, and, most worryingly, will not give any errors. This can lead to long-term debugging.P
In this case, it is necessary that P
be a key of type T
, therefore, if the type of Student
used here, then it is necessary that P
be the string "name"
, "age"
or "hasScar"
. Here's how to do it. function getBy<T, P extends keyof T>(model: T[], prop: P, value): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "naem", "Hermione") // Error: Argument of type '"naem"' is not assignable to parameter of type '"name" | "age" | "hasScar"'.
keyof
is a very powerful technique. If you are writing programs to an IDE that supports TypeScript, then by entering arguments, you can take advantage of the auto-completion features, which is very convenient.getBy
function. It has a third argument, the type of which we have not yet specified. It does not suit us at all. Until now, we could not know in advance about what type it should be, since it depends on what we pass as the second argument. But now, since we have type P
, we can dynamically infer the type for the third argument. The type of the third argument will be T[P]
. As a result, if T
is Student
, and P
is "age"
, then T[P]
will correspond to the number
type. function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "age", "17") // Error: Argument of type '"17"' is not assignable to parameter of type 'number'. const anotherResult = getBy(students, "hasScar", "true") // Error: Argument of type '"true"' is not assignable to parameter of type 'boolean'. const yetAnotherResult = getBy(students, "name", "Harry") //
window
object, or extend the behavior of some external library like Express
. Both in that and in other cases you have no opportunity to directly influence the object with which you want to work.getBy
function you already know to the Array
prototype. This will allow us, using this function, to build more accurate syntactic constructions. At the moment we are not talking about whether it is good or bad to expand standard objects, since our main goal is to study the approach under consideration.Array
prototype, the compiler will not like it very much: Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; // Error: Property 'getBy' does not exist on type 'any[]'. const bestie = students.getBy("name", "Ron"); // Error: Property 'getBy' does not exist on type 'Student[]'. const potionsTeacher = (teachers as any).getBy("subject", "Potions") // ... ?
as any
construct, we negate everything we have achieved. The compiler will be silent, but you can forget about safe work with types.Array
type, but before doing this, let's talk about how TypeScript handles situations of presence in the code of two interfaces of the same type. Here is a simple scheme of action. Ads will, if possible, be merged. If you can not combine them - the system will give an error. interface Wand { length: number } interface Wand { core: string } const myWand: Wand = { length: 11, core: "phoenix feather" } // !
interface Wand { length: number } interface Wand { length: string } // Error: Subsequent property declarations must have the same type. Property 'length' must be of type 'number', but here has type 'string'.
Array<T>
interface and add the getBy
function to it. interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; const bestie = students.getBy("name", "Ron"); // ! const potionsTeacher = (teachers as any).getBy("subject", "Potions") //
Array
interface, you will need access to the global scope. This can be done by putting the type definition inside declare global
. For example - so: declare global { interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } }
namespace
this library. Here is an example of how to add the userId
field to the Request
from the Express
library: declare global { namespace Express { interface Request { userId: string; } } }
Source: https://habr.com/ru/post/426729/
All Articles