web-dev-qa-db-ja.com

一見同じように見える3項演算子構成体が生成しないのに、 'if-else'ステートメントを使用するとTypeScriptコンパイラエラーが生成されるのはなぜですか?

IDBValidKeyの値またはIDBValidKeyに変換された値を返すことを目的とした関数があります。三項演算子を使用して関数を記述した場合、正常に機能しますが、if-elseステートメントとして記述した場合、コンパイラエラーが発生します。

interface IDBValidKeyConvertible<TConverted extends IDBValidKey> {
    convertToIDBValidKey: () => TConverted;
}

function isIDBValidKeyConvertible<TConvertedDBValidKey extends IDBValidKey>(object: unknown): object is IDBValidKeyConvertible<TConvertedDBValidKey> {
    return typeof((object as IDBValidKeyConvertible<TConvertedDBValidKey>).convertToIDBValidKey) === "function";
}

type IDBValidKeyOrConverted<TKey> = TKey extends IDBValidKeyConvertible<infer TConvertedKey> ? TConvertedKey : TKey;

function getKeyOrConvertedKey<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey> {
    if (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) {
        return input.convertToIDBValidKey();
    } else {
        return input;
    }
}

function getKeyOrConvertedKeyTernary<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey> {
    return (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) ? input.convertToIDBValidKey() : input;
}

getKeyOrConvertedKeyTernaryはエラーを生成しませんが、elsegetKeyOrConvertedKeyブロックはこのエラーを生成します:

Type 'TKey' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
  Type 'string | number | Date | ArrayBufferView | ArrayBuffer | IDBArrayKey | IDBValidKeyConvertible<any>' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
    Type 'string' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.

三項演算子とif-elseステートメントは同等ではありませんか?

ありがとう!

8
Auth Infant

一見同じように見える3項演算子構成体が生成しないのに、 'if-else'ステートメントを使用するとTypeScriptコンパイラエラーが生成されるのはなぜですか?

短い答え

TypeScriptは_if-else_を、それぞれが独立した型を持つ複数の式を持つステートメントと見なします。 TypeScriptは、3項を、真側と偽側の和集合型を持つ式と見なします。時々、その共用体型は、コンパイラーが文句を言わないように十分に広くなります。

詳細な回答

三項演算子とif-elseステートメントは同等ではありませんか?

結構です。

違いは、である三項から生じます。 ここでの会話 があり、Ryan Cavanaughが3項ステートメントとif/elseステートメントの違いを説明しています。 三項式の型は、truefalseの結果の結合です

特定の状況では、3項式のタイプはanyです。コンパイラが文句を言わないのはそのためです。 3項は、input型とinput.convert()戻り型の和集合です。コンパイル時に、input型は_Container<any>_を拡張します。したがって、input.convert()の戻り値の型はanyです。 anyとの共用体はanyであるため、3項の型はanyです。

簡単な解決策は、_<TKey extends IDBValidKey | IDBValidKeyConvertible<any>_でanyunknownに変更することです。これにより、if-elseと3項の両方でコンパイラエラーが発生します。

簡略化された例

これが プレイグラウンドリンク で、質問を簡単に複製したものです。 anyunknownに変更して、コンパイラーの応答を確認してください。

_interface Container<TValue> {
  value: TValue;
}

declare function hasValue<TResult>(
  object: unknown
): object is Container<TResult>;

// Change any to unknown.
const funcIfElse = <T extends Container<any>>(input: T): string => {
  if (hasValue<string>(input)) {
    return input.value;
  }

  return input;
};

// Change any to unknown.
const funcTernary = <T extends Container<any>>(input: T): string =>
  hasValue<string>(input)
    ? input.value
    : input;
_
2
Shaun Luttin

2つのまったく別の問題が発生しています:

  1. TypeScriptにはバグまたは制限があります[1]が、これは質問が想定するものの反対です

    三項演算子を使用して関数を記述した場合、問題なく機能します

    実は良くないです。 if-elseバージョンのエラーは正しく、3値バージョンも同じエラーになるはずです。

    私たちのほとんどがそうであるように、私たちはコードが正しいと仮定する傾向があるので、あなたはおそらくあなたがそうしたと仮定しました。これは2番目の問題につながります。


  1. あなたのコードは非論理的です。 (失礼な言い訳はご容赦ください。)

(今は問題の簡略化されたコードで)#2の方が見やすいと思います。でも、暇があれば詳しく説明しようと思います。


[1]これはTypeScriptの欠陥ではない可能性がありますが、私が知らないif-else? :の間の微妙な非等価性により予期される動作です。 Javascriptには多くのレガシーな奇妙さがあるので、私は驚かないでしょう。 [編集:参照してください Shaun Luttinの回答 私が私のものを入力しているときと同じです。]

0
Inigo