JavaScript使用最佳实践

1. 简介

JavaScript Action可以通过几个新功能扩展应用程序。要最有效地实现JavaScript Action,请务必遵循以下最佳实践。

此最佳实践将教您如何执行以下 Action:

  • 创建高质量的JavaScript Action
  • 学习常见的实现模式
  • 设计更好的API
  • 在nanoflows中使用JavaScript Action

 

2. 实施JavaScript Action

JavaScript Action在浏览器中运行,每个浏览器版本都有自己的JavaScript标准样式实现。因此,某些 Action可以在某些浏览器中运行,但不能在其他浏览器中运行。为了兼容性,建议使用ECMAScript 5。

较旧的浏览器可能没有实现新的ES6功能,因此从internet复制和粘贴现代示例代码时要小心,尤其是在必须支持IE11的情况下。一些ECMAScript 6函数由Mendix 客户端渲染填充。

Mendix Studio Pro包含来自Core JS的以下polyfills:

  • core-js/fn/array/find
  • core-js/fn/array/from
  • core-js/fn/array/includes
  • core-js/fn/array/fill
  • core-js/fn/array/find-index
  • core-js/fn/object/assign
  • core-js/fn/object/entries
  • core-js/fn/object/is
  • core-js/fn/object/set-prototype-of
  • core-js/fn/object/values
  • core-js/fn/string/starts-with
  • core-js/fn/string/ends-with
  • core-js/fn/string/pad-end
  • core-js/features/string/includes
  • core-js/features/promise
  • core-js/features/symbol
  • core-js/features/set
  • core-js/features/map

Mendix Studio Pro还包含一个用于Mozilla的fetchapi的polyfill

2.1 处理输入

创建JavaScript Action时,可以使用输入参数。您的JavaScript Action将被其他人使用,但您永远不知道它们是否会被正确使用。要使 Action更健壮,请验证所有输入参数,并在可能时启用默认值。

使用以下代码验证输入字符串文本:

/**

 * @param {string} text

 */

async function TextToSpeech(text) {

    // BEGIN USER CODE

    if (text === undefined) {

        // Throw an error when the parameter is set to 'empty', the value will be undefined

        throw new Error("The Text parameter is required");

    }

    if (text.trim() === "") {

        // Throw an error when the text is an empty string ""

        throw new Error("The Text parameter can not be empty");

    }

    /* implementation */

    // END USER CODE

  }

 

使用以下代码验证Mendix输入对象:

/**

 * @param {MxObject} audioFile

 */

async function PlayAudio(audioFile) {

    // BEGIN USER CODE

    if (!audioFile) {

        throw new Error("The 'Audio file' parameter can not be empty");

    }

    if (!audioFile.isA("System.FileDocument") && !audioFile.inheritsFrom("System.FileDocument")) {

        throw new Error("The 'Audio file' parameter should inherit from System.FileDocument");

    }

    if (!audioFile.get("HasContents")) {

        throw new Error("The 'Audio file' parameter does not have any content");

    }

    const allowedExtensions = ["mp3", "wav", "ogg"]

    const fileName = audioFile.get("Name");

    const dotIndex = fileName.lastIndexOf(".");

    const extension = fileName.substring(dotIndex + 1).toLowerCase();

    if (dotIndex === -1 || allowedExtensions.indexOf(extension) === -1) {

        throw new Error("The 'Audio file' parameter only supports files with extension .mp3, .wav or .ogg");

    }

    /* implementation */

    // END USER CODE

}

 

使用以下代码验证对象和属性名称的输入列表:

/**

 * @param {MxObject[]} objectList

 * @param {string} attributeName

 * @returns {Promise.}

 */

async function SumListAttributeValues(objectList, attributeName) {

    // BEGIN USER CODE

    if (!attributeName || attributeName.trim() === "") {

        throw new Error("The 'Attribute name' parameter can not be empty");

    }

    if (!objectList || objectList.length === 0) {

        // Return early, sum of empty is 0

        return new Big(0);

    }

    if (!objectList[0].has(attributeName)) {

        throw new Error("List of type " + objectList[0].getEntity() + " does not have an attribute named " + attributeName);

    }

    if (!objectList[0].isNumeric(attributeName)) {

        throw new Error("List of type " + objectList[0].getEntity() + " an attribute named " + attributeName + " is not numeric");

    }

    /* implementation */

    // END USER CODE

}

 

