★Dart-3-构建和测试Dart app

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了★Dart-3-构建和测试Dart app相关的知识,希望对你有一定的参考价值。


是时候弄点真正的Dart代码了。使用变量、函数和类的核心语言结构,您将构建一个名为 PackList的简单的基于浏览器的装箱单应用程序(packing-list app),使用户可以跟踪假日中要做的事情。Dart的设计目标之一是让人对它熟悉;在我们在后面的章节中介绍更令人惊讶和有趣的特性之前,本章将帮助您熟悉有关变量、函数和类的Dart功能。

您将使用内置dart:html库中的Element类来构建简单的用户界面,而不是使用原始HTML构建简单的用户界面。在撰写本文时,Dart SDK中没有GUI或小部件库,尽管各种开源第三方库正在开发中。Dart开发团队的目标是使Dart成为一个“包含电池(batteries included)”的解决方案,并且最终将在SDK中包含一个UI库。不过现在,您可以通过在Dart代码中操作HTML元素来构建用户界面;当UI库出现时,了解如何做到这一点也会对您有所帮助,因为您将对小部件背后的底层机制更有信心。

尽管PackList示例应用程序简单明了,但在现实世界中,您也可以通过这种方式创建单页web应用程序。简单的UI将包含一个输入文本框,用于从用户处获取一些输入,一个按钮供用户单击,以及一个<div>以显示用户希望在假日携带的物品列表。您的应用程序将通过添加事件侦听器函数对用户事件作出反应,该函数允许用户向列表中添加项目并将其标记为已打包。最后,您将创建一个类来保存该项(以及该项是否已打包)。该设计为代码提供了结构化和可重用性。

PackList示例与web应用程序的服务器端部分无关。该应用程序可以直接从本地文件在浏览器中运行(尽管您也可以将文件托管在web服务器上)。PackList只在客户端运行;我将在第14章中讨论在客户端和服务器之间来回发送数据。该应用程序最终应如下图所示。

到本章结束时,您将拥有一个工作的浏览器托管的单页应用程序和一组可用于验证代码的简单单元测试。

1.用dart:html构建UI

像PackList应用程序这样的单页应用程序通过在web浏览器中执行代码来创建和操作HTML元素来构建其UI。这种方法的优点是将UI显示逻辑保留在浏览器中(例如根据用户数据的状态做出布局决策),这最终可以释放服务器资源以服务更多用户。在单页应用程序设计中,服务器将Dart应用程序代码作为静态文件发送到浏览器,然后在应用程序开始运行后将数据发送到浏览器。

您将从承载Dart脚本的入口点HTML文件和在浏览器中创建HTML元素并将其附加到HTML文档的Dart代码文件构建PackList应用程序。

创建dart:html元素
在Dart中构建UI时,请使用dart:html库,它是Dart SDK中的Dart库之一。dart:html通过抽象出浏览器DOM的一些特性(类似于javascript流行的jQuery库),为您提供了一种与浏览器交互的标准化方法。使用dart:html,您可以创建html元素(例如按钮、<div>等)并将其附加到浏览器。HTML元素使用父元素类(以及子类,如DivElement和ButtonElement)表示,您可以通过编程访问HTML元素上可用的所有属性和方法。元素可以包含子元素、ID和样式以及附加事件监听器。

事实上,您正在使用Element(以及DivElement和ButtonElement)接口的实现,但这对您来说是透明的,您的代码编写者有一个“看起来像”Element的实例。我将在第6章中更多地讨论接口及其与类的关系。

在Dart中创建HTML元素有两种方法:
➀创建一个空的HTML标记,需要通过编程方式填充它,例如<div></div>
➁生成一个预构建的HTML标记,如<h2 id="title">Pack<em>List</em></h2>。(<em> 标签告诉浏览器把其中的文本表示为强调的内容)

要创建新Element,请使用new关键字调用dart:html Element构造函数。此步骤将创建一个新元素对象,用于存储在变量中,然后附加到body中。
dart:html允许您创建元素的第一种方法是创建空HTML标记:
var myElement=new-Element.tag('div');// 'div’是HTML的tag name

