打字稿和猫鼬:“文档”类型不存在属性“ x”

时间:2020-10-30 11:12:43

标签: javascript typescript mongoose mongoose-schema

这是我与TypeScript一起使用的Mongoose模型:

import mongoose, { Schema } from "mongoose";

const userSchema: Schema = new Schema(
  {
    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
    },
    name: {
      type: String,
      maxlength: 50,
    },
    ...
    ...
  }
);

userSchema.method({
  transform() {
    const transformed = {};
    const fields = ["id", "name", "email", "createdAt", "role"];

    fields.forEach((field) => {
      transformed[field] = this[field];
    });
    return transformed;
  },
});

userSchema.statics = {
  roles,
  checkDuplicateEmailError(err: any) {
    if (err.code === 11000) {
      var error = new Error("Email already taken");
      return error;
    }

    return err;
  },
};

export default mongoose.model("User", userSchema);

我在控制器中使用此模型:

import { Request, Response, NextFunction } from "express";
import User from "../models/user.model";
import httpStatus from "http-status";

export const register = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const user = new User(req.body);
    const savedUser = await user.save();
    res.status(httpStatus.CREATED);
    res.send(savedUser.transform());
  } catch (error) {
    return next(User.checkDuplicateEmailError(error));
  }
};

我收到以下错误:

类型“文档”上不存在属性“转换”。

类型'Model '中不存在属性'checkDuplicateEmailError'。

我尝试了export default mongoose.model<any>("User", userSchema);,但没有收到transform错误,但仍然是checkDuplicateEmailError的错误。

1 个答案:

答案 0 :(得分:1)

您知道mongoose.model("User", userSchema);创建了Model,但问题是:什么模型?

没有任何类型注释,模型User的类型为Model<Document, {}>,从user创建的new User()对象的类型为Document。因此,您当然会遇到类似“类型'Document'上不存在属性'transform'的错误。”

添加<any>变量后,user的类型变为any。与知道userDocument相比,实际上给我们的信息更少。

我们想要做的是为描述用户的特定类型Document创建一个模型。用户实例应具有方法transform(),而模型本身应具有方法checkDuplicateEmailError()。为此,我们将泛型传递给mongoose.model()函数:

export default mongoose.model<UserDocument, UserModel>("User", userSchema);

困难的部分是找出这两种类型。令人沮丧的是,虽然there are packages that do this,猫鼬不会自动将架构中的字段用作类型的属性。因此,我们必须将它们写为打字稿类型。

interface UserDocument extends Document {
  id: number;
  name: string;
  email: string;
  createdAt: number;
  role: string;
  transform(): Transformed;
}

我们的transform函数从UserDocument返回具有5个特定属性的对象。为了访问这些属性的名称而不必再次键入它们,我将fields方法内部的transform移到了顶级属性中。我使用as const来将它们的类型保留为字符串文字,而不仅仅是string(typeof transformFields)[number]给我们这些字符串的并集。

const transformFields = ["id", "name", "email", "createdAt", "role"] as const;

type Transformed = Pick<UserDocument, (typeof transformFields)[number]>

我们的UserModelModel的{​​{1}},它还包含我们的UserDocument函数。

checkDuplicateEmailError

在创建interface UserModel extends Model<UserDocument> { checkDuplicateEmailError(err: any): any; } 时,我们还应该添加UserDocument泛型,以便在我们通过模式方法访问Schema时,this的类型为UserDocument。 / p>

const userSchema = new Schema<UserDocument>({

尝试实现transform()方法时遇到各种打字错误,包括缺少索引签名。通过使用pick中的lodash方法,我们可以避免在这里重新发明轮子。我仍然遇到猫鼬methods()辅助函数的问题,但是使用直接分配方法可以正常工作。

userSchema.methods.transform = function (): Transformed {
  return pick(this, transformFields);
};

您还可以使用解构来避免索引签名问题。

userSchema.methods.transform = function (): Transformed {
  const {id, name, email, createdAt, role} = this;
  return {id, name, email, createdAt, role};
}

在您的电子邮件检查功能中,我添加了typeof检查,以避免在err.codeerr的情况下尝试访问属性undefined的运行时错误。

if ( typeof err === "object" && err.code === 11000) {

那应该可以解决所有错误。

Playground Link