如何使用 Express / Node.JS 创建可在所有视图中访问的全局变量?

Posted

技术标签:

【中文标题】如何使用 Express / Node.JS 创建可在所有视图中访问的全局变量?【英文标题】:How to create global variables accessible in all views using Express / Node.JS? 【发布时间】:2013-05-03 08:37:15 【问题描述】:

好的,所以我使用Jekyll 构建了一个博客,您可以在文件_config.yml 中定义变量,这些变量可在所有模板/布局中访问。我目前正在使用Node.JS / Express 与EJS 模板和ejs-locals(用于部分/布局。我正在寻找类似于site.title 之类的全局变量,如果有人在_config.yml 中找到熟悉 Jekyll。我有网站标题(而不是页面标题)、作者/公司名称等变量,这些变量在我的所有页面上都保持不变。

这是我目前正在做的一个例子。:

exports.index = function(req, res)
    res.render('index',  
        siteTitle: 'My Website Title',
        pageTitle: 'The Root Splash Page',
        author: 'Cory Gross',
        description: 'My app description',
        indexSpecificData: someData
    );
;

exports.home = function (req, res) 
    res.render('home', 
        siteTitle: 'My Website Title',
        pageTitle: 'The Home Page',
        author: 'Cory Gross',
        description: 'My app description',
        homeSpecificData: someOtherData
    );
;

我希望能够在一个地方定义变量,如我的网站的标题、描述、作者等,并通过 EJS 在我的布局/模板中访问它们,而不必将它们作为选项传递给每次调用 res.render .有没有办法做到这一点并且仍然允许我传递特定于每个页面的其他变量?

【问题讨论】:

【参考方案1】:

在有机会进一步研究Express 3 API Reference 之后,我发现了我正在寻找的东西。特别是 app.locals 的条目,然后再往下一点 res.locals 包含我需要的答案。

我自己发现函数app.locals 接受一个对象并将其所有属性存储为应用程序范围内的全局变量。这些全局变量作为局部变量传递给每个视图。但是,函数 res.locals 的范围仅限于请求,因此,响应局部变量只能由在该特定请求/响应期间呈现的视图访问。

所以对于我在app.js 中的情况,我所做的是添加:

app.locals(
    site: 
        title: 'ExpressBootstrapEJS',
        description: 'A boilerplate for a simple web application with a Node.JS and Express backend, with an EJS template with using Twitter Bootstrap.'
    ,
    author: 
        name: 'Cory Gross',
        contact: 'CoryG89@gmail.com'
    
);

那么在我看来,所有这些变量都可以访问为site.titlesite.descriptionauthor.nameauthor.contact

我还可以使用res.locals 为对请求的每个响应定义局部变量,或者只是在render 调用中将页面标题等变量作为options 参数传递。

编辑:此方法将允许您在中间件中使用这些本地变量。正如 Pickels 在下面的评论中建议的那样,我确实遇到了这个问题。在这种情况下,您将需要在他的替代(和赞赏)答案中创建一个中间件函数。您的中间件函数需要为每个响应将它们添加到res.locals,然后调用next。这个中间件函数需要放置在需要使用这些局部变量的任何其他中间件之上。

编辑:通过app.localsres.locals 声明本地变量的另一个区别是app.locals 的变量被设置一次并在应用程序的整个生命周期中持续存在。当您在中间件中使用 res.locals 设置本地变量时,每次收到请求时都会设置这些变量。您基本上应该更喜欢通过app.locals 设置全局变量,除非该值取决于传递给中间件的请求req 变量。如果值没有改变,那么在app.locals 中设置一次会更有效。

【讨论】:

app.locals 比我建议的更有意义。但它有一个问题,您无法访问中间件中的本地变量。在这种情况下,它可能无关紧要,但这是您有时会遇到的问题之一。 您还可以在 js 或 json 文件中定义配置,并在需要的任何地方简单地 require() 它。该文件只会在应用程序期间加载一次,并且每个需要它的模块都可以访问它定义的值。查看***.com/questions/5869216/…的各种答案 Express 4locals不是一个函数,而是一个对象。设置属性:app.locals.site.title = 'Example'; 哇!现在整个应用程序可以通过单个文件进行配置。干净利落的一枪。 +1 @MarttiLaine 谢谢哥们!【参考方案2】:

您可以通过将它们添加到通用中间件中的 locals 对象来做到这一点。

app.use(function (req, res, next) 
   res.locals = 
     siteTitle: "My Website's Title",
     pageTitle: "The Home Page",
     author: "Cory Gross",
     description: "My app's description",
   ;
   next();
);

Locals 也是一个扩展 locals 对象而不是覆盖它的函数。所以下面的工作也是如此

res.locals(
  siteTitle: "My Website's Title",
  pageTitle: "The Home Page",
  author: "Cory Gross",
  description: "My app's description",
);

完整示例

var app = express();

var middleware = 

    render: function (view) 
        return function (req, res, next) 
            res.render(view);
        
    ,

    globalLocals: function (req, res, next) 
        res.locals( 
            siteTitle: "My Website's Title",
            pageTitle: "The Root Splash Page",
            author: "Cory Gross",
            description: "My app's description",
        );
        next();
    ,

    index: function (req, res, next) 
        res.locals(
            indexSpecificData: someData
        );
        next();
    

;


app.use(middleware.globalLocals);
app.get('/', middleware.index, middleware.render('home'));
app.get('/products', middleware.products, middleware.render('products'));

我还添加了一个通用渲染中间件。这样您就不必为每个路由添加 res.render,这意味着您可以更好地重用代码。一旦您走上可重用中间件路线,您会发现您将拥有大量构建块,这将极大地加快开发速度。

【讨论】:

我知道这是一个旧答案,但是您的 globalLocals 函数中 reqres 的参数是倒退的。 我可以这样查询全局数据吗,例如每个页面请求直接从数据库加载的导航栏数据?这似乎是这个想法可能超出了当地人的使用范围,例如猫鼬的叫声。我正在寻找每条路线上的直接加载,因为导航栏是全局的,稍后将研究缓存。或者,也许我需要一个运行一次的函数,然后在收集数据后将它们加载到本地。 res.locals 似乎不再是一个函数了。 我使用您的方法在页脚中显示一些产品。谢谢!【参考方案3】:

对于 Express 4.0,我发现使用应用程序级变量的工作方式略有不同,Cory 的回答对我不起作用。

来自文档:http://expressjs.com/en/api.html#app.locals

我发现你可以在

中为应用声明一个全局变量
app.locals

例如

app.locals.baseUrl = "http://www.google.com"

然后在您的应用程序中,您可以访问这些变量,并且在您的快速中间件中,您可以在 req 对象中访问它们

req.app.locals.baseUrl

例如

console.log(req.app.locals.baseUrl)
//prints out http://www.google.com

【讨论】:

另外,当在代码中添加新的本地人时,需要重新启动快速服务器。【参考方案4】:

在您的 app.js 中,您需要添加类似这样的内容

global.myvar = 100;

现在,在您想要使用此变量的所有文件中,您只需以myvar 的身份访问它

【讨论】:

我正在使用 require 模块,并且必须在任何地方将其引用为 global.myvar,这很有效。对于 socket.io,这是一个很好的简单解决方案,我想在其他文件中从处理程序发出消息。我把我的“io”对象放在global.myIO 上,一切都很好。 这在 Node 开发中实际上不被推荐或被认为是一个好的实践。 Node.JS 包含一个基于 CommonJS 系统的模块系统实现,完全围绕模块不共享全局范围的思想,而是使用 require 方法。这也会在 Node 中设置一个全局 js var,而不是像问题要求的那样为 Express 视图/模板设置一个本地变量。 @CoryGross 像这样将请求对象设置为全局变量是一种好习惯吗? 只有这个有效。谢谢F先生! @CoryGross 如何让我的应用程序中的所有文件访问单个对象? 这是最好的答案。这意味着全局对象可以传递给视图或您想要的任何地方。就像 global.cdnURL = "https://cdn.domain.com 一样,您可以将其传递给视图以加载静态内容。【参考方案5】:

你也可以使用“全局”

例子:

这样声明:

  app.use(function(req,res,next)
      global.site_url = req.headers.host;   // hostname = 'localhost:8080'
      next();
   );

这样使用: 在任何视图或 ejs 文件中

在 js 文件中 console.log(site_url);

【讨论】:

这将导致竞争条件。 global 没有针对该特定请求进行命名空间。【参考方案6】:

一种方法是在app.js 中更新该应用的app.locals 变量

通过关注设置

var app = express();
app.locals.appName = "DRC on FHIR";

获取/访问

app.listen(3000, function () 
    console.log('[' + app.locals.appName + '] => app listening on port 3001!');
);

使用来自@RamRovi 示例的屏幕截图进行详细说明,并略有增强。

【讨论】:

【参考方案7】:

有了不同的答案,我实现了这段代码以使用“app.locals”中加载的外部文件 JSON

参数


    "web": 
        "title" : "Le titre de ma Page",
        "cssFile" : "20200608_1018.css"
    

应用程序

var express     = require('express');
var appli       = express();
var serveur     = require('http').Server(appli);

var myParams    = require('./include/my_params.json');
var myFonctions = require('./include/my_fonctions.js');

appli.locals = myParams;

EJS 页面

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title><%= web.title %></title>
    <link rel="stylesheet" type="text/css" href="/css/<%= web.cssFile %>">
</head>

</body>
</html>

希望对你有帮助

【讨论】:

【参考方案8】:

为了避免全局范围受到污染,我要做的是创建一个可以在任何地方包含的脚本。

// my-script.js
const ActionsOverTime = require('@bigteam/node-aot').ActionsOverTime;
const config = require('../../config/config').actionsOverTime;
let aotInstance;

(function () 
  if (!aotInstance) 
    console.log('Create new aot instance');
    aotInstance = ActionsOverTime.createActionOverTimeEmitter(config);
  
)();

exports = aotInstance;

这样做只会创建一次新实例,并在包含文件的任何地方共享该实例。我不确定是因为变量被缓存还是因为应用程序的内部引用机制(可能包括缓存)。任何关于节点如何解决这个问题的 cmets 都会很棒。

也许还可以阅读本文以了解 require 的工作原理: http://fredkschott.com/post/2014/06/require-and-the-module-system/

【讨论】:

以上是关于如何使用 Express / Node.JS 创建可在所有视图中访问的全局变量?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建服务于 Vue.js SPA 的 Node.js Express 应用程序?

Node.js / Sequelize.js / Express.js - 如何插入多对多关联? (同步/异步?)

Node.js + express web应用如何打包部署?

如何使用在 node.js 上的方法和 express 来获取更新信息

如何打包和部署Node.js + express Web应用程序?

Socket.io 使用 node.js,根本没有 express?