将此代码用于默认输入值:

/**

 * @param {Big} targetSize

 * @param {"Module.PictureSource.camera"|"Module.PictureSource.gallery"} pictureSource

 * @param {boolean} correctOrientation

 * @param {string} waterMark

 */

function CameraStart(targetSize, pictureSource, correctOrientation, waterMark) {

    // BEGIN USER CODE

    targetSize = targetSize && targetSize > 0 ? targetSize : 150;  // numeric

    pictureSource = pictureSource ? pictureSource : "camera"; // enumeration

    correctOrientation = correctOrientation ? true : false; // boolean

    waterMark = waterMark !== undefined ? waterMark : "DEMO"; // string

    /* implementation */

    // END USER CODE

}

 

2.2 编写Action代码 

要自定义JavaScript Action,请参阅下面的部分。

2.2.1 了解Mendix客户端API

在JavaScript Action中,提供了完整的Mendix客户端API。如需参考,请参阅Mendix客户端API。请注意,Mendix客户端API的某些部分是为小部件创建的,与JavaScript Action关系不大。

 

2.2.2 在JavaScript Action中使用数字参数

当您使用decimal、integer或long类型的参数时,您的参数将不像您在JavaScript中使用的那样是数字。相反,它将是一个Big对象,它来自一个名为big.js由Mendix客户使用。这是为了确保应用程序中使用的数字不受默认JavaScript数字限制的约束。

// Precision limitation of JavaScript numbers

0.1 + 0.2                  // 0.30000000000000004

// Solved with BigJs

x = new Big(0.1)

y = x.plus(0.2)            // '0.3'

 

如果您知道您的JavaScript Action不需要这种扩展精度(例如,如果您期望一个1到100之间的简单整数),那么您可以轻松地将一个big对象转换为JavaScript数字:

const numberValue = Number(bigJsValue); // number

了解如何使用big.js,咨询big.js应用程序编程接口。

 

2.2.3 创建对象

使用以下代码创建对象:

mx.data.create({

    entity: "MyFirstModule.Cat",

    callback: function(object) {

        console.log("Object created on server");

    },

    error: function(error) {

        console.error("Could not commit object:", error);

    }

});

有关创建对象的更多信息,请参阅Mendix客户端API的创建部分。

 

2.2.4 更改对象

使用以下代码更改对象:

mxobj.get("Name");               // "Fred"

mxobj.set("Name", "Henry");

mxobj.get("Name");               // "Henry"

mxobj.getOriginalValue("Name")   // "Fred"

 

2.2.5 装载平台

使用以下代码加载平台附带的依赖项(请注意,附带的依赖项可能因Mendix版本而异):

// Synchronous libs that are already loaded

var lang = require("mendix/lang");

 

Mendix客户提供以下库:

  • mendix/lang
  • mendix/validator
  • mxui/dom
  • mxui/html/parser

虽然有Dojo和Document Object Model(DOM)函数可用,但不建议使用它们。有关Dojo和DOM函数的更多信息,请参阅本文下面的“理解错误实践”部分。

 

2.2.6 在浏览器中使用外部依赖项

当前不支持加载和绑定外部库。在JavaScript中嵌入库代码和CSS并不理想。将库JavaScript文件和CSS添加到主题文件夹中,并在索引.html以及组件.json建议使用。

下面是使用基于pdf库的外部依赖关系的示例:

  1. 打开命令提示符并使用cd--your app folder--/javascriptsource/--ModuleName--/actions导航到正确的文件夹。
  2. 运行npm install pdf lib。
  3. 在JavaScript Action中,使用以下代码导入库:
  4. // This file was generated by Mendix Studio Pro.
  5. //
  6. // WARNING: Only the following code will be retained when actions are regenerated:
  7. // - the import list
  8. // - the code between BEGIN USER CODE and END USER CODE
  9. // - the code between BEGIN EXTRA CODE and END EXTRA CODE
  10. // Other code you write will be lost the next time you deploy the project.
  11. import {  Big } from "big.js";
  12. import {  PDFDocument } from "pdf-lib"
  13.  
  14. // BEGIN EXTRA CODE

 

2.2.7 了解Hybrid App程序的外部依赖关系

