第二章 Opa基础
在第一章,你写下了你的第一个Opa应用程序,该程序总是返回一个固定的Hello, world字符串来作为页面标题,如下:
<h1>Hello, world</h1>
这个值我们称之为HTML片段,这是Opa的原生类型之一。第一章中(使用“脚手架”创建)的名为myapp的那个应用也包含这样的HTML值,可参见src/view/page.opa文件:
content =
<div class="hero-unit">
Page content goes here...
</div>
此处的HTML值被定义成名为content的变量以便后续可以复用。
Opa也提供一个通用的关闭标签</>,你可以用它来关闭任何一个标签,比如上例你可以像下面这么写:
content =
<div class="hero-unit">
Page content goes here...
</>
现在让我们去探索Opa的值类型吧。
原生值
正如大部分编程语言那样Opa也支持strings, integers, and floats,同时,Opa也支持本地web值类型如HTML和CSS元素。正如你所见Opa注释以双斜线//开头:
"hi" // 字符串
12 // 整数
3.14159 // 单精度浮点数
<p>Paragraph</p> // HTML片段
#id // DOM标志
css {color: black} // CSS属性
HTML值就是HTML5片段。上例中的HTML5片段以<p>开始以</p>结束。<p> 标签用来定义一个文字段落。很快我们会提供一份详尽的标签列表,此处先列出常用标签:
Tag Definition
p Paragraph
h1 Level 1 title (header)
h2..6 Level 2 to 6 title (header)
div Generic container
span Inline generic container
ul List of unnumbered items
li List item
a Link
video Video item
下面是一个在Opa中嵌入HTML片段的例子:
<div>
Content <span>button</span>
<ul>
<li>First item</li>
<li>Second item</li>
</ul>
</div>
关闭便签时要谨慎对待:原则上最先打开的标签要最后关闭,但个别标签不用被关闭,如<meta>,<img>,<input>等。
标签可以拥有属性,比如超链接标签a可以有个href属性用来指向实际的(网络)地址。举个实例,为了插入一个链接到http://google.com(译者叹:唉~~),你可以这么写:
<a href="http://google.com">Google</a>
众多的HTML片段组合在一起就构成一份HTML文档。在第五章我们会详解一份HTML文档中最重要的相关内容,而现在,我们只需记住,标签可以通过id属性来给自己标记一个独一无二的ID。
比如,<div id="iamunique">...</div>为div标签创建了一个值为iamunique的ID,此后我们可以通过#iamunique这种DOM标志符来访问该div标签。
Opa中所有的值都可以被命名(译者注:即变量化)以便后续使用,下例演示了基本值的用法:
customer = "John Doe"
price = 12.9
tax = price * 0.16
total = price + tax
下面的例子演示了如何在前面的 Hello, World应用程序中使用基本类型的用法:
Server.start(Server.http,
{ title: "Hello, world",
page: function() {
customer = "John Doe";
price = 12.99;
tax = price * 0.16;
total = price + tax;
<p>Customer {customer} has to pay {total}</p>
}
}
)
关于上面的代码示例有如下几点需要注意:
-
传统意义上,每行行末以一个分号;结尾。这事儿(以分号结尾)好还是不好业界一直争论不休。在Opa中,你可以选择加上分号,也可以选择不加。
-
最后一行计算代码(<p>标签那个)使用一种称之为“字符阐述”的机制来将customer和total代表的值插入到文本中。正如你所见,Opa中"字符阐述"机制的使用非常简单——将变量名用大括号括起来即可。
动态内容
截止现在,你已经学习了如何制作静态网页内容。而所谓静态,如,每次你在浏览器输入http://localhost:8080并按下回车键显示的页面内容是不变的。
最初的互联网就是这样产生的,虽然那时的机制跟现在有所不同。那时,开发人员将页面内容写到一个文件中,通过静态服务器程序(如Apache)将文件发送到用户的浏览器。页面使用<a>标签创建链接以在页面之间或站点之间跳转。
但现在我们创建的web应用程序应当可以完成如下功能:
-
能够响应用户动作
-
能够将用户数据(如用户账号信息) 永久性地存储到数据库中
最基本的用户动作莫过于鼠标点击事件了,现代web应用通常不会用链接标签响应鼠标点击事件了,取而代之我们在绝大部分HTML标签中使用onclick属性来干这事儿。
下面,让我们创建一个小型的应用,该应用允许用户点击“Click me”按钮时显示“Thank you”文字:
Server.start(Server.http,
{ title: "Hello, world",
page: function() {
<div οnclick={function(_) { #thankyou = "Thank you" }}>Click me</div>
<div id="thankyou"/>
}
}
)
运行之,你将看到如下页面显示。当然,由于地址没变,我们都知道可以通过在浏览器中进行刷新可以重新显示该页面。
本例中最重要的代码为追加了如下行:
<div οnclick={function(_) { #thankyou = "Thank you" }}>Click me</div>
该行将下面两者结合起来:
-
HTML值<div οnclick={...}>Click me</div>
-
响应动作function(_) { #thankyou = "Thank you"}
此刻我们不会解释每个代码的细节(本章末会详尽阐述),重要的是理解下面两点:
-
通过大括号你将Opa代码与onclick事件结合起来
-
你知道了#thankyou是一个DOM标志符而你可以给它赋值
在继续探索之前你需要先理解Opa中2个重要机制:
-
档案,用来组织数据让数据变得有条理
-
函数,用来组织代码让代码变得有条理
很明显这2种机制你都在使用!下面的title和page都是档案的组成部分:
{ title: ..., page: ... }
至于函数,本例中用了2次:
-
页面默认显示时一次
-
鼠标点击“Click me”按钮时一次
档案
Opa在数据组织方面一个嗷嗷牛叉的特性就是“档案化数据”。就像你看到的本书中到处充斥着档案数据,后续你将会更多地使用这种强大的数据组织方式来组织你的数据。
档案其实是字段的集合,每一组字段由字段名和字段值组成,其中字段值可以为任何Opa的合法类型,甚至是一些自定义的复杂类型:
// 下面是一个持有3个字段的档案
{ first_name: "John", last_name: "Doe", age: 18 }
// 下面的档案有2个字段,其中一个字段的值类型也是档案
{ product: "book", price: { base: 29.99, tax: 4.80 } }
毫无疑问,将Opa值变量化是好习惯,以后基于该变量可以一步步组织更加复杂的数据结构:
level1_pricing = { base: 29.99, tax: 4.80 }
book = { product: "book", price: level1_pricing }
就像我们之前提到的,所有的程序(包括最开始的那个)都用到了档案:
{ title: "Hello, world",
page: function() { <h1>Hello, world</h1> }
}
上记代码使用了一个包含两个字段的档案:title是字符串类型,page类型有些小复杂我们会在后续讨论。
Opa中档案可以被继承,也就是说,档案定义后可以动态添加字段(译者注:经试验不可,可能跟Opa版本有关):
level1_pricing = { level1_pricing with total: 29.99 + 4.80}
类型及更多档案相关介绍
现在,你知道了基本类型和档案类型,是时候学习更多类型的时候了。
作为一名开发人员,你知道计算机中普遍存在的了字符串和整数,你知道它们代表不同的类型和值,在这一点上Opa也是一样的。
"Hey" // 字符串类型
10 // 整数类型
你可以在写值时强迫定义类型,但在绝大多数情况下,Opa会自动识别值的类型,也就是说,为值指定类型在Opa中不是必须的(译者注:Java则相反,必须强制定义值类型):
string text = "Hey"
Opa使用类型信息来做出一些诊断,如,当你将不同类型混合使用时Opa会认为这是一个错误或至少是不好的做法。试着写成:
1 + "Hello"
来看看Opa会对你说什么。
通常来说,开发语言的错误提示信息并不是那么一目了然,但Opa在这方面做得特别好。如果你写成
a = 1;
b = "Hello";
a + b
(编译时)Opa会告诉你有错误发生及为什么该错误会发生,如下:
Error: File "typeerror.opa", line 3, characters 1-5, (3:1-3:5 | 21-25)
Type Conflict
(1:5-1:5) int
(2:5-2:11) string
The types of the first argument and the second argument
of function + of stdlib.core should be the same
喏,上面的错误信息说了
-
1的类型是整数(此属Opa推测)
-
"Hello"的类型是字符串(此也属Opa推测)
-
在计算表达式a + b时发生类型错误
-
函数 + 的两个参数应该具有相同的类型
你也可以自定义类型,如下:
type mytype = string
mytype text = "Hey"
自定义类型在档案中非常有用,与Opa中其它值一样每个档案都有自己对应的类型(即使你没有显式地定义)。比如:
{ title: "The Firm",
author: { first_name: "John", last_name: "Grisham" } }
在Opa内部可看做有这样的数据结构:
type book = { string title,
{string first_name, string last_name} author }
book是一个档案,该档案具有2个字段:title和author。字段 title的类型为字符串,字段author是一个具有2个字符串类型字段first_name和last_name的档案类型。
因此,你可以这么写代码:
book some_book = { title: "The Firm",
author: {first_name: "John", last_name: "Grisham"} }
有时候档案字段值是一个表达式而该表达式可能很长且复杂:
author = { first_name: long_expression_to_compute_first_name,
last_name: long_expression_to_compute_last_name}
这种情况下为了提高代码的可读性可以先给这个长且复杂的表达式起个变量名以便后续使用:
author =
first_name = long_expression_to_compute_first_name
last_name = long_expression_to_compute_last_name
{first_name: first_name, last_name: last_name}
正式编码时很多时候给档案字段赋值的变量名和字段名一模一样,这时,Opa提供了一种简短的写法:
{~first_name, ~last_name}
此处的{~field, ...}跟{field: field, ...}一个效果。如果档案中所有字段名和字段值变量名都一样,甚至可以更加简化:
~{first_name, last_name}
译者注:此处一部分未翻译,原文范例或说明原理在Opa1.2.0环境下无法有效运行或被证实。
函数:构建代码块
在我们前往本章最重要范例之前,让我们先看看在编码时我们时常会遇到的代码块是怎么一回事。
有效组织代码是非常重要的。你永远不会希望写下一大段挤作一团地回过头看看怎么看怎么像一坨的代码,正相反,你希望代码一段一段错落有序这样看上去如好身材的妙龄女郎般给人以美感。大致来说有两种给代码分块的方法:
• 模块(大型应用系统中常用,后面我们会谈及)
• 函数(存在于模块中适合于任何应用系统)
通过将代码组织成函数,我们的程序可读性和代码复用性都得到了提高。函数的组织和调用都非常简单:
// the following function computes the tax of a given price
function compute_tax(price) {
price * 0.16;
}
// we now can call (or invoke) the compute_tax function as much as we want
tax1 = compute_tax(4.99);
tax2 = compute_tax(29.99);
看,function关键字加上函数名称(本例为compute_tax)加上圆括号里面的参数列表(本例只有一个参数price) 就构成函数定义部分,随后的大括号中就是函数的具体实现。Opa没有return关键字来显式地定义函数返回值,取而代之,函数中最后一次出现的表达式的值就被认为是函数的返回值。
本例中,函数实现体中只有一个表达式,所以当然也是最后一个表达式,那么函数返回值就是表达式price * 0.16 的值了。
函数调用时圆括号必须紧跟函数名(中间不能有空格)。
本例中函数的作用在于,当税率发生变化时只需维护函数体内一行代码即可,用不着翻遍整个应用代码将所有需要计算税的地方都修改一遍。很赞不是吗?所以,要尽可能地使用函数。这么说吧,每当你拷贝粘贴你的代码的时候,请想想是否可以函数化。
你也许注意到了我们在每行行末加上了分号。好吧这是故意的是为了提醒一行结束开始新行。我们说过在Opa中分号可以被忽略,而业界也未就加不加分号的优劣性达成一致,所以,到底加不加分号你自己看着办吧。
对该文任何意见,建议请直接与译者联系。
译者微信号:liu_matureshadow
译者威信公众号: