Условное выражение машинописного текста равно нулю

Я хотел бы создать функцию, которая принимает аргумент, который обычно является объектом, но может иметь значение null / undefined, и условно возвращать тип, если он не равен null, в противном случае - null.

function objOrNull<T>(t: T): T extends null ? null: T {
   if (t == null) return null;
   
   return t;
}

Эта сигнатура функции кажется правильной, я могу вызвать ее с помощью objOrNull({}), objOrNull({a:42}), objOrNull(null), objOrNull(undefined), и возвращаемая переменная имеет ожидаемый тип. Но тело функции не может быть скомпилировано, поскольку ни return null, ни return t не могут быть присвоены объявленному возвращаемому типу. Если я объявляю возвращаемый тип как T | null, функция компилируется, но всякий раз, когда я ее вызываю, тип, который я возвращаю, потенциально имеет значение null, что, очевидно, не то, что я хочу - во время выполнения должно быть возможно определить, что если t имеет значение null, возвращаемое значение также равно null, иначе это не так?

Есть ли правильный способ реализовать такую ​​функцию, не считая добавления as any или подобных уродливых приведений типов? Что эквивалентно условному типу extends null ? для значения? Почему == null не сообщает компилятору, что я нахожусь в T extends null состоянии?

(Моим исходным вариантом использования была функция, которая принимает объект или значение NULL, и если объект имеет значение NULL, возвращает NULL, в противном случае возвращает вариант объекта с некоторыми добавленными и удаленными свойствами, но сводит вопрос к моему основному вопросу.) Мой исходный вариант использования


person JHH    schedule 05.10.2020    source источник
comment
Вы пробовали === null? Я подозреваю, что == может обмануть средство проверки типов, потому что условие также верно для 0, undefined и других ложных вещей.   -  person Clashsoft    schedule 05.10.2020
comment
Изучите средства защиты пользовательского типа   -  person Linda Paiste    schedule 05.10.2020
comment
Существует канонический ответ на вопрос, почему компилятор не может проверить возможность присвоения условным типам, которые зависят от неуказанных параметров универсального типа, но ваша функция не выполняет то, что она говорит. undefined не расширяет null (при условии, что вы используете --strictNullChecks). Возможно, вам вместо этого нужно что-то вроде function objOrNull<T>(t: T): T extends undefined ? null : T { return (typeof t === "undefined") ? null : t; }? Этот код имеет ту же проблему, но, по крайней мере, действительно делает то, что вы говорите.   -  person jcalz    schedule 05.10.2020


Ответы (2)


По сути, это текущее ограничение дизайна TypeScript. Компилятор не использует анализ потока управления для сужения типа неопределенного универсального параметры типа (например, T внутри вашей реализации функции) и не использует его для сужения типа значений, которые зависят от таких параметров типа (например, t внутри вашей реализации функции).

Это означает, что компилятор не может проверить, можно ли присвоить значение условному типу, например T extends undefined ? null : T. Даже если вы отметите (typeof t === "undefined"), анализ потока управления, который обычно сужает t с чего-то конкретного, например, string | undefined, до undefined, не подходит для такого типа, как T. Даже если он сузил t от T до (скажем) T & undefined, он не сузил T сам до undefined, что должно было произойти, чтобы компилятор понял, что null назначается T extends undefined ? null : T.

Для этого существует каноническая проблема на microsoft / TypeScript # 33912, запрашивающая способ использование компилятором анализа потока управления для проверки возможности присвоения возвращаемых значений универсальным условным типам. Но я не знаю, будет ли и когда это решено (хотя это, по крайней мере, хороший признак того, что это было предложено одним из основных членов команды TS).


Это означает, что лучшее, что вы можете сделать на данный момент, - это использовать введите утверждение (то, что вы называете уродливым приведением):

function objOrNull<T>(t: T): T extends undefined ? null : T {
    return (typeof t === "undefined") ? null : t as any;
}

или моральный эквивалент, такой как перегрузка:

function objOrNull<T>(t: T): T extends undefined ? null : T;
function objOrNull<T>(t: T): T | null {
    return (typeof t === "undefined") ? null : t;
}

Эта перегрузка не требует использования утверждения типа, но она небезопасна так же, как и использование утверждения типа. Значение: любой способ должен успокоить компилятор, хотя на самом деле это не проверка безопасности. Это ваша работа, поскольку компилятор не может этого сделать:

function badObjOrNull<T>(t: T): T extends undefined ? null : T {
    return (typeof t !== "undefined") ? null : t as any; // oops
}

or

function badObjOrNull<T>(t: T): T extends undefined ? null : T;
function badObjOrNull<T>(t: T): T | null {
    return (typeof t !== "undefined") ? null : t; // oops
}

Однако, если вы реализуете его правильно, он все равно должен вести себя так, как вы ожидаете от вызывающего абонента:

const x = objOrNull(undefined); // null
console.log(x); // null

const y = objOrNull(Math.random() < 0.5 ? undefined : "hello"); // "hello" | null
console.log(y); // sometimes null, sometimes "hello"

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

person jcalz    schedule 05.10.2020

Вы можете использовать перегрузки, чтобы получить тот же результат

function objOrNull(t:null|undefined):null
function objOrNull<T>(t: T):T
function objOrNull<T>(t: T|null):T|null {
   return t == null ? null : t;
}

площадка Link

person MacD    schedule 05.10.2020
comment
Итак, какой тип objOrNull(Math.random()<0.5 ? "hello" : undefined); возвращает? - person jcalz; 05.10.2020
comment
он возвращает string | undefined. - person MacD; 05.10.2020
comment
Но этого не должно быть, правда? Это должно быть string | null. Тот факт, что он говорит, что может вернуть undefined вместо null, является проблемой. - person jcalz; 05.10.2020