【问题标题】:Typescript mongoose static model method "Property does not exist on type"Typescript mongoose 静态模型方法“类型上不存在属性”
【发布时间】:2017-07-15 20:38:25
【问题描述】:

我目前正在尝试向我的 mongoose 架构添加一个静态方法,但我找不到它不能以这种方式工作的原因。

我的模特:

import * as bcrypt from 'bcryptjs';
import { Document, Schema, Model, model } from 'mongoose';

import { IUser } from '../interfaces/IUser';

export interface IUserModel extends IUser, Document {
    comparePassword(password: string): boolean;
}

export const userSchema: Schema = new Schema({
    email: { type: String, index: { unique: true }, required: true },
    name: { type: String, index: { unique: true }, required: true },
    password: { type: String, required: true }
});

userSchema.method('comparePassword', function (password: string): boolean {
    if (bcrypt.compareSync(password, this.password)) return true;
    return false;
});

userSchema.static('hashPassword', (password: string): string => {
    return bcrypt.hashSync(password);
});

export const User: Model<IUserModel> = model<IUserModel>('User', userSchema);

export default User;

用户:

export interface IUser {
    email: string;
    name: string;
    password: string;
}

如果我现在尝试调用User.hashPassword(password),我会收到以下错误[ts] Property 'hashPassword' does not exist on type 'Model&lt;IUserModel&gt;'.

我知道我没有在任何地方定义方法,但我真的不知道我可以把它放在哪里,因为我不能只将静态方法放入接口中。 希望您能帮我找出错误,在此先感谢!