第二种方法构造一个元素,其中包含在代码段中指定的子元素和属性:
var myElement=new Element.html('<h2 id=“title”>Pack<em>List</em></h2>');// '<h2…</h2>'是HTML tag

这两种方法都有各自的优点,在构建应用程序的UI时,我们将在本节中介绍它们。

PackList应用程序将有一个<h2>标题、一个文本框、一个按钮和一个<div>元素,以包含每个度假项目。您将在Dart代码中创建这些元素,并将它们添加到document body中;生成的HTML类似于下图。

从HTML片段创建新元素:
对于使用构造函数new Element.html("...some html...");创建Elements来说,当您想要提供一个HTML字符串来创建一个元素,并且满足以下条件之一时,此选项非常有用:
■ 预先知道元素(及其子元素)的外观。
■ 不需要在Dart中将每个子元素作为变量单独引用(你以后仍然可以通过查询来访问它们)。

您可以将任何HTML字符串传递到Element.html构造函数中,只要是生成一个顶级HTML元素就行:
“<p>Some html</p><p>Another line</p>”
以上HTML字符串无效,因为包含两个顶级<p>元素。
“<div><p>Some html</p><p>Another line</p></div>”
有效,因为两个<p>元素都在<div>这个顶级元素里面。

在Dart中声明字符串:
在Dart中声明字符串时,有许多选择。可以使用三重引号声明多行字符串,例如:

var myString = """<div>
	<p>a multiline string</p>
</div>""";

这将存储一个字符串,其格式如下:

<div>
	<p>a multiline string</p>
</div>

如果像如下声明字符串:

var myString = """<div>
					<p>a multiline string</p>
				  </div>""";

输出以下字符串(可能不是预期的):

<div>
					<p>a multiline string</p>
				  </div>

幸运的是,对于HTML代码段,我们不关心最后的字符串是否是多行的,但我们关心可读的代码。Dart允许您自动连接相邻的字符串文字(如果两个字符串相邻,即使跨换行符,它们也会连接在一起)。因此,以下两个字符串声明存储相同(但不是多行)的值:

var myString = "<div>" "<p>a string</p>" "</div>";
var myString = "<div>"
				  "<p>a string</p>"
				"</div>";

第二个myString可以生成漂亮、可读的代码,也不会影响HTML输出。

Element.html构造函数的理想用法如下:

var paragraphContent = "Some about box text";
Element infoBoxDiv = new Element.html("""
<div id='infoBox'>
  <h3>About PackList</h3>
  <p>$paragraphContent</p>// 注:将paragraphContent变量嵌入到带有$前缀的HTML代码段中
</div>""");

这是一个理想的元素集,因为您不需要引用aboutBox <div>的子元素,并且文本是相对静态的。paragraphContent变量嵌入在多行字符串中。

将变量嵌入到字符串声明中:
Dart还提供了一种将变量嵌入字符串的简单方法:$variableName${expression}。此功能允许您按如下方式声明字符串:

var myValue = 1234;
var myString = "<p>$myValue</p>";
var myOtherString = "<p>${myValue + 1}</p>";

示例将"<p>1234</p>“存储在myString,将”<p>1235</p>"存储在myOtherString中。

使用名为Element.html的构造函数创建元素时,仍然可以访问元素变量上的所有属性和方法。如下:

infoBoxDiv.children.add(new Element.html("<p>a second paragraph</p>");
var id = infoBoxDiv.id;

创建HTML元素的第二种方法是使用Element.tag()构造函数,它为您提供了一个空元素,您可以在Dart代码中填充和操作该元素。

通过tag name创建元素:

var itemInput = new Element.tag("input");// 创建映射到HTML的InputElement:<input></input>
itemInput.id = "txt-item";
itemInput.placeholder = "Enter an item";

这将创建如下所示的HTML:

<input id="txt-item" placeholder="Enter an item"></input>

无论使用Element.tag()还是Element.html(),都会返回一个看起来像dart:html元素的对象。不过,有时访问特定元素类型(如InputElement或ButtonElement)上可用的额外属性很有用。对于Dart的可选类型,运行的代码不关心您是否指定了元素的实际类型,但是声明元素的特定类型可能会很有用,因此如果您试图以对特定对象没有意义的方式使用元素,类型检查器可以提供警告,从而提供帮助。例如,您可以使用以下任一项创建新文本框:

var itemInput = new Element.tag("input");
Element itemInput = new Element.tag("input");
InputElement itemInput = new Element.tag("input");

第三行指定itemInput是一个InputElement(而不是ButtonElement或DivElement)让工具(和代码的其他读取器)确认您打算处理InputElement。您还可以使用Dart编辑器中InputElement的特定属性和方法方便地完成代码。

您希望在HTML中找到的所有元素都在dart:html库中定义了一个等效的类型,包括最新的HTML5元素,如CanvasElement。

Dart还提供了dart:dom库。它提供了对浏览器DOM的直接访问,相当于JavaScript DOM操作,但代价是无法使用一致的Dart元素接口。

添加元素到HTML document:
dart:html在库的顶层定义了一个文档属性,它本身包含一个body元素属性。在代码中引用此document.body属性,使用如下形式:
document.body.children.add(...some element...)

顶级文档还定义了document.head属性,该属性可用于将元关键字或文档标题元素等元素动态附加到HTML页面标题中。

import "dart:html";
main() {
	var title = new Element.html("<h2>PackList</h2>");
	document.body.children.add(title);
	
	InputElement itemInput = new Element.tag("input");
	document.body.children.add(itemInput);
	
	ButtonElement addButton = new Element.tag("button");
	document.body.children.add(addButton);
	
	DivElement itemContainer = new Element.tag("div");
	document.body.children.add(itemContainer);
}

当应用程序开始运行时,您会得到包含以下代码段的HTML:

<body>
	<h2>PackList</h2>
	<input></input>
	<button></button>
	<div></div>
</body>

除了向body中添加元素外,还需要填充新元素的某些属性,例如:

inputButton.placeholder = "Enter an item";
addButton.text = "Add";
addButton.id = "add-btn";

**注:**无论在将元素添加到浏览器主体之前还是之后执行此操作,浏览器都将根据需要进行更新,以反映HTML元素的当前状态。

最后,要完成UI,需要添加一个<div>元素来包含假日项目列表。通过使用一些element.style属性(如下清单所示),您可以将样式信息直接应用到<div>(在现实世界中,使用CSS进行布局格式化)。

import "dart:html";
main() {
	var title = new Element.html("<h2>PackList</h2>");
	document.body.children.add(title);
	
	InputElement itemInput = new Element.tag("input");
	itemInput.id = "txt-item";
	itemInput.placeholder = "Enter an item";
	document.body.children.add(itemInput);
	
	ButtonElement addButton = new Element.tag("button");
	addButton.id = "btn-add";
	addButton.text = "Add";
	document.body.children.add(addButton);
	
	DivElement itemContainer = new Element.tag("div");
	itemContainer.id = "items";
	itemContainer.style.width = "300px";// set element style:width
	itemContainer.style.border = "1px solid black";// set element style:border
	itemContainer.innerHTML = "&nbsp;";
	document.body.children.add(itemContainer);
}

当您运行应用程序时,它会生成如下图所示的UI。

通过使用Element.html构造函数,还可以更简洁地创建itemContainer DivElement,如下所示:

DivElement itemContainer = new Element.html('<div id="items" style="width:300px;border:1px solid black">&nbsp;</div>');

记住:
■ 您可以使用Element.html(…snippet…)或Element.tag(…tag name…)创建元素类型。
■ dart:html库定义了现代浏览器能够理解的所有元素。
■ Dart编辑器可以帮助您提供属性的自动完成信息(api.dartlang.org上的API文档可以帮助提供更多详细信息)。
■ 元素在浏览器中成为HTML标记。属性是这些标记上的属性。
■ 所有元素(包括body)都有一个包含其子元素列表的children属性。

2.使用浏览器事件构建交互

要让UI对用户事件(如按钮单击)作出反应,请使用事件监听器。dart:html事件监听器是一个接受单个事件参数的函数,该参数是实现Event接口的类型,如以下代码段中的类型:

myEventListenerFunction(Event event) {
	window.alert("Look - an event has been triggered");
}

event参数提供有关事件的额外信息。和Element及ButtonElement类型一样,Event是一种通用类型。根据创建事件的元素,可以处理特定类型的事件。例如,如果是MouseEvent,则event参数包含一个标志,指示单击了左按钮还是右按钮。PackList应用程序不需要知道这一点,尽管它只需要知道单击了“Add”按钮,所以通用Event对象就可以了。

main() {
	// ...snip ui element creation code...
	addButton.onClick.listen((event) {
		var packItem = itemInput.value;
		var listElement = new Element.html("<div class='item'>${packItem}<div>");
		itemContainer.children.add(listElement);
		itemInput.value= "";// clear the inputBox
	});
}

语法糖:

// 定义一
int add(int a, int b) {
	return a + b;
}
// 升级:定义二
int add(int a, int b) => a + b;
// 再升级:定义三
add(a, b) => a + b;// 无参数类型 且 无返回类型,这都是允许的

在PackList应用程序中,addButton.onClick.listen()将函数作为其参数,您可以传入事件处理程序,而无需先给它命名,如下所示:

addButton.onClick.listen(
	(event) {// 匿名函数
		// function body
	}
);
// 也可以先将函数存储在一个变量中,如下:
var myEventListener = (event) => ...single statement...;
addButton.onClick.listen(myEventListener);

记住:
■ 函数具有多行语法和速记语法。
■ 函数返回类型信息和参数类型信息是可选的。
■ 匿名函数可以作为参数传递并存储在变量中。

响应浏览器事件:
例如,按钮可能产生如下事件:

addButton.onClick
addButton.onDrag
addButton.onMouseMove

事实上,这些事件中的每一个都是另一个列表(特别是EventListenerList),其中包含一个事件监听器列表。
添加事件监听:addButton.onClick.listen(myEventListenerFunction);
移除事件监听:addButton.onClick.remove(myEventListenerFunction);

重构事件监听器以便重用:

itemInput.onKeyPress.listen((event) {
	if (event.keyCode == 13) {// keyCode 13 is the Enter key
		var packItem = itemInput.value;
		var listElement = Element.html("<div class='item'>${packItem}</div>");
		itemContainer.children.add(listElement);
		itemInput.value = "";
	}
});

应将该代码块提取到main()函数之外的单独函数中,以便在应用程序中重用:

addItem() {
	var packItem = itemInput.value;
	var listElement = Element.html("<div class='item'>${packItem}</div>");
	itemContainer.children.add(listElement);
	itemInput.value = "";
}

这样一来,实现点击“Add”按钮或键盘按下Enter键,都完成添加一个条目的功能:

addButton.onClick.listen((event)=>addItem());
itemInput.onKeyPress.listen((event) {
	if (event.keyCode == 13)
		addItem();
});

很明显,由于addItem()函数是定义在main()外的,addItem()函数本身就会因元素未定义而报错。但这种封装出一个可复用的函数的策略是很好的。这就通过CSS选择器来解决这个问题。

查询HTML元素:

querySelector()函数的官方说明:
dart:html
Element? querySelector(String selectors)
Type: Element? Function(String)
查找与指定选择器组匹配的此文档的第一个子元素。除非您的网页包含多个文档,否则顶级querySelector方法的行为与此方法相同,因此您应该使用它来保存键入的几个字符。
参数selectors是一个满足CSS选择器语法的字符串。
var element1 = document.querySelector('.className');
var element2 = document.querySelector('#id');

addItem() {
	var itemInputList = querySelectorAll("input");
	InputElement itemInput = itemInputList[0];
	DivElement itemContainer = querySelector("#items");
	// ...snip...rest the function body
}

到目前为止,完整的应用程序列表如下,它现在通过让用户单击鼠标或按Enter键添加条目来对事件作出反应。

import 'dart:html';
main() {
	var title = new Element.html("<h2>PackList</h2>");
	document.body.children.add(title);
	
	InputElement itemInput = new Element.tag("input");
	itemInput.id = "txt-item";
	itemInput.placeholder = "Enter an item";
	itemInput.onKeyPress.listen((event) {
		if (event.keyCode == 13)
			addItem();
	});
	document.body.children.add(itemInput);
	
	ButtonElement addButton = new Element.tag("button");
	addButton.id = "btn-add";
	addButton.text = "Add";
	addButton.onClick.listen((event) => addItem());
	document.body.children.add(addButton);
	
	DivElement itemContainer = new Element.tag("div");
	itemContainer.id = "items";
	itemContainer.style.width = "300px";
	itemContainer.style.border = "1px solid black";
	itemContainer.innerHTML = "&nbsp;";
	document.body.children.add(itemContainer);
}
addItem() {
	var itemInputList = querySelectorAll("input");
	InputElement itemInput = itemInputList[0];
	
	DivElement itemContainer = querySelector("#items");
	var itemText = itemInput.value;
	var listElement = new Element.html("<div class='item'>${itemText}<div>");
	itemContainer.children.add(listElement);
	itemInput.value = "";
}

此例中,还可以通过itemContainer.querySelectorAll(".item")来得到所有新添加的条目元素。

记住:
■ dart:html中的事件监听器是一个接受单个事件参数的函数。
■ 您可以添加多个事件监听器来监听正在引发的单个事件。
■ dart:html允许您使用querySelector()函数使用CSS选择器查询单个元素。
■ 使用querySelectorAll()函数查询多个子元素。

3.用类包装结构和功能

PackList应用程序的最后一步是用户能够勾选打包的项目。用户应该能够通过单击项目在正在打包和未打包的项目之间切换。

<body>
	<style type="text/css">
		.item { cursor:pointer; }// 表明是可点击的
		.packed { text-decoration:line-through; }// 当item应用此style时是删除线效果
	</style>
	... etc ...

Dart除了作为一种标准的、单继承的、多接口的、基于类的语言之外,还添加了一些不错的特性。

你可能已经注意到,这些特性之一是类不是强制性的(与C#和Java不同)。函数可以不被包装在类中而存在;main()和addItem()函数存在于顶级作用域中,而不是类的一部分。与C#和Java不同的是,您可以将任意多个类放入一个Dart文件中——没有任何限制,但您可以将代码拆分为单独的文件,以保持源代码的组织,这一主题将在第5章中介绍。

除此之外,Dart中的类具有构造函数、方法和属性,这些构造函数、方法和属性可以是公共的,也可以是私有的,并且它们具有用于getter和setter的特殊语法。我们将首先处理构造函数,它将允许您创建类。

构造PackItem类:

class PackItem {
	var itemText;// 用户输入的内容
	var uiElement;// 将要添加的UI元素
	PackItem(this.itemText) {}
}

注:更好的设计应该有两个类:一个负责UI布局,另一个负责保存和操作数据。然后,您可以将一个应用程序松散地耦合到另一个应用程序。

PackItem(this.itemText) {}
以上this关键字不可省,因为它等效于:
PackItem(itemText) {
	this.itemText = itemText;
}
扩展:
PackItem(this.itemText, color, quantity) {
	this.color = color;
	this.quantity = quantity;
}

现在重构addItem()函数如下:

addItem() {
	var itemInputList = querySelectorAll("input");
	InputElement itemInput = itemInputList[0];
	DivElement itemContainer = querySelector("#items");
	var packItem = new PackItem(itemInput.value);
	itemContainer.children.add(packItem.uiElement);// packItem.uiElement为null,严重的问题!
	itemInput.value = "";
}

使用getter和setter:
getter(或setter)是以get(或set)关键字为前缀的方法。getter不能接受任何参数,setter必须接受单个参数。

class PackItem {
	var _uiElement;// uiElement属性重命名为_uiElement,使它为private的,对外通过getter/setter公开
	DivElement get uiElement => _uiElement以上是关于★Dart-3-构建和测试Dart app的主要内容,如果未能解决你的问题,请参考以下文章

Dart:3.Dart运算符流程控制

Flutter 的下一步, Dart 3 重大变更即将在 2023 到来

在 Dart 中运行所有单元测试

flutter+dart仿微信App界面聊天实例

Flutter跨平台框架(Dart语言)

不用掉一根头发!用 Flutter + Dart 快速构建一款绝美移动 App