Nodejs,使用ES6类作为Express路由

28

我希望整理一下我的项目,现在我尝试使用es6类来管理路由。但我的问题是this始终为undefined。

var express = require('express');
var app = express();

class Routes {
    constructor(){
        this.foo = 10
    }

    Root(req, res, next){
        res.json({foo: this.foo}); // TypeError: Cannot read property 'foo' of undefined
    }
}

var routes = new Routes();
app.get('/', routes.Root);
app.listen(8080);
6个回答

20

尝试使用代码来固定 this

app.get('/', routes.Root.bind(routes));

您可以使用下划线bindAll函数来摆脱样板文件。例如:

var _ = require('underscore');

// ..

var routes = new Routes();
_.bindAll(routes, 'Root')
app.get('/', routes.Root);

我还发现es7允许以更优美的方式编写代码:

class Routes {
    constructor(){
        this.foo = 10
    }

    Root = (req, res, next) => {
        res.json({foo: this.foo});
    }
}

var routes = new Routes();
app.get('/', routes.Root);

1
直到es7到来之前 - 我喜欢使用_.bindAll,我以前完全不知道它。这比在每个路由中将用户绑定到自身要好得多! - Huston Hedinger
1
根据underscore文档,您需要提供要绑定的方法名称列表:_.bindAll(object, *methodNames),因此在您的示例中应使用_.bindAll(routes, 'Root') - Craig Myles

12

你遇到这个问题是因为你将一个方法作为独立函数传递给了express。Express并不知道它来自哪个类,因此当调用你的方法时不知道要使用哪个值作为this

你可以使用bind强制指定this的值。

app.get('/', routes.Root.bind(routes));

或者你可以使用一种替代构造来管理路由。您仍然可以利用面向对象编程的许多语法优势,而不需要类。

function Routes() {
  const foo = 10;

  return {
    Root(req, res, next) {
      res.json({ foo });
    }
  };
}

const routes = Routes();
app.get('/', routes.Root);
app.listen(8080);
  • 您不必担心this的值
  • 无论函数是否使用new被调用,都没有关系
  • 您可以避免在每个路由上调用bind的复杂性

这里有一个很好的资源列表(链接),解释了为什么ES6类并不像它们看起来那么好。


9
import express from 'express';
const app = express();

class Routes {
    constructor(){
        this.foo = 10
    }

    const Root = (req, res, next) => {
        res.json({foo: this.foo}); // TypeError: Cannot read property 'foo' of undefined
    }
}

const routes = new Routes();
app.get('/', routes.Root);
app.listen(8080);

这是代码的一些微小改写,但正如这里的某些答案指出的那样,当路由配置中引用函数本身时,该函数本身不知道 this 并且必须绑定。为了解决这个问题,您只需要编写自动绑定自身的“胖箭头”函数,而不是编写“普通”的函数,就可以解决这个问题!


2
似乎如果你没有使用TS,你不能在类内部使用“const”来定义成员函数。我还想指出,在类内部使用箭头函数会使该函数对继承类成为“私有”的,即super.Root()将无法使用。 - Andre Ravazzi

4

如果您不喜欢按路线绑定上下文,您可以选择将其绑定到类构造函数中的方法。

例如:

constructor() {
   this.foo = 10;
   this.Root = this.Root.bind(this);
}

3

我们最近将所有的Express控制器重构为使用基础控制器类,并且也遇到了这个问题。我们的解决方案是让每个控制器在构造函数中调用以下帮助方法,将其方法绑定到自身:

  /**
   * Bind methods
   */
  bindMethods() {

    //Get methods
    const proto = Object.getPrototypeOf(this);
    const methods = [
      ...Object.getOwnPropertyNames(Controller.prototype),
      ...Object.getOwnPropertyNames(proto),
    ];

    //Bind methods
    for (const method of methods) {
      if (typeof this[method] === 'function') {
        this[method] = this[method].bind(this);
      }
    }
  }

这可以确保父控制器方法和子控制器类中的任何自定义方法正确绑定(例如Foo extends Controller)。


1
上面的答案似乎有些复杂。看看我在这里做的:
class Routes {
  constructor(req, res, next) {
    this.req = req;
    this.res = res;
    this.next = next;
    this.foo = "BAR"
    // Add more data to this. here if you like
  }

  findAll (){
    const {data, res,} = this; // Or just reference the objects directly with 'this'
    // Call functions, do whaterver here...
    // Once you have the right data you can use the res obejct to pass it back down

    res.json ({foo: this.foo}); // Grabs the foo value from the constructor

  }
}

现在,当涉及使用此类时,您可以按照以下方式操作:
var express = require('express');
var router = express.Router();
var {Routes} = require('./Routes');

router.get('/foo', (req, res, next) => {
  new Routes(req, res, next).findAll();
});

我建议将这两个文件分开,这样你只需在Router文件中引用Routes类即可。
希望对你有所帮助!

1
这似乎比被接受的答案要复杂得多。这需要您在类中拥有req、res和next()的私有成员。这些类成员实际上只是中间件函数,只需要提供与所需的Express中间件类型相同的签名即可。使用.bind方法是解决这个设计考虑的最佳方案。值得一提的是,我正在使用TypeScript,看起来效果很不错。 - Michael Leanos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接