TypeScript - 如何链接访问可选的嵌套类型属性?

TypeScript - How do you chain accessing optional nested type properties?

我有一个 Client class 存储应用程序需要保留在内存中的其他对象的缓存。对象缓存的结构是开发人员定义的。例如,如果我们有 Example 个对象的缓存:

class Example {
    property1: string;
    property2: string;
}

开发人员可能只想 property1 缓存。


import { EventEmitter } from "events";

// Constructor options for `Client`
interface ClientData {
    cacheStrategies?: CacheStrategies;
}

// How various objects should be cached
interface CacheStrategies {
    example?: ExampleCacheStrategies;
    ...
}

// Metadata for how each type of object should be cached
interface ExampleCacheStrategies {
    cacheFor?: number;
    customCache?: ExampleCustomCacheData;
}

// The custom structure of what parts of `example` objects should be cached
interface ExampleCustomCacheData {
    property1?: boolean;
    property2?: boolean;
}

// The object stored in `Client.exampleCache`, based on the custom structure defined in `ExampleCustomCacheData`
interface CustomExampleData<CachedExampleProperties extends ExampleCustomCacheData> {
    property1: CachedExampleProperties["property1"] extends true ? string /* 1 */ : undefined;
    property2: CachedExampleProperties["property2"] extends true ? string : undefined;
}

class Client<ClientOptions extends ClientData> extends EventEmitter {

    // The map's value should be based on the custom structure defined in `ExampleCustomCacheData`
    exampleCache: Map<string, CustomExampleData<ClientOptions["cacheStrategies"]["example"]["customCache"]>>;

    constructor(clientData: ClientOptions) {
        super();
        this.exampleCache = new Map();
    }
}

const client = new Client({
    cacheStrategies: {
        example: {

            /**
             * The properties of `example` objects that should be cached
             * This says that `property1` should be cached (string (1))
             */
            customCache: {
                property1: true, // (2)
                ... // (3)
            }
        }
    }
});

client.exampleCache.set("123", {
    property1: "value"
});

const exampleObject = client.exampleCache.get("123");

if (exampleObject) {

    // Should be `string` instead of `string | undefined` (2)
    console.log(exampleObject.property1);

    // `string | undefined`, as expected since it's falsey (3)
    console.log(exampleObject.property2);
}

console.log() 上方的评论中所述,目标是让从缓存中拉出的对象 property1 成为 string 而不是 string | undefined.

问题是 exampleCache: Map<string, CustomExampleData<ClientOptions["cacheStrategies"]["example"]["customCache"]>>; 不起作用,因为 ClientOptions["cacheStrategies"]ClientOptions["cacheStrategies"]["example"] 都是可选的。以下也不起作用:

exampleCache: Map<string, CustomExampleData<ClientOptions["cacheStrategies"]?.["example"]?.["customCache"]>>;

它在 ?. 处出现 '>' expected 错误。我该如何解决?

转译后的语法类似于 the optional chaining operator ?. or the non-null assertion operator ! only applies to value expressions that will make it through to JavaScript in some form. But you need something that works with type expressions which exist only in the static type system and are erased

有一个 NonNullable<T> utility type which is the type system analog of the non-null assertion operator. Given a union 类型 T,类型 NonNullable<T> 将与 T 相同,但没有 null 类型的任何联合成员或undefined:

type Foo = string | number | undefined;
type NonNullableFoo = NonNullable<Foo>;
// type NonNullableFoo = string | number

事实上,编译器实际上用它来表示应用了非空断言运算符的表达式的类型:

function nonNullAssertion<T>(x: T) {
    const nonNullX = x!;
    // const nonNullX: NonNullable<T>
}

所以,如果你有一个包含 nullundefined 的类型 T,并且你想删除它,你可以使用 NonNullable<T>。在您的代码中,您需要多次执行此操作。为了简洁(代码,不是我的解释),让我们使用一个更短的别名:

type NN<T> = NonNullable<T>;

然后

class Client<C extends ClientData> extends EventEmitter {

  exampleCache: Map<string, CustomExampleData<
      NN<NN<NN<C["cacheStrategies"]>["example"]>["customCache"]>>
  >;

}

此编译没有错误,并且表现出我认为您喜欢的方式:

console.log(exampleObject.property1.toUpperCase()); // string
console.log(exampleObject.property2); // undefined

Playground link to code