大众点评点餐小程序开发经验 - 视图层

做者介绍:何延希,美团点评工程师,4年Web开发经验,如今是美团点评点餐团队的一员。javascript

接上一篇大众点评点餐小程序开发经验 - 概述,本期想要和你们分享下大众点评点餐小程序中View视图层的一些开发经验。本文部分示例来自于「大众点评点餐」小程序的菜单页面。html

页面代码结构为:前端

menu
├── menu.html
├── menu.js
├── menu.json
└── menu.lessvue

咱们将要说的小程序的View视图层是由WXML(menu.html) 与 WXSS(menu.less) 两大部分组成,由视图最小单元 - 组件来进行展现。视图层将逻辑层的数据(menu.js+menu.json)反应成视图,同时将视图层中定义的事件发送给逻辑层,一图以蔽之。java

WXML

WXML(WeiXin Markup Language)与HTML对应,用于描述页面的结构,能够类比React的JSX。项目中menu.html为WXML语法,一个页面的顶层是page节点。WXML中获取逻辑层定义的数据后,经过一系列本身的语法和逻辑展现出这些数据。结构上组件是其最小单元,经过如下方式动态渲染。react

一、数据绑定

数据绑定是最简单的使用数据方式,语法采用Mustache的变量替换,用双大括号将变量包起来,若是组件的属性则需将数据放置于引号之中。git

<view class="dish-item" data-id="{{dishId}}"><text class="name">{{dishName}}</text></view>复制代码

数据绑定还支持ES6规范的扩展运算符 “...”、解构赋值。github

<template is="dishItem" data="{{...item, count, soldout: true }}"></template>复制代码

二、逻辑运算

双大括号中可进行算数运算、三目运算、逻辑判断、字符串拼接等操做。web

<text class="{{orderBanner.type !== 0 ? 'order-banner arrow' : 'order-banner'}}">{{orderBanner.text}}</text>复制代码

三、条件渲染

与经常使用模板语言将渲染内容写在 if/else 判断条件之中不同的是,小程序的条件渲染将渲染条件直接写在渲染内容组件的 wx:if/wx:else 属性中,若是渲染组件为多个,可将多个组件放在 组件内,渲染条件置于 <block>组件wx:if/wx:else 属性中,此时的 组件只充当容器做用,页面中不会渲染。 算法

if/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>复制代码

四、列表渲染

列表渲染是将遍历元素做为渲染组件的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第二层中wx:for-item和wx:for-index失效
  • wx:for渲染异常
  • wx:for中wx:index错乱
    以上问题小程序更新版本后均已修复。

注意:

  • 一、循环遍历时,除官方说明的数组类型能够循环遍历外,对象类型也可经过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"
            }]
        }
    }
})复制代码

结果为:

  • 二、循环遍历时,小程序以前还支持wx:forin遍历,功能和wx:for类似,但官方文档中未说明,如今尝试不会报错,但功能已经失效,估计后期已经合并。

五、模板 & 引用

模板相似于React中的组件component的概念,能够在模板中定义代码片断,而后在不一样的地方调用,减小重复的代码。

一、定义:使用name属性,做为模板的名字,而后在<template/>内定义模板代码片断;
二、使用方式有2种:

  • 使用include方式,将目标文件除了<template/>的整个代码引入,至关因而拷贝到include位置,因此没法传入参数;
  • 经过import的方式引入定义的文件,而后经过<template/>组件的 is 属性,声明须要的使用的模板,而后将模板所须要的 data 传入,模板拥有本身的做用域,只能使用data传入的数据。

注意:

  • 只会 import 目标文件中定义的 <template/>,不能引用目标文件中引用的 <template/>。
  • React的父组件经过props将数据传入子组件中,传值方式为引用传值,子组件中可修改自身props影响父组件数据。小程序的模板只能单向使用传入的数据。

示例(单个菜品组件):

<import src="../../components/common/dish-item.wxml" />
<template is="dishItem" data="{{...item}}"></template>复制代码

六、绑定事件

事件名称为字符串,会默认传入event参数,没法定制其余参数,因此通常将所需参数经过data-属性绑定至组件后经过e.currentTarget.dataset获取。

<view class="cart-btn" data-type="1" bindtap="redirectCart">选好了</view>复制代码

WXSS

WXSS(WeiXin Style Sheet)与CSS对应,用于描述页面的样式。
定义在app.less中的样式为全局样式,做用于每个页面;在page的wxss文件中定义的样式为局部样式,只做用在对应的页面,并会覆盖app.less中相同的选择器,如代码结构中menu.less做用于menu.html。

一、支持的特性

  • 内联样式:组件的 style 接收动态的样式,在运行时会进行解析,请尽可能避免将静态的样式写进style中,以避免影响渲染速度。
  • 选择器
    对于经常使用的选择器,目前支持的选择器有:

注:绿色背景色行表示官方文档中没有说明,但经我的亲测后肯定也支持的选择器。

目前不支持的选择器有:


注意:

  • 如以前提到,页面的顶层是 节点,因此做用于整个页面的样式或修改顶层节点样式请使用page选择器。
  • 小程序目前不支持Media Query。