【问题讨论】:

    标签: node.js typescript mongoose


    【解决方案1】:

    参考:https://mongoosejs.com/docs/typescript.html

    1. 只需在表示文档结构的模式之前创建一个接口。
    2. 将接口类型添加到模型中。
    3. 导出模型。

    以下引用自 mongoose 文档:

    import { Schema, model, connect } from 'mongoose';
    
    // 1. Create an interface representing a document in MongoDB.
    interface User {
      name: string;
      email: string;
      avatar?: string;
    }
    
    // 2. Create a Schema corresponding to the document interface.
    const schema = new Schema<User>({
      name: { type: String, required: true },
      email: { type: String, required: true },
      avatar: String
    });
    
    // 3. Create a Model.
    const UserModel = model<User>('User', schema);
    

    如果您向架构添加任何方法,请在接口中添加其定义。

    【讨论】:

      【解决方案2】:

      我认为您遇到的问题与我刚才遇到的问题相同。这个问题在你的电话中。几个教程有你这样从模型中调用.comparePassword()方法。

      User.comparePassword(candidate, cb...)
      

      这不起作用,因为该方法在 schema 上而不是在 model 上。我能够调用该方法的唯一方法是使用标准的 mongoose/mongo 查询方法找到模型的这个实例。

      这是我的护照中间件的相关部分:

      passport.use(
        new LocalStrategy({
          usernameField: 'email'
        },
          function (email: string, password: string, done: any) {
            User.findOne({ email: email }, function (err: Error, user: IUserModel) {
              if (err) throw err;
              if (!user) return done(null, false, { msg: 'unknown User' });
              user.schema.methods.comparePassword(password, user.password, function (error: Error, isMatch: boolean) {
                if (error) throw error;
                if (!isMatch) return done(null, false, { msg: 'Invalid password' });
                else {
                  console.log('it was a match'); // lost my $HÏT when I saw it
                  return done(null, user);
                }
              })
            })
          })
      );
      

      所以我使用findOne({}) 来获取文档实例,然后必须通过挖掘文档user.schema.methods.comparePassword 上的架构属性来访问架构方法

      我注意到的几个不同点:

      1. 我的是instance 方法,而你的是static 方法。我相信有类似的方法访问策略。
      2. 我发现我必须将哈希传递给comparePassword() 函数。也许这在静态方面不是必需的,但我无法访问this.password

      【讨论】:

      • 确实有效。非常感谢!一个问题,你为什么不使用user.comparePassword?也许这会解决您的this.password 问题(stackoverflow.com/questions/42415142/…),因为我遇到了类似的问题。
      • 如果我理解你的问题,原因是 Typescript 抛出了编译错误。
      • 我也为这个问题苦苦挣扎了一段时间。要通过 Typescript 访问静态模式方法,请使用 User.schema.statics.hashPassword()
      【解决方案3】:

      所以我也给了一个有 70 次更新的人。但这不是一个完整的解决方案。他使用了一个基于 OP 的简单示例。然而,当我们为了扩展模型的功能而使用staticsmethods 时,我们往往希望引用模型本身。他的解决方案的问题是他使用了一个回调函数,这意味着this 的值不会引用类上下文,而是一个全局的。

      第一步是调用statics 属性,而不是将属性作为参数传递给static 函数:

      schema.statics.hashPassword
      

      现在我们不能给这个成员分配箭头函数,因为箭头函数内部的this 仍然会引用全局对象!为了在模型的上下文中捕获this,我们必须使用函数表达式语法:

      schema.statics.hashPassword = async function(password: string): Promise<string> {
          console.log('the number of users: ', await this.count({}));
          ...
      }
      

      【讨论】:

        【解决方案4】:

        我遇到了和你一样的问题,然后在阅读了 TS mongoose typings 中的文档后终于设法解决了它(我以前不知道,我不确定文档已经存在了多长时间左右),特别是this section


        至于您的情况,您需要遵循与您目前所拥有的类似的模式,尽管您需要在两个文件中更改一些内容。

        IUser 文件

        1. IUser 重命名为IUserDocument。这是为了将您的架构与您的实例方法分开。
        2. 从猫鼬导入Document
        3. Document扩展接口。

        模型文件

        1. IUser 的所有实例重命名为IUserDocument,包括重命名文件时的模块路径。
        2. 仅将IUserModel 的定义重命名为IUser
        3. IUser 的扩展内容从IUserDocument, Document 更改为IUserDocument
        4. 创建一个名为IUserModel 的新接口,该接口从Model&lt;IUser&gt; 扩展而来。
        5. IUserModel 中声明您的静态方法。
        6. User 常量类型从Model&lt;IUserModel&gt; 更改为IUserModel,因为IUserModel 现在扩展了Model&lt;IUser&gt;
        7. 将模型调用的类型参数从 &lt;IUserModel&gt; 更改为 &lt;IUser, IUserModel&gt;

        以下是您的模型文件经过这些更改后的样子:

        import * as bcrypt from 'bcryptjs';
        import { Document, Schema, Model, model } from 'mongoose';
        
        import { IUserDocument } from '../interfaces/IUserDocument';
        
        export interface IUser extends IUserDocument {
            comparePassword(password: string): boolean; 
        }
        
        export interface IUserModel extends Model<IUser> {
            hashPassword(password: string): string;
        }
        
        export const userSchema: Schema = new Schema({
            email: { type: String, index: { unique: true }, required: true },
            name: { type: String, index: { unique: true }, required: true },
            password: { type: String, required: true }
        });
        
        userSchema.method('comparePassword', function (password: string): boolean {
            if (bcrypt.compareSync(password, this.password)) return true;
            return false;
        });
        
        userSchema.static('hashPassword', (password: string): string => {
            return bcrypt.hashSync(password);
        });
        
        export const User: IUserModel = model<IUser, IUserModel>('User', userSchema);
        
        export default User;
        

        您的(新重命名的)../interfaces/IUserDocument 模块将如下所示:

        import { Document } from 'mongoose';
        
        export interface IUserDocument extends Document {
            email: string;
            name: string;
            password: string;
        }
        

        【讨论】:

        • 惊人的答案,非常感谢!你知道如何在 Typescript 中模拟 IUserModel 吗?我有一个注入 IUserModel 的类 Foo,并且 Foo 的一些方法使用 IUserModel 静态。我想用假静态注入 IUserModel 的模拟,我该怎么做?
        • 非常感谢这个答案,花了一天时间才找到这个答案,但值得。谢谢
        • 这肯定是解决原始问题的正确方法!接受的答案确实是一种解决方法。
        • 补充一点:如果您在 IUserDocument 中有数组,我建议使用 mongoose.Types.Array&lt;T&gt; 作为属性类型。此类型包含额外的方法(例如addToSetpull
        • 这应该是 Mongoose 与 Typescript 的正确使用方法,我花了很长时间寻找 mongoose 与 ts 的解决方案,网上的教程都不完整,你应该发个博文关于使用 mongoose 和 typescript 使用这个 anwser
        【解决方案5】:

        对于未来的读者:

        请记住,我们正在处理两个不同的 Mongo/Mongoose 概念:模型和文档。

        可以从单个模型创建许多文档。模型是蓝图,文档是根据模型指令创建的东西。

        每个文档都包含自己的数据。每个还带有自己的实例方法,这些方法与自己的 this 相关联,并且只对那个特定的实例进行操作。

        模型可以有“静态”方法,这些方法不绑定到特定的 Document 实例,而是对整个 Documents 集合进行操作。

        这一切与 TypeScript 的关系:

        • 扩展文档以定义实例属性和.method 函数的类型。
        • 扩展(文档的)模型以定义 .static 函数的类型。

        这里的其他答案有不错的代码,因此请查看它们并跟踪文档的定义方式和模型的定义方式之间的差异。

        请记住,当您在代码中使用这些东西时,模型用于创建新文档并调用静态方法,例如 User.findOne 或您的自定义静态方法(例如上面定义的 User.hashPassword)。

        您可以使用 Documents 从对象中访问特定数据,或调用上面定义的 this.save 之类的实例方法和 this.comparePassword 之类的自定义实例方法。

        【讨论】:

        • 感谢您的解释。当在 typescript 中通过严格类型检查在文档上定义实例方法时,我收到 Property 'checkPassword' does not exist on type 'User' 其中 User 是我扩展 Document 的接口。但是,如果我将我的变量(User 类型)更改为user.schema.methods.checkPassword,它不会因为它是任何而抱怨。大图,user.checkPassword 抱怨,但当我的架构定义 UserSchema.methods.checkPassword = function( 时,user.schema.methods.checkPassword 没有我需要在我的界面中定义该方法吗?
        【解决方案6】:

        我看不到您的 IUser 界面,但我怀疑您没有在其中包含方法。 EG

        export interface IUser {
            email: string,
            hash: string,
            salt: string,
        
            setPassword(password: string): void,
            validPassword(password: string): boolean,
            generateJwt(): string
        }
        

        typescript 会识别你的方法并停止抱怨

        【讨论】:

        • 我现在无法访问源代码,但我尝试将方法添加到IUserModel,但没有添加到IUser。但这甚至会有所作为吗?不过,稍后会尝试。
        • 我不确定会有什么不同,尽管您定义方法的方式也与我的方式不同。我使用userSchema.methods.functionName = function(){} 格式我不确定这是否会影响您,但可能会。
        • 由于某些文档,我确实改变了声明方法的方式,但这并没有改变。当我有时间做的时候,我仍然会测试你的答案
        • 出于好奇,您是在 User 类还是实例上运行 hashPassword 方法?
        • 从架构运行它。我没有真正的用户类。 (User: Model&lt;IUserModel&gt;) 所以我用User.hashPassword(password) 来称呼它。
        猜你喜欢
        • 2019-11-05
        • 1970-01-01
        • 2021-06-22
        • 2023-04-02
        • 2018-08-17
        • 1970-01-01
        • 2023-02-06
        • 1970-01-01
        • 2021-03-18
        相关资源
        最近更新 更多