typescriptthis

Explicit `this: <type>` in TypeScript functions


I'm reading Declaring this in a Function from the TypeScript Handbook, and I'm confused by this example:

... but there are a lot of cases where you need more control over what object this represents. The JavaScript specification states that you cannot have a parameter called this, and so TypeScript uses that syntax space to let you declare the type for this in the function body. This pattern is common with callback-style APIs, where another object typically controls when your function is called.

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

I didn't fully get this paragraph message, in particular how filterUsers method should be implemented so that explicit this: User would work, so I decided to bring this example to a working state:

type User = {
  id: number;
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const users: User[] = [
    { id: 1, admin: true },
    { id: 2, admin: false },
  ];

const db:DB = {
  filterUsers(filter: (this: User) => boolean) {
    return users.filter(filter);
  }
};


const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

console.log(admins);

Obviously this example won't work, although the compiler doesn't complain. It's even more clear from the resulting JavaScript - this will not be User instance inside function (this: User) { return this.admin; } :

"use strict";
const users = [
    { id: 1, admin: true },
    { id: 2, admin: false },
];
const db = {
    filterUsers(filter) {
        return users.filter(filter);
    }
};
const admins = db.filterUsers(function () {
    return this.admin;
});
console.log(admins);

So, what would be the correct implementation filterUsers so that the idea of explicit this: User would work and be useful?


Solution

  • I would recommend the filter function that takes a User object as an argument instead of relying on this:

    type User = {
      id: number;
      admin: boolean;
    }
    
    interface DB {
      filterUsers(filter: (user: User) => boolean): User[];
    }
    
    const users: User[] = [
      { id: 1, admin: true },
      { id: 2, admin: false },
    ];
    
    const db: DB = {
      filterUsers(filter: (user: User) => boolean) {
        return users.filter(filter);
      }
    };
    
    const admins = db.filterUsers((user: User) => {
      return user.admin;
    });
    
    console.log(admins);
    

    If you want to try to set the param name to be this, You could also use filter.call(user) inside the filterUsers method, or explicitly bind the object like @rozsazoltan did.

    This ensures that this inside the filter function refers to the current user object.

    type User = {
      id: number;
      admin: boolean;
    }
    
    interface DB {
      filterUsers(filter: (this: User) => boolean): User[];
    }
    
    const users: User[] = [
      { id: 1, admin: true },
      { id: 2, admin: false },
    ];
    
    const db: DB = {
      filterUsers(filter: (this: User) => boolean) {
        return users.filter(user => filter.call(user));
      }
    };
    
    const admins = db.filterUsers(function (this: User) {
      return this.admin;
    });
    
    console.log(admins);