默认情况下,Mendix hybrid App附带了大量插件。

也可以在移动构建期间添加新插件。

实际使用的插件列表可以在部署包中的config.xml中找到。

 

2.3 了解返回(Return)

JavaScript Action可以指定返回类型,例如Integer、DateTime、Object、Object列表和泛型。

JavaScript Action可以是同步的,也可以是异步的。同步 Action将直接返回值并完成执行。异步 Action将返回一个promise,并将在稍后继续执行和完成。当promise得到解决时,Nano Flow将继续执行。

JavaScript的核心是一种同步编程语言,它一次运行一行代码。如果一行代码正在执行,它会阻止Mendix客户端中的所有其他JavaScript运行,从而使Mendix客户端看起来很慢。异步函数解决了这个问题。对于异步函数,当结果可用时,存储函数以供以后执行。这样,就不会阻止其他JavaScript运行。

当结果直接可用时,请使用以下代码使用同步返回:

/**

 * @param {Big} valueA

 * @param {Big} valueB

 * @param {Big}

 */

function AddValue(valueA, valueB) {

    // BEGIN USER CODE

    return valueA.plus(valueB)

    // END USER CODE

}

 

使用以下代码在nanoflow需要等待 Action完成时使用异步返回:

function Wait(delay) {

    // BEGIN USER CODE

    return new Promise(function(resolve) {

        window.setTimeout(function(){

            resolve();

        }, delay);

    });

    // END USER CODE

}

 

许多api和函数是以异步方式设计的,并且使用回调函数或promise。JavaScript Action要求返回一个promise。Promise应该按照行动中预期的返回值来解决。

 

2.3.1 理解promise

Promise对象表示异步 Action的最终完成(或失败)及其结果值。

使用以下代码将回调API包装到promise中:

function AskConfirmation(question) {

    // BEGIN USER CODE

    return new Promise(function (resolve) {

        mx.ui.confirmation({

            content: question,

            handler: function() {

                resolve(true);

            },

            onCancel: function() {

                resolve(false);

            }

        });

    });

    // END USER CODE

}

 

解释回调代码:

  • 使用标准的Mendix客户端显示一个带有“确定”和“取消”按钮的确认对话框(nanoflow的执行将暂停,直到用户单击其中一个按钮)
  • 解析将返回一个布尔值,用作 Action的返回值
  • 在nanoflow中,返回变量可用于确认和取消的替代流

 

2.3.2 了解Promise API

此函数使用Fetch API:

async function GetUserNameSampleRest(userID) {

    // BEGIN USER CODE

    if (!userID) {

        throw new Error("The UserID parameter is required")

    }

    const url = "https://jsonplaceholder.typicode.com/users/" + userID;

    try {

        const response = await fetch(url); // Fetch returns a promise, gets the url and wait for result

        const jsonData = await response.json(); // Transform to json

        return jsonData.name; // Get the data

    } catch (error) {

        throw new Error("Failed to get user information");

    }

    // END USER CODE

}

