65.9K
CodeProject 正在变化。 阅读更多。
Home

RequireJS 简单教程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2020年2月10日

MIT

13分钟阅读

viewsIcon

14679

downloadIcon

204

本教程将介绍使用 RequireJS 构建模块化 JavaScript 应用程序所需的基础知识。

引言

最近,我开始了一个新项目。这是一个基于 Web 的应用程序,需要一个 JavaScript 框架来支持一些简单的前端用户交互。我不需要 AngularJS,因为我需要的功能很简单,而且不需要 AngularJS 这样强大的 JavaScript 库。我曾考虑再次使用 JQuery,但考虑到单独使用它最终会造成混乱,我决定最好将几个小的、优秀的 JavaScript 库结合起来使用,并将 JavaScript 端模块化。RequireJS 可用于管理这些不同的库组件,并促使我思考代码的模块化,以及使组件在应用程序的不同区域可重用。在本教程中,我将讨论如何使用 RequireJS 的基础知识。我将讨论两个基本概念:

  1. 依赖注入
  2. 在应用程序不同部分重用组件

本教程中的示例应用程序非常简单。该 Web 应用程序是用 Spring Boot 创建的。它没有控制器。它唯一的功能是提供静态内容:网页和 JavaScript 文件。该应用程序由一个网页组成。它显示一个表单,允许用户输入一个人的联系信息,然后单击“提交”将信息发送到后端。页面将显示一个状态消息,指示请求已成功发送。该应用程序还执行输入验证。如果字段输入无效,它将显示一个状态消息,指示字段验证失败。

状态消息显示可以是一个可重用组件。在一个页面或许多其他页面中,可以有很多位置显示验证状态消息。我们可以一遍又一遍地在所有涉及这些位置的 JavaScript 文件中重复相同的代码。或者,我们可以编写一个可重用的函数或对象来替换这些重复的代码。在本教程中,我将展示如何创建这样的组件。最后,该应用程序将演示依赖配置和注入,这使得该应用程序看起来像一个 AngularJS 应用程序。

我知道这可能没什么意义,那么看看源代码怎么样?

页面标记

我先从 HTML 页面标记开始。这个示例应用程序只有一个页面,看起来是这样的:

我再重复一下我之前介绍的关于这个应用程序的内容。该页面接受一个人的全名和邮寄地址。输入字段没有基于 HTML5 的验证(输入验证通过 JavaScript 代码完成)。屏幕底部有两个按钮,允许用户提交(单击“提交”按钮)或重置(单击“清除”按钮)表单。

该应用程序页面标记的 HTML 文件存储在名为“index.html”的文件中。页面标记如下:

<!DOCTYPE html>
<html>
   <head>
      <title>Test</title>
      <link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
      <link href="/assets/css/index.css" rel="stylesheet">

      <script src="/assets/jquery/js/jquery.min.js"></script>
      <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
      <script src="/assets/requirejs/require.js"></script>
   </head>
   <body>
      <div class="container">
         <div class="row">
            <div class="col-xs-12 col-sm-offset-1 colsm-10 col-md-offset-2
                        col-md-8 col-lg-offset-3 col-lg-6">
               <h3>Require JS Sample App</h3>
               <div class="panel panel-default">
                  <div class="panel-body">
                     <form id="testAppForm">
                        <div id="statusDisp"></div>
                        <div class="form-group">
                           <label for="firstName">First Name</label>
                           <input class="form-control" type="text"
                           id="firstName" name="firstName" placeholder="First Name">
                        </div>
                        <div class="form-group">
                           <label for="lastName">Last Name</label>
                           <input class="form-control" type="text"
                           id="lastName" name="lastName" placeholder="Last Name">
                        </div>
                        <div class="form-group">
                           <label for="addressLine1">Address Line 1</label>
                           <input class="form-control" type="text"
                           id="addressLine1" name="addressLine1" placeholder="Address Line 1">
                        </div>
                        <div class="form-group">
                           <label for="addressLine2">Address Line 2</label>
                           <input class="form-control" type="text"
                           id="addressLine2" name="addressLine2" placeholder="Address Line 1">
                        </div>
                        <div class="form-group">
                           <label for="city">City</label>
                           <input class="form-control" type="text"
                           id="city" name="city" placeholder="City">
                        </div>
                        <div class="form-group">
                           <label for="state">State</label>
                           <input class="form-control" type="text"
                           id="state" name="state" placeholder="State">
                        </div>
                        <div class="form-group">
                           <label for="zipCode">Zip Code</label>
                           <input class="form-control" type="text"
                           id="zipCode" name="zipCode" placeholder="Zip Code">
                        </div>
                        <div class="row">
                           <div class="col-xs-12 col-sm-6">
                              <button class="btn btn-success form-control"
                              type="submit" id="submitBtn">Submit</button>
                           </div>
                           <div class="col-xs-12 col-sm-6">
                              <button class="btn btn-danger form-control"
                              type="reset" id="clearBtn">Clear</button>
                           </div>
                        </div>
                     </form>
                  </div>
               </div>
            </div>
         </div>
      </div>

      <script type="text/javascript">
      ...
      </script>
   </body>
