TypeScript — проверьте, является ли свойство объекта функцией с заданной сигнатурой

#typescript #testing #types #node.js-tape

Вопрос:

У меня есть функция, которая получает свойство от объекта.

 // Utils.ts
export function getProperty<T, K extends keyof T>(obj: T, key: string): T[K] {
    if (key in obj) { return obj[key as K]; }
    throw new Error(`Invalid object member "${key}"`);
}
 

Я хотел бы проверить, является ли возвращаемое свойство функцией с заданной подписью, а затем вызвать свойство с предоставленным параметром.

getProperty() используется для динамического получения одного из методов объекта и его вызова. Я пытался:

 let property: this[keyof this] = utils.getProperty(this, name);
if (typeof property === 'function') ) { property(conf); }
 

Но это дает «Невозможно вызвать выражение, тип которого не имеет подписи вызова. Тип «любой» не имеет совместимых сигнатур вызовов». ошибка. Я понимаю, что свойство, которое исходит от getProperty() , действительно может быть любого типа, но как определить, что это функция с (conf: {}): void подписью?

Комментарии:

1. <a rel=»noreferrer noopener nofollow» href=»https://www.typescriptlang.org/play/index.html#src=function getProperty(obj: T, key: K): T[K] {
if (key in obj) { return obj[key]; }
throw new Error(`Invalid object member «${key}»`);
}

const test = {
log: (message: string) => console.log(message)
}

const property = getProperty(test, ‘log’);

property(‘Hello world!’);» rel=»nofollow noreferrer»> Это то, чего ты хочешь?

Ответ №1:

Не похоже, что typeof защита типов существует для типов функций. Похоже, это сделано специально; см. microsoft/TypeScript#2072 (эта проблема связана instanceof с защитой типов, но я предполагаю, что это аналогичное рассуждение).

Тем не менее, TypeScript имеет определяемые пользователем типы защиты, что означает, что вы можете написать функцию, которая сужает тип своего аргумента любым удобным для вас способом.


Система статических типов TypeScript стирается при компиляции кода в JavaScript. Во время выполнения нет такой вещи, (conf: {}) => void которую вы могли бы изучить. Если вы хотите написать тест во время выполнения, чтобы отличать значения типа (conf: {}) => void от значений других типов, вы сможете зайти так далеко.

Вы можете проверить, если typeof x === "function" . Но это просто говорит вам, что это функция. В нем не указано, сколько параметров принимает функция и каковы их типы. Вы также можете проверить, x.length === 1 ожидает ли функция один аргумент, хотя есть предостережения, Function.length касающиеся параметров rest и параметров по умолчанию. Но теперь ты вроде как застрял.

Во время выполнения любая информация о типах параметров функций будет удалена, если эти функции вообще были получены из кода машинописного текста. Самое большее, вы могли бы «прощупать» функцию, вызвав ее с некоторыми параметрами теста и проверив, не сломается ли что-нибудь. Но это разрушительный тест с возможными побочными эффектами, и он противоречит цели проверки функции правильного типа перед ее вызовом.


Возможно, вас устраивает это ограничение, и вы просто предположите, что функция, которая length есть 1 , является достаточно хорошим тестом. Или, может быть, у вас есть какой-то другой тест (например, возможно, вы можете добавить свойство с именем isOfRightType , значение которого относится true ко всем таким функциям, которые вас интересуют, а затем просто протестировать это свойство. Это устраняет ложные срабатывания, вводя возможность ложных негативов). Как только вы узнаете свой тест во время выполнения, вы можете создать защиту типа:

 function isFunctionOfRightType(x: any): x is (conf: {})=>void {
   return (typeof x === 'function') amp;amp; (x.length === 1); // or whatever test
}

// ... later

let property: this[keyof this] = utils.getProperty(this, name);
if (isFunctionOfRightType(property)) { property(conf); } // no error now
 

Ссылка на игровую площадку для кода

Ответ №2:

В вашей сигнатуре функции есть ошибка, key параметр должен иметь тип K , и это даст вам лучший вывод при использовании констант для параметров:

 export function getProperty<T, K extends keyof T>(obj: T, key: string): T[K] {
    if (key in obj) { return obj[key as K]; }
    throw new Error(`Invalid object member "${key}"`);
}

let foo = {
    fn (conf: {}): void {}
}
let fn = getProperty(foo, "fn");
fn({}); // callable 
 

Проблема, когда вы используете ключ, который является строкой и не проверяется во время компиляции, заключается в том, что компилятор на самом деле ничем не может вам помочь. Это будет означать, что, поскольку вы индексируете мою произвольную строку, возвращаемым типом может быть любой допустимый тип поля целевого объекта. Нет способа проверить типы параметров функций во время выполнения, так как они будут удалены, однако вы можете проверить количество параметров:

 let property:  Function = getProperty(this, name) as any;
if (typeof property === 'function' amp;amp; property.length == 1)  
{ 
    property({}); 
}