二、扩展的特性

  • 尺寸单位rpx
    rpx为小程序自创的单位,能够根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在iPhone6上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

建议:开发微信小程序时设计师能够用 iPhone6 做为视觉稿的标准。

注意: 因为数值较小时渲染时会存在四舍五入的状况,在较小屏幕上差距会很大,因此要求精确而较小的视图内容需避免使用此单位。

以下图所示菜品的减号操做图标,高度iPhone6(750)下是2px,iPhone4s(640)下直接渲染成了1px(实际比例值为1.7px),而加号按钮图标高度iPhone6(750)下是11px,iPhone4s(640)下渲染成了9px(实际比例值为9.48px),偏差比例较小没有出现明显视觉问题,因此二者看起来会不协调。

  • 样式导入
    用@import语句能够导入外联样式表,@import后跟须要导入的外联样式表的相对路径,用;表示语句结束。

组件

如上WXML中所述,组件是视图层的基本组成单元,与HMTL中标签做用相似,基于Web Component标准,属性和内容的使用方法也和HTML标签相似,组件和属性都须小写。

一、组件列表

二、原生组件

如上统计,input、textarea、video、map、canvas为系统原生组件。原生组件相对来讲性能和用户交互方面会有所提高。

以部分机型input元素fixed时唤起键盘被遮挡的问题举例,在某魅族机型上H5页面中父元素fixed的输入框会被遮挡:

同一机型小程序中,输入框不会被遮挡:

三、组件属性

支持类型

  • Boolean:布尔值
  • Number:数字
  • String:字符
  • Array:数组
  • Object:对象
  • EventHandler:事件处理函数名,事件绑定(如bindtap)属性
  • Any:任意属性(不是很明白是什么意思)

共同属性

  • id:组件的惟一标识
  • class:组件的样式类,和wxss中定义的class选择器对应
  • style:内联样式
  • hidden:组件是否显示
  • data-*:自定义属性,可传入自定义数据,逻辑层事件处理函数中经过e.currentTarget.dataset获取。
  • bind / catch:都是事件绑定,bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定能够阻止冒泡事件向上冒泡。

特殊属性
特殊属性是各个组件本身定义的属性,如 <icon> 组件的size属性,具体各参见官方文档各组件具体说明。

兼容性

渲染机制
根据官方文档的说明:

  • 在iOS 上,小程序的javascript 代码是运行在JavaScriptCore 中,是由WKWebView来渲染的,环境有iOS八、iOS九、iOS10;
  • 在Android上,小程序的javascript代码是经过X5 JSCore来解析,是由 X5 基于Mobile Chrome 37 内核来渲染的;
  • 在开发工具上,小程序的javascript代码是运行在nwjs中,是由Chrome Webview来渲染的。

因为内核渲染表现不一致,H5开发过程当中存在X5浏览器和各种机型或系统的兼容性的部分问题小程序中依旧存在。

常见问题分类

  • X5浏览器
    X5前端开发问题
    X5 Caniuse Tests
  • iOS/Android版本致使的兼容性问题:小程序在iOS/Andriod 系统中只要求微信版本 >= 6.5.3,对机型的系统版本并没有限制。
  • Android机型碎片化致使的兼容性问题

性能优化

前端经常使用的模板方案通常有2种:

  • 一、将模板编译成js函数代码,经过字符串拼接的方式生成渲染的DOM节点,如:Mustache / tpl(点评内部开发使用),数据更改时DOM节点所有更新;
  • 二、字符串parse和compile后拼接渲染外,有本身的DOM节点更新机制, 如:Vue.js / React等,数据更改时经过DOM Diff算法更新DOM节点。

当数据改变触发渲染层从新渲染的时候,会校订带有key的组件,框架会确保他们被从新排序,而不是从新建立,以确保使组件保持自身的状态,而且提升列表渲染时的效率。

小程序对组件的渲染方式咱们不得而知,只能对开发中碰到的一些问题来推测。结合小程序对列表渲染wx:key的解释可知其模板渲染属于第二种,数据更新时会根据key进行渲染优化。但小程序官方未提供相关接口或性能调试工具,因此项目中咱们只能本身尝试不一样方案而后对比渲染速度。以菜单页面为例,商户菜品数量多者成百上千,优化后的效果对比仍是比较明显。

采起方案

  • 一、减小数据嵌套层数/减小组件嵌套层数:菜单页面将菜品数据扁平化为一层,并合理利用key值;设计组件结构时采用精简的组件结构,减小渲染时的数据遍历和组件嵌套深度带来的性能消耗。
  • 二、将数据变更的组件与数据不变的组件进行拆分,减小数据更改带来的组件更新量,如将加减按钮和菜品信息分离。
  • 三、使用动态加载等方式减少首屏渲染数据量,提高用户体验。

本文时间为2017-02-24,所提小程序暂不支持属性或碰到的bug以此时间为准,后续更新或修复请查看官方文档

参考资料:
微信小程序开发者文档

相关文章
相关标签/搜索