</html>

这是一个非常简单的标记,一个带有七个输入字段的表单。状态字段应该是下拉列表,并且州应该从后端服务器加载。这里没有。相反,我匆忙处理了,只是将其创建为一个文本框。而且,我暂时将底部的脚本留空了。

自 JQuery 问世以来,我们将事件处理方法移到了脚本文件或部分。页面上的按钮具有点击事件,如前所述。单击“提交”按钮将触发输入验证,并在顶部显示状态消息,如下所示:

当所有输入数据检查通过后,单击“提交”按钮将显示以下状态消息:

为了让所有这些都能工作,我需要添加一些 JavaScript 代码。我做的第一件事是创建引导代码,即启动配置。还记得上面留空的 JavaScript 部分吗?那就是引导 JavaScript 代码。

启动配置的“引导”

我要指出的一个地方是,在这种情况下,当我提到“引导”时,我并不是指 Bootstrap UI 框架,我也为这个项目使用了该框架。我所说的“引导”是指我将要设置的配置,以便 RequireJS 可以加载不同的组件,从而构建一个工作的 Web 应用程序。这与 Bootstrap 框架不同。

您可以在“index.html”文件最底部找到我的引导代码。它看起来是这样的:

   require.config({
       paths: {
         jquery: "./assets/jquery/js/jquery.min",
         underscore: "./assets/underscore/underscore-min-1.9.2",
         statusDisplay: "./assets/app/components/statusDisplay/statusDisplay",
         stapes: "./assets/stapes/stapes-min-1.0.0",
         app: "./assets/app/app"
      }
   });

   require(["app"], function(app) {
      app.init();
   });

这段代码有两个部分。第一部分是设置所有组件的配置。RequireJS 需要知道两件事:

  • 组件的名称。此名称可以视为哈希映射的键,与该键关联的值将是该组件。
  • JavaScript 文件的位置,以便 RequireJS 可以加载该组件的源代码。

在这部分,我添加了另外两个库。一个是 Stapes,它是 BackboneJS 框架的升级版本。但是,我将其视为 JQuery 之上的包装器。另一个是 UnderscoreJS。我使用它的模板功能通过模板字符串渲染实际的 HTML。这是我创建可重用且交互式 HTML 组件的方式。我将创建另一个关于这两个库的教程。

第二部分是实际应用程序的启动。由于我使用了 Stapes JS 库进行 HTML 元素操作,我所要做的就是调用初始化代码,UI 将自行处理交互。

这仅仅是开始。这个应用程序都关于可重用组件。接下来,我将向您展示如何创建这样的可重用组件。

可重用组件

如前所述,状态显示可以在应用程序的许多地方使用。最好将其提取为一个可重用组件。在这些位置,或者占位符可以设置好,以便后端 JS 代码可以插入可重用组件以形成最终的输出显示。

这是这个可重用组件的完整源代码:

