「大众点评点餐」小程序开发经验 02:视图
文 | 何延希
何延希,美团点评工程师,4 年 web 开发经验,现在是美团点评点餐团队的一员。
在 上一期 ,知晓程序(微信号 zxcx0101)与大家分享了「大众点评点餐」小程序团队带来的小程序技术分析。
本期,我们想要和大家分享下大众点评点餐小程序中,有关 View 视图层的一些开发经验。
本文部分示例来自于「大众点评点餐」小程序的菜单页面。
页面代码结构为:
menu
├── menu.html
├── menu.js
├── menu.json
└── menu.less
我们将要说的小程序的 View 视图层,是由 WXML(
menu.html
)与 WXSS(
menu.less
)两大部分组成,由组件——也就是视图的最小单元——进行展示。
视图层将逻辑层的数据(
menu.js
和
menu.json
)反应为视图,同时将视图层中定义的事件发送给逻辑层。
WXML
WXML(WeiXin Markup Language)与 HTML 对应,用于描述页面的结构
,可以类比 React 的 JSX。项目中
menu.html
使用 WXML 语法,一个页面的顶层是
page
节点。
在 WXML 中获取逻辑层定义的数据后,我们通过一系列自己的语法和逻辑展示出这些数据。
结构上,组件是视图层的最小单元。我们可以通过以下方式,进行动态渲染。
1. 数据绑定
数据绑定是最简单的使用数据方式。 采用 Mustache 语法的变量替换,用双大括号将变量名包起来,包括组件的属性也可以使用变量。
<view
class
=
"dish-item"
data-id=
"{{dishId}}"
>
<
text
class
=
"name"
>
{{dishName}}
</
text
>
</
view
>
小程序还支持 ES 6 规范的扩展运算符
...
和解构赋值。
<template is=
"dishItem"
data=
"{{...item, count, soldout: true }}"
>
</
template
>
2. 逻辑运算
双大括号中,可进行一些简单运算操作 ,包括四则运算、三目运算、逻辑判断、字符串拼接等。
<text
class
=
"{{orderBanner.type !== 0 ? 'order-banner arrow' : 'order-banner'}}"
>
{{orderBanner.text}}
<
/text>
3. 条件渲染
与通常将渲染内容写在
if
或
else
判断条件中不同,
小程序的条件渲染,要求将条件直接写在相应组件的
wx:if
与
wx:else
属性中。
如果渲染组件为多个,可将多个组件放在组件内,渲染条件置于
<block>
组件的
wx:if
与
wx:else
属性中。
此时的组件,只充当容器作用,页面中不会渲染。
我们来看条件渲染实际应用的例子:
<text wx:if="{{item.soldOut}}" class="status-soldout">已售完</text>
<template wx:else is="numberCount" data="{{count: cartSpuCount[item.spuId]}}"></template>
用上
<block>
的实例:
<block wx:if="{{serverError}}">
<text>点小评去吃满汉全席啦~</text>
<button class="menu-btn" bindtap="requestMenu">重试</button>
</block>
4. 列表渲染
列表渲染,是将元素进行遍历,并利用
wx:for
属性值进行循环渲染。
与此相关的还有以下几个属性:
-
wx:key
:遍历元素的唯一的标识符,主要用于数据动态变化时,DOM 的更新机制。数据不变,则可无视。 -
wx:for-item
:遍历元素的变量名,默认值为item
-
wx:for-index
:遍历元素下标的变量名,默认index
以上属性的值都可以用字符串,但值中不要使用
-
等符号。
例如
dish-item
,在使用时,小程序会将
{{dish-item}}
中的
-
解析成减号,造成取值失败。
在这里,我们利用测试数据举个例子:
<block wx:
for
=
"{{testData}}"
wx:
for
-item=
"mainitem"
wx:key=
"{{mainindex}}"
wx:
for
-index=
"mainindex"
>
<
view
wx:for
=
"{{mainitem}}"
wx:for-item
=
"subitem"
wx:key
=
"{{subitem.id}}"
wx:for-index
=
"subindex"
>
<
view
class
=
"dom-item"
>
第一层 index: {{mainindex}}
id: {{subitem.id}}
name: {{subitem.name}}
</
view
>
</
view
>
<
/block>
以上代码结构上分为两层:
-
第一层
block
循环遍历testData
数组,每个遍历值变量名为mainitem
。 -
第二层
view
循环遍历mainitem
数组,每个遍历值变量名为subitem
,展示第一层index
、第二层id
和name
属性;
// 创建页面实例对象
Page({
/**
* 页面的初始数据
*/
data: {
"testData"
: [
[ {
"id"
:
"1-1"
,
"name"
:
"节点1 - 1"
}, {
"id"
:
"1-2"
,
"name"
:
"节点1 - 2"
}], [{
"id"
:
"2-1"
,
"name"
:
"节点2 - 1"
}, {
"id"
:
"2-2"
,
"name"
:
"节点2 - 2"
}]
]
}
})
展示结果:
循环遍历时,除官方说明的数组类型可以循环遍历外,
对象类型也可通过
wx:for
进行属性遍历
。此时
for-index
为属性的 key 值。
例如,在上面例子中,将
testData
换成对象类型:
// 创建页面实例对象
Page({
/**
* 页面的初始数据
*/
data: {
"testData"
: {
"a"
: [{
"id"
:
"1-1"
,
"name"
:
"节点1 - 1"
}, {
"id"
:
"1-2"
,
"name"
:
"节点1 - 2"
}],
"b"
: [{
"id"
:
"2-1"
,
"name"
:
"节点2 - 1"
}, {
"id"
:
"2-2"
,
"name"
:
"节点2 - 2"
}]
}
}
})
结果为:
5. 模板 & 引用
小程序中的模板,概念类似于 React 中的组件(components)。
我们可以在模板中定义代码片段,然后在不同的地方进行调用 ,减少重复的代码量。
如何定义一个模板呢?
我们使用
name
属性,作为模板的名字,然后在
<template/>
内定义模板代码片段就可以了
。
定义后的使用方式有 2 种:
-
使用
include
方式 ,将目标文件除了<template/>
部分外的整个代码引入。这样的操作,相当于是将整个文件里的代码拷贝到include
位置,所以无法传入参数。 -
使用
import
方式 ,引入定义的文件,然后通过<template/>
组件的is
属性,声明需要的使用的模板,然后将模板所需要的data
传入。这样的模板拥有自己的作用域,只能使用data
传入的数据。
需要注意的几个地方:
-
小程序只会
import
目标文件中定义的<template/>
,不能引用目标文件中引用的<template/>
。 - 小程序的模板中,只能单向使用传入的数据,不像 React 可以利用 props 让父子组件进行传值。
我们以单个菜品组件为例,看看如何在小程序中使用模板:
<
import
src=
"../../components/common/dish-item.wxml"
/>
<
template
is
=
"dishItem"
data
=
"{{...item}}"
>
</
template
>
6. 绑定事件
事件名称为字符串,会默认传入
event
参数,无法定制其他参数。
我们一般将所需参数通过
data-
属性,绑定至组件,再通过
e.currentTarget.dataset
获取。例如这样:
<view
class
=
"cart-btn"
data-type=
"1"
bindtap=
"redirectCart"
>选好了<
/view>
WXSS
WXSS(WeiXin Style Sheet)与 CSS 对应,用于描述页面的样式。
定义在
app.less
中的样式为全局样式
,可作用于每一个页面。
在页面里的样式文件中定义的样式为局部样式
,只作用在对应的页面,并会覆盖
app.less
中相同的选择器。
例如,代码结构中
menu.less
能且只能作用于
menu.html
。
1. 支持的特性
WXSS 支持内联样式和选择器两种特性。
小程序组件的
style
可以接收动态的样式,会在运行时会进行解析。但请尽量避免将静态的样式写进
style
中,以免影响渲染速度。
WXSS 支持选择器。对于常用的选择器,小程序目前支持以下这些:
目前不支持的选择器有:
此外,还有几个需要注意的地方:
-
如之前提到,页面的顶层是节点,所以想要修改作用于整个页面的样式、顶层节点样式,请使用
page
选择器。 - 小程序目前不支持 Media Query。
2. 扩展的特性
在 CSS 的基础上,WXSS 还扩展了几个特性。
首先是尺寸单位 RPX。这是小程序自创的单位,可以根据屏幕宽度进行自适应。
RPX 将所有手机的屏幕宽度规定为 750rpx。例如,在 屏幕宽度为 375 px 的 iPhone 6 上,换算出来 1 rpx = 0.5 px = 1 物理像素。
我们建议设计师在开发微信小程序时,可以用 iPhone 6 作为视觉稿的标准。
另外,由于数值较小时渲染时会存在四舍五入的情况,在较小屏幕上差距会很大,所以要求精确而较小的视图内容需避免使用此单位。
例如,下图所示菜品的减号操作图标的高度,iPhone 6 下是 2 px,iPhone 4s 下直接渲染成了1 px(实际比例值为 1.7 px)。
而加号按钮图标高度,在 iPhone 6 下是 11 px,iPhone 4s 下,就渲染成了 9 px(实际比例值为 9.48 px)。
这样的差距,就会让小程序在两台手机上,看起来不那么协调了。
关注微信号 zxcx0101,在后台回复「 rpx 」,一篇文章带你看懂 RPX。
此外,
在小程序中使用
@import
语句,可以导入外联样式表
。
具体的使用方式是:在
@import
后,写上需要导入的外联样式表的相对路径,用
;
符号表示语句结束。
组件
如前面 WXML 部分中所述, 组件是视图层的基本组成单元 。
它与 HTML 中的标签类似,基于 Web Component 标准,属性和内容的使用方法也和 HTML 标签类似。
组件名称和属性名称,都必须使用小写。
1. 组件列表
2. 原生组件
如上统计,
input
、
textarea
、
video
、
map
、
canvas
均为系统原生组件。
原生组件相对来说性能和用户交互方面会有所提升。
以部分机型
input
元素
fixed
时唤起键盘被遮挡的问题举例,在某魅族机型上 HTML 5 页面中,父元素
fixed
的输入框会被遮挡:
在同一机型中,小程序里的输入框就不会被遮挡。
3. 组件属性
小程序的组件中,支持以下的数据类型:
-
Boolean
:布尔值 -
Number
:数字 -
String
:字符 -
Array
:数组 -
Object
:对象 -
EventHandler
:事件处理函数名,事件绑定属性(如bindtap
) -
Any
:任意属性(不是很明白是什么意思)
所有组件都有的共同属性:
-
id
:组件的唯一标识 -
class
:组件的样式类,和在 WXSS 中定义的类选择器对应 -
style
:内联样式 -
hidden
:组件隐藏或显示 -
data-*
:自定义属性,可传入自定义数据。逻辑层事件处理函数中,可通过e.currentTarget.dataset
获取。 -
bind
和catch
:都是事件绑定,差别在于:bind
不会阻止事件向上冒泡,catch
可以阻止事件向上冒泡。
此外,各个组件都有自定义的特殊属性,如
<icon>
组件的
size
属性。你可以在官方文档中查阅每个组件的不同属性。
兼容性
根据官方文档的说明:
- 在 iOS 上,小程序的 JavaScript 代码是运行在JavaScriptCore 中,是由 WKWebView 进行渲染,可用环境有 iOS 8、iOS 9、iOS 10。
- 在 Android 上,小程序的 JavaScript 代码通过 X5 JSCore 解析,由 X5 基于Mobile Chrome 37 内核进行渲染;
- 在开发工具上,小程序的 JavaScript 代码运行在 NW.js,由 Chrome WebView 进行渲染。
由于内核渲染表现不一致,在开发过程中,存在于 X5 浏览器和各类机型或系统的兼容性问题,一部分会在小程序中存在。
性能优化
前端常用的模板方案一般有 2 种:
- 将模板编译成 JS 函数代码,通过字符串拼接的方式生成渲染的 DOM 节点。 例如:Mustache / tpl(点评内部开发使用),数据更改时,会将 DOM 节点全部更新。
- 字符串 parse 和 compile 后拼接渲染外,有自己的 DOM 节点更新机制。 例如:Vue.js / React等,数据更改时通过 DOM Diff 算法更新 DOM 节点。
当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件。 框架会确保他们被重新排序,而不是重新创建。
这样做,我们可以确保组件保持自身的状态,并且提高列表渲染时的效率。
小程序对组件的渲染方式我们不得而知,只能对开发中碰到的一些问题来推测。
结合小程序对列表渲染 wx:key 的解释,可知 小程序的模板渲染属于第二种,数据更新时会根据 key 进行渲染优化 。
但小程序官方未提供相关接口或性能调试工具,所以项目中我们只能自己尝试不同方案然后对比渲染速度。
以菜单页面为例,商户菜品数量多者成百上千,优化后的效果对比还是比较明显。
由以上的描述,我们可以得出以下的优化建议:
- 在菜单页面,将菜品数据扁平化为一层,并合理利用 key 值。
- 设计组件结构时采用精简的组件结构,减少渲染时的数据遍历和组件嵌套深度带来的性能消耗。
- 将数据变动的组件与数据不变的组件进行拆分,减少数据更改带来的组件更新量,如将加减按钮和菜品信息分离。
- 使用动态加载等方式减小首屏渲染数据量,提升用户体验。
原文地址: https://juejin.im/post/58affbff2f301e006cfc5b46
本文由知晓程序授权转载,关注微信号 zxcx0101, 在微信后台回复「 点餐 」,获取「大众点评点餐」小程序全套开发经验。