在现代 Web 开发中,模块化性能优化是两个核心诉求。Dojo 作为一个成熟的前端框架,通过其强大的部件(Widget)体系自定义构建工具,为我们提供了优雅的解决方案。本文将带你深入了解 Dojo 的部件开发,并学习如何通过框架部署优化来提升应用性能。


1. 基础部件

Dojo 的部件(Widget)是构建用户界面的基础单元,它们封装了 HTML、CSS 和 JavaScript 逻辑,提供了可复用、可扩展的 UI 组件。

1.1 dijit/form/TextBox

dijit/form/TextBox 是 Dojo 中最常用的表单控件之一,它提供了比原生 <input> 更丰富的功能,如输入验证占位符禁用状态等。

示例代码:

require([
    "dijit/form/TextBox",
    "dojo/dom",
    "dojo/domReady!"
], function(TextBox, dom) {
    // 创建一个 TextBox 实例
    var usernameTextBox = new TextBox({
        placeHolder: "请输入用户名", // 占位符文本
        required: true, // 是否为必填项
        trim: true, // 自动去除首尾空格
        style: {
            width: "300px",
            margin: "10px"
        },
        onChange: function(value) {
            // 值改变时触发的事件
            console.log("用户名已改为:", value);
        }
    }, "username"); // 将部件挂载到 id 为 "username" 的 DOM 节点上

    // 手动设置值
    usernameTextBox.set("value", "admin");

    // 获取当前值
    console.log("当前用户名:", usernameTextBox.get("value"));
});

HTML 结构:

<input type="text" id="username">

核心特性:

  • 输入验证:通过 requiredpattern 等属性进行基础验证。
  • 事件监听onChangeonFocusonBlur 等事件,方便处理用户交互。
  • 样式定制:支持通过 style 属性或 CSS 类进行样式调整。
1.2 dijit/layout/TabContainer

dijit/layout/TabContainer 是一个布局部件,用于将内容分成多个标签页,用户可以通过点击标签切换显示内容。它常用于复杂表单、数据展示等场景。

示例代码:

require([
    "dijit/layout/TabContainer",
    "dijit/layout/ContentPane",
    "dojo/dom",
    "dojo/domReady!"
], function(TabContainer, ContentPane, dom) {
    // 创建 TabContainer 实例
    var tabContainer = new TabContainer({
        style: {
            width: "500px",
            height: "300px",
            margin: "10px"
        },
        tabPosition: "top", // 标签位置:top、bottom、left、right
        nested: false // 是否允许嵌套标签
    }, "tabContainer");

    // 创建第一个标签页
    var tab1 = new ContentPane({
        title: "基本信息", // 标签标题
        content: `
            <div style="padding: 10px;">
                <label>姓名:</label>
                <input type="text" id="name" style="width: 200px;"><br><br>
                <label>年龄:</label>
                <input type="number" id="age" style="width: 200px;">
            </div>
        `
    });

    // 创建第二个标签页
    var tab2 = new ContentPane({
        title: "兴趣爱好",
        content: `
            <div style="padding: 10px;">
                <label>爱好:</label><br>
                <input type="checkbox" id="hobby1"> 篮球<br>
                <input type="checkbox" id="hobby2"> 读书<br>
                <input type="checkbox" id="hobby3"> 旅游
            </div>
        `
    });

    // 将标签页添加到 TabContainer 中
    tabContainer.addChild(tab1);
    tabContainer.addChild(tab2);

    // 启动 TabContainer
    tabContainer.startup();
});

HTML 结构:

<div id="tabContainer"></div>

核心特性:

  • 标签切换:支持通过点击标签或编程方式切换标签页。
  • 布局灵活:标签位置可自定义,支持嵌套标签。
  • 内容动态加载:可以通过 href 属性加载远程内容。

2. 自定义构建

Dojo 框架默认包含了大量的模块和部件,但在实际项目中,我们可能只用到其中的一部分。通过自定义构建,我们可以裁剪掉不必要的模块,减小框架体积,提升应用加载性能。

2.1 自定义构建的核心概念
  • 模块依赖:Dojo 采用 AMD 规范,模块之间通过 require 和 define 进行依赖管理。
  • 构建配置文件:用于定义构建规则,如要包含的模块、输出路径、压缩方式等。
  • 层(Layer):将多个模块打包成一个文件,减少 HTTP 请求次数。
2.2 自定义构建的步骤
步骤 1:安装 Dojo 构建工具

首先,确保你已经安装了 Node.js 和 npm。然后,通过 npm 安装 Dojo 构建工具:

npm install -g dojo-cli
步骤 2:创建构建配置文件

在项目根目录下创建一个 profile.js 文件,用于定义构建规则。

示例 profile.js

var profile = {
    // 基础路径
    basePath: "./src",

    // 要构建的包
    packages: [
        {
            name: "dojo",
            location: "dojo" // dojo 核心库路径
        },
        {
            name: "dijit",
            location: "dijit" // dijit 部件库路径
        },
        {
            name: "app",
            location: "app" // 自定义应用模块路径
        }
    ],

    // 构建输出目录
    outDir: "./build",

    // 层定义
    layers: {
        // 核心层:包含应用所需的 Dojo 核心模块
        "dojo/dojo": {
            include: [
                "dojo/dojo",
                "dojo/dom",
                "dojo/on",
                "dojo/fx",
                "dijit/form/TextBox",
                "dijit/layout/TabContainer"
            ],
            exclude: [
                "dojo/request/script", // 排除不需要的模块
                "dojo/request/xhr"
            ]
        },

        // 应用层:包含自定义应用模块
        "app/main": {
            include: [
                "app/main",
                "app/widgets/MyWidget"
            ],
            exclude: [
                "dojo/dojo" // 排除已包含在核心层中的模块
            ]
        }
    },

    // 压缩选项
    optimize: "uglify", // 压缩方式:uglify、closure、none

    // 是否生成 source map
    sourceMaps: true,

    // 是否保留版权信息
    copyright: "© 2024 My App"
};

module.exports = profile;
步骤 3:执行构建命令

在项目根目录下运行以下命令开始构建:

dojo build --profile profile.js
步骤 4:使用构建后的文件

构建完成后,会在 ./build 目录下生成压缩后的模块文件。在应用中,通过以下方式引入构建后的文件:

<script src="build/dojo/dojo.js"></script>
<script>
    require(["app/main"], function(main) {
        main.init();
    });
</script>
2.3 自定义构建的优势
  • 减小文件体积:裁剪掉不必要的模块,核心库体积可减小 50% 以上。
  • 提升加载速度:减少 HTTP 请求次数,压缩后的文件加载更快。
  • 优化性能:构建过程中会进行代码优化,如死代码消除、变量名混淆等。

3. 开发模块化表单页面并优化部署

3.1 项目需求

开发一个用户注册表单页面,包含以下功能:

  • 分步骤填写表单(基本信息、账号信息、兴趣爱好)。
  • 表单验证(必填项、格式验证)。
  • 提交表单时显示加载动画。
  • 通过自定义构建优化框架部署。