define(["jquery", "underscore"], function ($, _) {
   var statusHtml = '<div class="col-xs-12">\n'+
   '   <div class="<%= alertStyleVal%>"><%= msgToShow%></div>\n'+
   '</div>';

   var statusTemplate = _.template(statusHtml);
   const errorStyle = "alert alert-danger small-alert";
   const successStyle = "alert alert-success small-alert";

   function renderStatus(outerDiv, msg, style) {
      if (outerDiv) {
         if (msg != null && msg.length > 0) {
            var divToAdd = statusTemplate({
               alertStyleVal: style,
               msgToShow: msg
            });
            $(outerDiv).html(divToAdd);
         }
      }
   }

   return {
      clearRender: function(outerDiv) {
         if (outerDiv) {
            $(outerDiv).html("");
         }
      },

      renderSuccess: function (outerDiv, msg) {
         renderStatus(outerDiv, msg, successStyle);
      },

      renderError: function (outerDiv, msg) {
         renderStatus(outerDiv, msg, errorStyle);
      }
   };
});

我将一步一步地解释所有这些内容。第一个是这个闭包:

define(["jquery", "underscore"], function ($, _) {
...
});

这是一个函数调用。该函数称为 define(...)。它是 RequireJS 的一个函数,用于定义一个组件。在这种情况下,该函数接受两个参数,第一个是数组,它接受几个字符串值,即依赖组件的名称。这就是依赖注入发生的地方。对于这个可重用组件,我需要 JQuery 和 UnderscoreJS。第二个参数是函数定义,参数列表的顺序与依赖项的名称相同。参数“$”是对 JQuery 的引用,而“_”是对 UnderscoreJS 的引用。有了这两个,我就可以像使用这两个框架一样使用它们了。

接下来,我声明一些变量并实例化它们:

...
   var statusHtml = '<div class="col-xs-12">\n'+
      '   <div class="<%= alertStyleVal%>"><%= msgToShow%></div>\n'+
      '</div>';

   var statusTemplate = _.template(statusHtml);
   const errorStyle = "alert alert-danger small-alert";
   const successStyle = "alert alert-success small-alert";
...

前两行定义了显示 HTML 源代码模板。在这里:

   var statusHtml = '<div class="col-xs-12">\n'+
      '   <div class="<%= alertStyleVal%>"><%= msgToShow%></div>\n'+
      '</div>';

然后我需要一个模板函数,以后可以用来注入一些值,并编译成 HTML 字符串以放置在最终的 HTML 页面中。这是我声明它的方式:

   var statusTemplate = _.template(statusHtml);

我所做的是调用 UnderscoreJS 库中的一个名为 template() 的方法。它接受模板源值,并返回一个函数引用。我们将在下一步看到它的实际应用。以下代码定义了一个函数,该函数将模板源渲染成 HTML 代码,然后附加到现有的 HTML 元素。这是:

   function renderStatus(outerDiv, msg, style) {
      if (outerDiv) {
         if (msg != null && msg.length > 0) {
            var divToAdd = statusTemplate({
               alertStyleVal: style,
               msgToShow: msg
            });
            $(outerDiv).html(divToAdd);
         }
      }
   }

该函数执行一些基本的数据验证,确保要显示的消息不为 null 或为空。它还检查要附加状态显示的元素是否不为 null。一旦检查通过,它就使用模板函数引用来渲染 HTML 显示字符串。然后,外部元素用于附加新创建的 HTML 显示值。

最后一部分是返回表示我正在定义的组件的对象。这是:

   ...
   return {
      clearRender: function(outerDiv) {
         if (outerDiv) {
            $(outerDiv).html("");
         }
      },

      renderSuccess: function (outerDiv, msg) {
         renderStatus(outerDiv, msg, successStyle);
      },

      renderError: function (outerDiv, msg) {
         renderStatus(outerDiv, msg, errorStyle);
      }
   };
   ...

这个返回的对象有三个方法:

  • 第一个名为 clearRender()。它接受输入 HTML 元素的引用,然后清除其内部 HTML 值。
  • 第二个将成功状态消息添加到目标 HTML 元素。成功状态消息具有绿色背景。
  • 第三个将错误状态消息添加到目标 HTML 元素。错误状态消息具有红色背景。