解释获取API代码:

  • URL引用了一个示例API,它返回一个JSON对象{id:string,name:string},fetch是一个浏览器API,用于检索返回promise的数据(请参阅MDI fetch API文档-响应是一个promise,它通过.JSON()函数转换为数据(名称被访问并返回)
  • 由于这是一个异步函数,所有三个步骤(fetch、parse JSON和访问数据)的错误处理都可以在一个try…catch块中完成(有关更详细的解释,请参阅MDN文档中有关async/await的错误处理)。

 

2.3.3 理解共同promise函数

以下是最常用的promise函数:

  • newpromise(executor):创建可以返回的新 –executor函数获取两个参数:resolve和reject函数
  • resolve(someValue):应与结果值一起调用–该值将在nanoflow返回值中使用
  • reject(rejectReason):在nanoflow中抛出一个错误,并停止执行
  • promise。决心(results):返回用结果解析的Promise对象,该对象将在nanoflow中 Action的输出变量中设置
  • promise.拒绝(errorMessage):返回被拒绝的Promise对象,并解释拒绝将导致Nano Flow中的错误
  • .then():启用链接promise,这将使代码比嵌套回调函数更易于阅读

JavaScript语言最近增加了异步函数和await关键字。这些特性使异步代码看起来更像同步代码,从而使异步代码更易于编写和读取。在代码中使用async/await有两个部分:

  • async关键字,将其放在函数声明前面,以将它们转换为异步函数(告诉它们返回promise,而不是直接返回值)
  • await可以放在任何基于异步promise的函数前面,在该行暂停代码,直到promise实现,然后返回结果值

对于错误处理,有两个选项:*使用带有async/await的同步try…catch结构,并将异步函数调用包装到其中。catch(error){}块将接收到rejected Promise*的error对象,该对象将.catch(error)块链接到.then()调用的末尾

 

2.3.3.1 使用Promise函数最佳实践

使用promise函数时,请注意以下几点:

  • 当前,JavaScript Action总是期望返回类型–如果 Action没有相关的返回值,请选择return type String(实现的返回或promise可以是未定义的)在nanoflow中使用JavaScript Action时,将output Use return变量设置为No返回类型Boolean不应返回未定义的值(如果在nanoflow中意外使用返回的变量,则会导致错误)建议尽早返回,以便在可以或应该跳过的情况下不执行任何代码—例如,在验证输入时
  • JavaScript中未捕获的错误会在微流中抛出错误——目前,没有办法像在微流中那样在nanoflows中添加错误处理程序

 

3. 制作可重用的JavaScript Action

要最有效地创建和优化JavaScript Action,请参阅下面的小节。

3.1 设计更好的API

有了设计良好的api,JavaScript Action将更易于重用。设计API时请考虑以下准则:

  • 创建小的功能性Actions——当具有许多功能的动作被分解成更小的组件时,Nano Flow可以以各种方式将它们组合起来
  • 生成最少数量的副作用–没有副作用的 Action没有状态,不依赖于其他组件的状态,因此可以独立运行(这也将使开发人员更容易进行测试和行为预测)
  • 不要实现可以用标准 Action完成的 Action,不要将新功能和现有功能组合成单个 Action(相反,在nanoflow中用多个 Action组合所需的功能)
  • 业务逻辑应该在nanoflow中—— Action应该只做事情、检查状态和获取数据
  • 为不知道Mendix客户端可以执行这些 Action的开发人员创建检查 Action和执行 Action—例如CheckCameraSupported和OpenCamera Action
  • 清楚地命名 Action和参数(这将在编写API文档时发挥更大的作用)
  • 记录 Action、参数、默认值、返回值、错误和兼容性–有关更多信息,请参阅下面的文档记录JavaScript Action部分
  • 只公开最通用的功能—公开功能较少的库更易于理解、使用、维护和测试(新功能总是可以稍后添加)
  • 设计一个独立于所用库的API(这样就可以在不更改API的情况下更改实现或替换库)
  • 使用开发人员熟悉的语言–例如,函数名不应包含实现细节;OpenPhoneGapCamera应为OpenCamera

考虑以下关于最佳API实践的附加技术建议:

  • 在自由格式字符串上,支持有限选项的枚举
  • 首选二进制参数的布尔选项–例如:“Blocking dialog”(true/false)
  • 首选基本返回类型–尽可能不返回对象,而是使用返回字符串或十进制
  • 不要更改对象;而是创建新的非持久化实体(NPE)对象—NPE对象应该与模块中的 Action一起提供,并且可以在各种不相关的NanoFlow中重用
  • 当您不工作时,使NPE对象依赖于实体或泛型参数(这样,您可以限制硬编码实体名称,当实体在域模型中重命名时,这些名称可能会产生错误)
  • 验证输入,永远不要相信开发人员正确地使用了 Action—有关更多信息,请参阅上面的文档的“处理输入”部分
  • 尽可能为输入参数提供合理的默认值

 

3.2 公开JavaScript Action

JavaScript Acton可以在带有JavaScript Action调用活动的nanoflow中使用。也可以公开活动列表中的 Action。这将使开发人员更容易找到 Action。建议仅公开将经常重用的 Action。

使用类别对动作进行分组,并使用图标使暴露的nanoflow动作在nanoflow中易于识别:

JavaScript使用最佳实践_第1张图片

 

在App Explorer中右键单击JavaScript Action,然后选择export document to file,可以导出单个 Action。然后,导出的文件可以与其他开发人员共享。单个Nano Flow不能在Mendix市场上发布。相反,将其作为模块发布。

您可以通过右键单击应用程序资源管理器中的模块,然后选择“从文件导入文档”来导入单个 Action。接下来,选择JavaScript Action文件。

单个Nano Flow行动不能在Mendix市场上公布。您可以将一个作为模块发布,但建议将相关的nanoflow Action作为模块内的一个组发布。对于包含多个nanoflow Action的模块,使用相关数据模型(如“实体”)对 Action进行分组,并为外部依赖关系提供相关文档。将模块作为一个整体导出并上传到Mendix Marketplace。

 

3.3 记录JavaScript Action

文档化的 Action更容易重用。记录时请考虑以下事项:

  • 正确的命名是文档最重要的方面
    • 使用“VerbNoun”命名约定,例如:GetUser
    • 使用自我解释含义的参数名称
  • 在 Action的“设置”>“文档”选项卡中,描述:
    • 行动在做什么
    • 返回值
    • 支持的平台,如web、移动或本机
    • 浏览器兼容性,如Chrome、FireFox或Edge
    • 相关模块(如有)
    • 用过的库或函数
  • 对于参数,添加描述并提供默认值(如果已实现)
  • 参考所用API的文档
  • 注意任何外部依赖项,并解释如何添加它们

 

4. 测试JavaScript Action

一个广泛的测试应用程序可以帮助使JavaScript Action更加健壮。在测试应用程序中,尝试创建输入的所有可能变化,考虑应该处理的空输入和错误情况。

测试时,请确保检查所有兼容平台(web、混合和本机)。web应该处理Mendix浏览器兼容性。有关兼容性的更多信息,请参阅系统需求的浏览器部分。

当 Action与平台不兼容时,请确保在遇到错误之前可以使用其他 Action检查该 Action。例如,在启动摄影机之前,使用CheckCameraSupport Action。当调用某个 Action但不兼容时,它应该优雅地失败或显示清晰的错误消息。

 

5. 调试JavaScript Action

Mendix Studio Pro不支持nanoflow调试,其方式与支持微流调试的方式相同。要验证 Action之间的中间结果,请使用日志消息活动。

调试JavaScript Action的代码可以在浏览器开发工具中完成。有关如何做到这一点的详细信息,请参阅浏览器的开发工具文档,网址为ChromeDevTools、Firefox developer tools、Microsoft Edge developer tools或Safari的Web开发工具。

最初,JavaScript Action的源代码未加载。它们的源代码将在模块 Action的第一次执行之前加载。从那一刻起,如果您使用Chrome,您可以在文件夹javascript Action中的Developer Tools的Sources选项卡中找到源代码。

加载文件后,可以通过单击内联编号(A,在下面的截图)在代码中设置断点。或者,选择暂停捕获的异常可以用来查找问题(B)。最后,您可以通过添加行调试器来更改源代码;(C)。此语句将在第一次执行 Action时启动调试工具,并将应用断点:

 

6. 了解不建议的做法

并非所有功能都建议使用。考虑 Action可能对Mendix客户端、DOM或其他小部件产生的副作用:

不要假定用户的浏览器–记住并非所有浏览器都具有相同的功能

永久性渲染应该使用可插入的小部件来完成——新的Mendix客户端将随意呈现页面并删除您的更改(例如,当您呈现DOM时,index.html))

由于Mendix客户端可以随意呈现DOM,因此可能会丢失对DOM的更改(例如,当您将CSS类添加到另一个组件时,Mendix客户端将随意呈现页面并删除您的更改)-您可以创建和更改放置在外部的DOM元素

避免使用不推荐的库–不要使用Dojo或Dijit,因为它们将被弃用(jQuery也不应该再使用)

避免使用返回undefined的布尔 Action–布尔变量是唯一需要值的变量,是唯一可接受的状态为true或false(其他变量可以设置为undefined,并且可以在Mendix Studio Pro中作为$variable != empty


更多信息,请访问以下链接:

Mendix官网:https://www.mendix.com/zh/

Mendix行业解决方案:https://solutions.mendix.com/

Mendix平台指南:https://www.mendix.com/evaluation-guide/

Mendix动画展示:https://www.mendix.com/demos/

Mendix公众号

 

谢谢阅读!

 

 

你可能感兴趣的:(Mendix,javascript,低代码开发,敏捷开发,低代码)