Явное использование `this: <type>` в функциях TypeScript

Я читаю Объявление этого в функции из TypeScript Handbook, и меня смущает этот пример:

... но во многих случаях вам нужен больший контроль над тем, что объект this представляет. Спецификация JavaScript гласит, что вы не может иметь параметр с именем this, поэтому TypeScript использует его синтаксическое пространство, позволяющее объявить тип this в функции тело. Этот шаблон является общим для API в стиле обратного вызова, где другой объект обычно контролирует, когда вызывается ваша функция.

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

Я не до конца понял суть этого абзаца, в частности, как следует реализовать метод filterUsers, чтобы явный this: User работал, поэтому я решил привести этот пример в рабочее состояние:

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.info(admins);

Очевидно, что этот пример не будет работать, хотя компилятор не жалуется. Из полученного JavaScript ещё более понятно — this не будет экземпляра User внутри 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.info(admins);

Итак, какой должна быть правильная реализация filterUsers, чтобы идея явного this: User работала и была полезной?

«в частности, как следует реализовать метод filterUsers, чтобы явно указать следующее: User будет работать», функцию необходимо вызывать с контекстом User. Вероятно, через .apply()/.call()/.bind().

VLAZ 26.08.2024 10:25

Вы не совсем об этом спрашиваете, так что я думаю, это просто отступление, но причина, по которой ваша неудачная попытка не имеет ошибки TS, заключается в отсутствии --strictThis, как это требуется в ms/TS#7968 . TS всегда позволит вам передать функцию this-параметра там, где ожидается параметр без this, как вы видите в своем коде и как минимально продемонстрировано в этой ссылке на игровую площадку. Я не знаю, как вставить эту информацию в свой пост.

jcalz 26.08.2024 15:49
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
2
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используя привязку, вы можете убедиться, что это действительно правильный экземпляр пользователя во время выполнения функции фильтра.

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) {
    // need to manually bind the `this` context to each user
    return users.filter(user => filter.bind(user)());
  }
};

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

console.info(admins);

Может показаться, что вы не можете контролировать значение this, поскольку его значение устанавливается автоматически, но на самом деле это не так.

У каждой функции есть метод .bind, который возвращает новую функцию, this привязанную к значению. Функция ведет себя точно так же, как и та, которую вы вызвали .bind, только она this была установлена ​​вами. Независимо от того, как и когда вызывается эта функция, this всегда будет ссылаться на переданное значение.

Источник: Как получить правильный this внутри обратного вызова
Феликс Клинг

Ответ принят как подходящий

Я бы порекомендовал функцию фильтра, которая принимает объект User в качестве аргумента вместо того, чтобы полагаться на это:

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.info(admins);

Если вы хотите попытаться установить такое имя параметра, вы также можете использовать filter.call(user) внутри метода filterUsers или явно привязать объект, как это сделал @rozsazoltan.

Это гарантирует, что это внутри функции фильтра относится к текущему объекту пользователя.

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.info(admins);

Итак, в этом примере из книги нет никакого практического удобства в использовании явного this, так как было бы понятнее написать его обычным способом? function (user: User) { return user.admin; }

Serg 26.08.2024 10:46

@Serg, ответ был обновлен, я только что заметил, что вы хотите использовать это имя в качестве имени параметра, что, я не думаю, имеет какое-либо практическое удобство.

huan feng 26.08.2024 10:48

@Serg, это мое личное мнение, я бы всегда рекомендовал использовать user:User вместо этого:User, чтобы избежать путаницы.

huan feng 26.08.2024 10:51

Моя вина была в том, что я пытался на их примере увидеть, что явный this помогает избежать какой-то ошибки или дает некоторое удобство при написании кода. Но тот пример из книги оказывается надуманным.

Serg 26.08.2024 10:53

@Serg Обновите ответ, включив в него оба, я по-прежнему предпочитаю передавать объект пользователя для ясности и простоты :)

huan feng 26.08.2024 10:58

Я думаю, вы допустили опечатку, потому что оба примера теперь идентичны. Но я абсолютно согласен — использование this: User только излишне усложняет код в этом контексте.

Serg 26.08.2024 11:13

@Serg Да, моя ошибка. Ответ обновлен, спасибо!

huan feng 26.08.2024 15:04

@Serg, не могли бы вы также проголосовать за этот ответ?

huan feng 27.08.2024 08:36

Другие вопросы по теме