现在我们有了一个可重用组件,让我们看看它如何被注入到页面组件中并被利用。

控制页面元素的代码

这是示例应用程序的复杂部分。有一个带有七个输入字段和两个按钮的页面。按钮必须关联事件处理方法。正如我在开头所说,我不想使用 JQuery。但它太流行了,不可能放弃它。所以我把它用作一个可注入的组件,并且主要用作 DOM 查询,以及附加新的元素,如前一节所示。在本节中,我使用 Stapes 框架来编程用户交互。

Stapes 框架基于 BackboneJS。在我看来,它非常酷。不幸的是,它已不再维护,可能没有人使用这个框架。无论如何,它给我留下了深刻的印象。我可能今年晚些时候会写一篇关于它的教程。对于本教程,我只用它来为按钮添加事件处理。

  • 当用户单击“提交”时,将首先发生输入数据验证,如果输入数据有任何问题,将显示红色错误状态消息。如果所有输入数据都正确,将显示成功状态消息。
  • 另一个按钮清除所有输入字段。

包含所有这些内容的文件的名称是“app.js”。这是其内容的完整列表:

define(["jquery", "stapes", "statusDisplay"], function ($, Stapes, statusDisplay) {

   var testAppForm = Stapes.subclass({
      constructor : function() {
         var self = this;
         self.$el = $("#testAppForm");

         var statusDisp = self.$el.find("#statusDisp");
         var firstNameInput = self.$el.find("#firstName");
         var lastNameInput = self.$el.find("#lastName");
         var addrLine1Input = self.$el.find("#addressLine1");
         var addrLine2Input = self.$el.find("#addressLine2");
         var cityInput = self.$el.find("#city");
         var stateInput = self.$el.find("#state");
         var zipCodeInput = self.$el.find("#zipCode");

         self.$el.on("submit", function(e) {
            e.preventDefault();
            if (validateForm()) {
               statusDisplay.renderSuccess(statusDisp,
                             "Your request has been handled successfully.");
            }
         });

         self.$el.on("reset", function(e) {
            e.preventDefault();
            clearCommentForm();
         });

         function clearCommentForm() {
            statusDisplay.clearRender(statusDisp);
            firstNameInput.val("");
            lastNameInput.val("");
            addrLine1Input.val("");
            addrLine2Input.val("");
            cityInput.val("");
            stateInput.val("");
            zipCodeInput.val("");
         }

         function validateForm() {
            statusDisplay.clearRender(statusDisp);
            var firstNameVal = firstNameInput.val();
            if (firstNameVal == null || firstNameVal.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your first name cannot be null or empty");
               return false;
            }

            if (firstNameVal != null && firstNameVal.length > 128) {
               statusDisplay.renderError(statusDisp,
                  "Your first name is too long, 128 characters or fewer.");
               return false;
            }

            var lastNameVal = lastNameInput.val();
            if (lastNameVal == null || lastNameVal.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your last name cannot be null or empty");
               return false;
            }

            if (lastNameVal != null && lastNameVal.length > 128) {
               statusDisplay.renderError(statusDisp,
                  "Your last name is too long, 128 characters or fewer.");
               return false;
            }

            var addressLine1Val = addrLine1Input.val();
            if (addressLine1Val == null || addressLine1Val.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your address line #1 cannot be null or empty.");
               return false;
            }

            if (addressLine1Val != null && addressLine1Val.length > 128) {
               statusDisplay.renderError(statusDisp,
                  "Your address line #1 cannot have more than 128 characters");
               return false;
            }

            var addressLine2Val = addrLine2Input.val();
            if (addressLine2Val != null && addressLine2Val.length > 128) {
               statusDisplay.renderError(statusDisp,
                  "Your address line #2 cannot have more than 128 characters");
               return false;
            }

            var cityVal = cityInput.val();
            if (cityVal == null || cityVal.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your city is null or empty.");
               return false;
            }

            if (cityVal != null && cityVal.length > 48) {
               statusDisplay.renderError(statusDisp,
                  "Your city cannot have more than 48 characters");
               return false;
            }

            var stateVal = stateInput.val();
            if (stateVal == null || stateVal.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your state is null or empty.");
               return false;
            }

            if (stateVal != null && stateVal.length > 2) {
               statusDisplay.renderError(statusDisp,
                  "Your state cannot have more than 2 characters");
               return false;
            }

            var zipCodeVal = zipCodeInput.val();
            if (zipCodeVal == null || zipCodeVal.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your state is null or empty.");
               return false;
            }

            if (zipCodeVal != null && zipCodeVal.length > 12) {
               statusDisplay.renderError(statusDisp,
                  "Your zip code cannot have more than 12 characters");
               return false;
            }

            return true;
         }
      }
   });

   return {
      init: function() {
         new testAppForm();
      }
   };
});

您应该知道这做什么。

define(["jquery", "stapes", "statusDisplay"], function ($, Stapes, statusDisplay) {
...
});

它将 JQuery、Stapes 和我的组件 statusDisplay 注入到此组件中。RequireJS 使用 defined 来将其注册为另一个组件。

接下来,我声明一个 Stapes 类类型。这是我的做法:

   var testAppForm = Stapes.subclass({
         constructor : function() {
            ...
         }
      });

请注意,我说这是一个我正在创建的类型,而不是一个对象。对于这个新类型,它只包含一个方法,即构造函数。在这个构造函数中,我首先要做的就是获取输入字段和按钮的句柄,如下所示:

      ...
         var self = this;
         self.$el = $("#testAppForm");

         var statusDisp = self.$el.find("#statusDisp");
         var firstNameInput = self.$el.find("#firstName");
         var lastNameInput = self.$el.find("#lastName");
         var addrLine1Input = self.$el.find("#addressLine1");
         var addrLine2Input = self.$el.find("#addressLine2");
         var cityInput = self.$el.find("#city");
         var stateInput = self.$el.find("#state");
         var zipCodeInput = self.$el.find("#zipCode");
      ...

如所示,我必须使用 JQuery 来获取这些输入字段的句柄。并将它们保存为我正在创建的类型的一部分。至于按钮,我附加了点击事件处理方法,就像这样:

      ...
         self.$el.on("submit", function(e) {
            e.preventDefault();
            if (validateForm()) {
               statusDisplay.renderSuccess(statusDisp,
                             "Your request has been handled successfully.");
            }
         });

         self.$el.on("reset", function(e) {
            e.preventDefault();
            clearCommentForm();
         });
      ...

如所示,所有这些都是 JQuery 代码,如何查询 HTML 元素,以及如何将事件处理附加到按钮。对于这两个按钮,在事件处理方法中,第一件事是调用事件对象“e”的 preventDefault() 方法。这将阻止按钮执行实际的提交或重置功能。然后该方法将执行自定义函数。

在上面的代码片段中,您可以看到正在使用 status display 可重用组件。在我们的页面中,有这个 <div>

<div id="statusDisp"></div>

在我上面的代码中,它获取了这个 div 的引用:

...
var statusDisp = self.$el.find("#statusDisp");
...

最后,代码可以将状态显示添加到页面:

   ...
   statusDisplay.renderSuccess(statusDisp, "Your request has been handled successfully.");
   ...

对于“提交”按钮,有输入字段的验证。任何验证失败都将触发错误状态消息的显示。如果所有输入字段验证都成功,将显示成功消息,模拟假设的数据传输到后端服务器。

数据输入验证漫长而乏味。我只会展示 validateForm() 方法的一部分:

         function validateForm() {
            statusDisplay.clearRender(statusDisp);
            var firstNameVal = firstNameInput.val();
            if (firstNameVal == null || firstNameVal.length <= 0) {
               statusDisplay.renderError(statusDisp,
                  "Your first name cannot be null or empty");
               return false;
            }

            if (firstNameVal != null && firstNameVal.length > 128) {
               statusDisplay.renderError(statusDisp,
                  "Your first name is too long, 128 characters or fewer.");
               return false;
            }

            ...

            return true;
         }

