在将Filter< T>作为参数传入的方法中,如何从类型脚本中的Filter< T>访问$AND运算符?

ercv8c1e  于 2022-10-21  发布在  其他
关注(0)|答案(2)|浏览(129)

我有一些可以工作的代码,但在我的IDE中总是出现打字错误。我已经精简了代码,以显示我的问题所在。
在Next.JS应用程序中,我有一个类型(TestModel),一个用于在该模型上构建大规模查询的方法(generateTestFilter),以及处理每个过滤条件的逻辑的其他方法(handleNameFilter)。当尝试访问handleNameFilter中的$and操作符时,出于某种原因,tyescrip不喜欢这样,而如果我尝试在方法generateTestFilter中访问它,这里没有问题。你知道为什么会发生这种事吗?

import { Filter } from "mongodb"

export interface TestModel {
    name: string,
    things: string[],
    stuff: number
}

function generateTestFilter(tm: TestModel) {
    const filter: Filter<TestModel> = { $and: [] }

    handleNameFilter(tm, filter)

    if (filter.$and && filter.$and.length) { // $and can be accessed here with no issues
        return filter
    }
    return null
}

function handleNameFilter(tm: TestModel, filter: Filter<TestModel>) {
    const condition: Filter<TestModel> = {name: 'Steve'}
    filter.$and.push(condition) // Property '$and' does not exist on type 'Filter<TestModel>'. Property '$and' does not exist on type 'Partial<TestModel>'.ts(2339)

}

打字Playground

xytpbqjk

xytpbqjk1#

问题

Filter的定义如下所示:

/**A MongoDB filter can be some portion of the schema or a set of operators @public */
export type Filter<TSchema> =
  | Partial<TSchema>
  | ({
      [Property in Join<NestedPaths<WithId<TSchema>, []>, '.'>]?: Condition<
        PropertyType<WithId<TSchema>, Property>
      >;
    } & RootFilterOperators<WithId<TSchema>>);

-node-mongodb-ative/src/mongo_tyes.ts:67-74@5f37cb6
您可以看到它是两种类型的并集,含义如下:
1.TSchema上的所有属性都是可选的。
1.与根过滤器操作符(其中$and是有效属性)相交的Map类型(太长而无法解释)。

为什么handleNameFilter出错?

由于该类型是一个联合,默认情况下,您只能访问对该联合的每个成员都有效的属性/方法。在handleNameFilter中,您尝试访问$and。该属性仅出现在并集的后半部分,而不出现在前半部分(Partial<TestModel>)。因此,TypeScrip会引发错误。

为什么generateTestFilter没有错误?

此前,我曾写道:
由于该类型是一个联合,默认情况下,您只能访问对该联合的每个成员都有效的属性/方法。
但是,您可以将文字缩小到并集的一侧。缩小是提供打字信息,以帮助使文字更具体(又名缩小)。在generateTestFilter中,您已经这样做了:

const filter: Filter<TestModel> = { $and: [] }

实际上,分配给filter的对象中的属性名称是$and并不重要。重要的是,属性名称不是出现在TestModel中的名称。由于没有一个属性与TestModel的属性匹配,因此它不满足并集的前半部分(Partial<TestModel>),因此类型脚本将其缩小到并集的后半部分。您可以通过更改键/值来测试这一点,以匹配TestModel中的属性-这应该会引发错误。

解决方案

既然问题已经解释好了,我们应该能够找到解决方案了。乍一看,答案似乎是以某种方式缩小了类型。然而,我想要谈的是另一个问题,我认为这是真正的根本原因。

function handleNameFilter(tm: TestModel, filter: Filter<TestModel>) {
    const condition: Filter<TestModel> = {name: 'Steve'}
    filter.$and.push(condition) // Property '$and' does not exist on type 'Filter<TestModel>'. Property '$and' does not exist on type 'Partial<TestModel>'.ts(2339)

}

您的handleNameFilter函数在类型为Filter<TestModel>filter参数处接受参数,然后推送到filter上的$and属性。然而,并不能保证$and一开始就会存在。$and仅因以下原因而存在:

const filter: Filter<TestModel> = { $and: [] }

    handleNameFilter(tm, filter)

假设在将来,您可以重构这一点并删除第一行。这会打破你的逻辑。TypeScrip会保护你不受此影响。
因此,您需要以某种方式使$and可用于推送。选项包括:
1.强制在filter参数类型中存在$and属性。不过,我不建议这样做,因为添加属性的责任最好放在函数中。因此:
1.将$and属性添加到filter,作为函数中逻辑的一部分。

nhaq1z21

nhaq1z212#

除了@Wing的详细解释外,实施这个解决方案实际上需要一点这两个“选项”:

  • 如第2点所述,确保在handleNameFilter函数中初始化了$and运算符(filter参数是Filter,它可能还没有*)
  • 我们仍然需要一点第1点(即在参数中强制存在$and成员),因为Filter中的联合:如果参数是普通的Partial<TestModel>,它不希望稍后在$and运算符上添加,即,当我们将类型“转换”为使用联合的另一部分时,Type脚本不喜欢
function handleNameFilter(
    tm: TestModel,
    // Force TS to accept adding later on the $and operator,
    // otherwise it complains because the Partial<TSchema> part of the union
    // does not expect it.
    filter: Filter<TestModel> & { $and?: Filter<TestModel>[] }
) {
    const condition: Filter<TestModel> = { name: 'Steve' }

    // Make sure the $and operator is present and initialized
    filter.$and ??= []

    filter.$and.push(condition) // Okay
}

Playground链接

相关问题