3.2 项目结构
project/
├── src/
│   ├── dojo/          # Dojo 核心库
│   ├── dijit/         # Dijit 部件库
│   ├── app/           # 自定义应用模块
│   │   ├── main.js    # 应用入口
│   │   └── widgets/   # 自定义部件
│   │       └── FormWidget.js  # 表单部件
│   └── index.html     # 页面入口
├── profile.js         # 构建配置文件
└── build/             # 构建输出目录(自动生成)
3.3 代码实现
3.3.1 自定义表单部件(app/widgets/FormWidget.js
define([
    "dojo/_base/declare",
    "dijit/_WidgetBase",
    "dijit/_TemplatedMixin",
    "dijit/layout/TabContainer",
    "dijit/layout/ContentPane",
    "dijit/form/TextBox",
    "dijit/form/PasswordTextBox",
    "dijit/form/Button",
    "dojo/text!app/widgets/FormWidget.html",
    "dojo/on",
    "dojo/fx"
], function(
    declare,
    _WidgetBase,
    _TemplatedMixin,
    TabContainer,
    ContentPane,
    TextBox,
    PasswordTextBox,
    Button,
    template,
    on,
    fx
) {
    return declare([_WidgetBase, _TemplatedMixin], {
        templateString: template,

        postCreate: function() {
            this.inherited(arguments);

            // 创建 TabContainer
            this.tabContainer = new TabContainer({
                style: "width: 100%; height: 400px;",
                tabPosition: "top"
            }, this.tabContainerNode);

            // 基本信息标签页
            var basicTab = new ContentPane({
                title: "基本信息"
            });
            this.nameTextBox = new TextBox({
                label: "姓名:",
                required: true,
                placeHolder: "请输入姓名"
            });
            basicTab.addChild(this.nameTextBox);

            // 账号信息标签页
            var accountTab = new ContentPane({
                title: "账号信息"
            });
            this.usernameTextBox = new TextBox({
                label: "用户名:",
                required: true,
                placeHolder: "请输入用户名"
            });
            this.passwordTextBox = new PasswordTextBox({
                label: "密码:",
                required: true,
                placeHolder: "请输入密码"
            });
            accountTab.addChild(this.usernameTextBox);
            accountTab.addChild(this.passwordTextBox);

            // 兴趣爱好标签页
            var hobbyTab = new ContentPane({
                title: "兴趣爱好"
            });
            this.hobbyTextBox = new TextBox({
                label: "兴趣爱好:",
                placeHolder: "请输入兴趣爱好,多个用逗号分隔"
            });
            hobbyTab.addChild(this.hobbyTextBox);

            // 添加标签页到 TabContainer
            this.tabContainer.addChild(basicTab);
            this.tabContainer.addChild(accountTab);
            this.tabContainer.addChild(hobbyTab);

            // 创建提交按钮
            this.submitButton = new Button({
                label: "提交",
                onClick: dojo.hitch(this, this.onSubmit)
            });
            this.submitButton.placeAt(this.submitButtonNode);

            // 启动部件
            this.tabContainer.startup();
        },

        onSubmit: function() {
            // 表单验证
            if (!this.nameTextBox.isValid() ||
                !this.usernameTextBox.isValid() ||
                !this.passwordTextBox.isValid()) {
                alert("请填写必填项!");
                return;
            }

            // 显示加载动画
            fx.fadeIn({
                node: this.loadingNode,
                duration: 300
            }).play();

            // 模拟表单提交
            setTimeout(dojo.hitch(this, function() {
                fx.fadeOut({
                    node: this.loadingNode,
                    duration: 300,
                    onEnd: dojo.hitch(this, function() {
                        alert("表单提交成功!");
                    })
                }).play();
            }), 2000);
        }
    });
});
3.3.2 表单部件模板(app/widgets/FormWidget.html
<div class="form-widget">
    <div id="tabContainerNode"></div>
    <div style="text-align: center; margin-top: 10px;">
        <div id="submitButtonNode"></div>
    </div>
    <div id="loadingNode" style="display: none; text-align: center; margin-top: 10px;">
        <img src="loading.gif" alt="加载中...">
    </div>
</div>
3.3.3 应用入口(app/main.js
define([
    "app/widgets/FormWidget",
    "dojo/dom",
    "dojo/domReady!"
], function(FormWidget, dom) {
    var formWidget = new FormWidget({}, dom.byId("formContainer"));
    formWidget.startup();
});
3.3.4 页面入口(src/index.html
<!DOCTYPE html>
<html>
<head>
    <title>用户注册表单</title>
    <link rel="stylesheet" href="dijit/themes/claro/claro.css">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .form-widget {
            width: 600px;
            margin: 0 auto;
            padding: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
    </style>
</head>
<body class="claro">
    <h1 style="text-align: center;">用户注册</h1>
    <div id="formContainer"></div>

    <script src="dojo/dojo.js" data-dojo-config="async: true"></script>
    <script>
        require(["app/main"]);
    </script>
</body>
</html>
3.4 自定义构建与部署优化
3.4.1 构建配置文件(profile.js
var profile = {
    basePath: "./src",
    packages: [
        { name: "dojo", location: "dojo" },
        { name: "dijit", location: "dijit" },
        { name: "app", location: "app" }
    ],
    outDir: "./build",
    layers: {
        "dojo/dojo": {
            include: [
                "dojo/dojo",
                "dojo/dom",
                "dojo/on",
                "dojo/fx",
                "dijit/layout/TabContainer",
                "dijit/layout/ContentPane",
                "dijit/form/TextBox",
                "dijit/form/PasswordTextBox",
                "dijit/form/Button"
            ]
        },
        "app/main": {
            include: ["app/main", "app/widgets/FormWidget"],
            exclude: ["dojo/dojo"]
        }
    },
    optimize: "uglify",
    sourceMaps: true
};

module.exports = profile;
3.4.2 执行构建
dojo build --profile profile.js
3.4.3 部署构建后的应用

将 build 目录下的文件部署到 Web 服务器,修改 index.html 中的脚本引入路径:

<script src="build/dojo/dojo.js" data-dojo-config="async: true"></script>
<script>
    require(["app/main"]);
</script>
3.5 优化效果
  • 文件体积减小:构建后的 dojo.js 和 app/main.js 体积大幅减小。
  • 加载速度提升:减少了 HTTP 请求次数,页面加载时间缩短。
  • 性能优化:代码压缩和优化后,运行效率更高。

4. 最后小结

4.1 核心要点回顾
  • 部件开发:Dojo 部件是 UI 构建的核心,通过封装 HTML、CSS 和 JavaScript,实现了组件的可复用性和可维护性。
  • 自定义构建:通过裁剪不必要的模块和打包成层,可以显著减小框架体积,提升应用性能。
  • 模块化部署:构建后的应用更适合生产环境部署,能够提供更快的加载速度和更好的用户体验。
4.2 进阶方向
  • 自定义部件开发:学习如何创建更复杂的自定义部件,如数据表格、图表等。
  • 构建优化技巧:深入了解构建配置选项,如代码分割、懒加载等。
  • 性能监控与调优:使用浏览器开发者工具和性能监控工具,持续优化应用性能。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