最后,清除输入字段的功能,它所做的就是将所有字段的值设置为空字符串:

         function clearCommentForm() {
            statusDisplay.clearRender(statusDisp);
            firstNameInput.val("");
            lastNameInput.val("");
            addrLine1Input.val("");
            addrLine2Input.val("");
            cityInput.val("");
            stateInput.val("");
            zipCodeInput.val("");
         }

这就是我使用 Stapes 创建的类型所做的一切。接下来,我将返回该组件,以便它可以在页面 HTML 中使用。这是:

define(["jquery", "stapes", "statusDisplay"], function ($, Stapes, statusDisplay) {
...
   return {
      init: function() {
         new testAppForm();
      }
   };
});

这是 app 组件。它只有一个名为 init() 的方法。它所做的就是实例化我使用 Stapes 创建的类型的一个对象。它怎么会起作用呢?好吧,它之所以起作用,是因为类型构造函数会从页面中查找所有 HTML 元素句柄,并附加所有事件处理程序。因此,所有对元素和事件处理方法的引用都将一直保存到页面卸载。

如何启动应用程序

现在回到页面源代码。JavaScript 源代码部分有这个:

   ...
   require(["app"], function(app) {
      app.init();
   });
   ...

对 RequireJS 的 require() 函数的调用基本上会调用作为第二个参数提供的函数。第一个是依赖项数组。提供的函数调用 app 对象的 init() 方法,该方法又创建了我使用 Stapes 创建的类型的实例。实例化将设置页面上的事件处理。如所示,此应用程序的整个设置确实很简单。

现在已经讨论了该应用程序的所有内容,让我们看看如何测试此应用程序。

如何构建和测试

在构建此应用程序之前,请将示例项目中的任何文件从 *.sj 重命名为 *.js

一旦您获取了示例项目,并在本地将其解压到您的计算机上,您就需要安装 Java 1.8 和 Maven 3.0 或更高版本才能进行编译。编译示例对象的方法是运行以下命令:

mvn clean install

构建成功完成后,您可以运行以下命令来启动 Web 应用程序:

java -jar target/testapp-0.0.1-SNAPSHOT.jar 

一旦命令成功启动,您就可以使用浏览器访问以下 URL 来测试应用程序:

https://:8080/

如果启动命令工作正常,页面应该看起来像顶部的 截图 #1。您可以输入一些信息,然后单击“提交”按钮查看将显示什么状态消息。

摘要

在本教程中,我基本介绍了如何使用 RequireJS 通过组件和依赖注入来实现 Web 应用程序。使用 RequireJS,您可以轻松地将应用程序分解为不同的组件,然后将它们缝合在一起形成一个工作的应用程序。模块化应用程序始终是一个好主意。它将应用程序分解为不同的齿轮,这些齿轮可以相互配合,其中一些可以在同一应用程序的不同地方重用。模块化可以使应用程序整洁、井然有序,有时易于测试。

我使用 JQuery 已经很长时间了,但从未对其作为许多复杂应用程序的唯一框架感到满意,并且使用了糟糕的妥协,并且在原地塞入了丑陋的代码片段。这次,我决定采取不同的做法,所以我选择了 RequireJS、Stapes 和 UnderscoreJS。框架是否过时或是否有人使用它并不重要。只要它能在应用程序中发挥作用,就可以并且应该使用它。我从未用过这三个库中的任何一个。所以我把它们用在了这个简单的示例应用程序中。我觉得这些库非常有效。

我拼凑的示例应用程序只是一个概念性的应用程序,我只是把它组合在一起。我将把这里完成的所有内容转移到我正在构建和仍在构建的应用程序中。在这个示例应用程序中,应用程序很简单,它有一个表单,七个输入字段,以及用于提交和重置表单的两个按钮。提交表单时,会进行字段验证,并显示错误状态显示或成功状态显示。状态显示被提取为一个可重用组件。本教程展示了所有这些如何组合在一起。这不算多。对我来说,这些库展示了如何正确使用它们来创建优秀应用程序的巨大潜力。

历史

  • 2020 年 2 月 9 日 - 初稿
© . All rights reserved.