问题描述
在浏览器端开发时,经常会有这种情况:单击某个元素,让其高亮显示,例如下图
点击添加class
这个用 js 很容易实现, 因为单击事件触发时,函数传入的 event 事件包含 target 对象,里面会有触发事件的 DOM 元素,我们只需要操作这个 DOM 元素,为其添加 class 名就好了。
而在微信小程序开发时,由于其类似于 vue 不建议直接操作 DOM (两者都有API可以做到),事件触发的时候同样会有默认参数传入, 但是无法直接取到 DOM 节点本身,而是包含挂载的一些数据,和点击部位的坐标信息等,具体参阅官方文档《事件·小程序》 ,this
也总是指向 Page ,所以我们就需要通过数据间接操纵 DOM来实现。
例如我遇到的问题是,我想做一个月历,当你选中某一天的时候,那一天高亮显示。
参考解决方案
由于自己在做这一块时遇到了很多这方面的困惑,所以我在网上看了一些解决方案,下面列举一种在 CSDN 上看到的方案:
- wxml
<view class="list-wrapper">
<view class="list-top">
<view data-num="1" class="list-menu list-menu1 {{_num==1?'cur':''}}" bindtap="menuClick">头条</view>
<view data-num="2" class="list-menu list-menu2 {{_num==2?'cur':''}}" bindtap="menuClick">活动</view>
<view data-num="3" class="list-menu list-menu3 {{_num==3?'cur':''}}" bindtap="menuClick">公告</view>
</view>
</view>
- js
menuClick:function(e){
this.setData({
_num:e.target.dataset.num
})
},
这个方法绑定了一个动态的 class 名,用一个变量 _num
可以做到切换 class 的作用,当点击元素时,js 获取到节点上 data-num
上的值,这里将值赋给变量 _num
,相应的由于是数据驱动,节点上的 class 名经过计算变化为 cur
,其他的同理。
原始解决方案
在没有搞清这个方法前,我制作月历是使用的条件渲染。具体做法是,每个日期节点准备两个 DOM 元素,一个带有 class="selected"
,一个没有, 经过列表渲染之后每个单位实际上存在两个逻辑上的元素,这个时候通过点击改变 Page
中 data
里面的 selectedDate
和 selectedDate
,进一步控制 wx:if
的条件来实现元素的渲染与否。
<!-- 列表渲染 -->
<view wx:for="{{unit.dates}}" wx:key="item.date">
<view wx:if="{{item.date == selectedDate && item.month == selectedMonth}}" class="selected" data-year="{{item.year}}" data-month="{{item.month}}" data-date="{{item.date}}">{{item.date}}</view>
<view wx:else data-year="{{item.year}}" data-month="{{item.month}}" data-date="{{item.date}}">{{item.date}}</view>
</view>
点击事件发生时,获取节点中的 data-month
和 data-date
值, 并赋给 selectedDate
和 selectedDate
由于每个月都有某些日期, 所以加个月份限制,这里我设置了只做从这个月到未来6个月的月历,所以不需要加年份限制。
深知这个方案问题很大,是这一类的MVVM框架因的条件渲染切换消耗较大,微信小程序开发文档中介绍了 wx:if 相关:
wx:if
vshidden
因为wx:if
之中的模板也可能包含数据绑定,所有当wx:if
的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。
同时wx:if
也是惰性的,如果在初始渲染条件为false
,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。
相比之下,hidden
就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。
一般来说,wx:if
有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则wx:if
较好。
当用户点击某个日期的时候会重新渲染整个 DOM ,所以这个方案并不好。
改进方案
<view class="day {{item.date > 0 ? '' : 'hidden'}}" wx:for="{{unit.dates}}" wx:key="item.date">
<view wx:if="{{item.date > 0}}">
<view class="{{item.date == selectedDate && item.month == selectedMonth ? 'selected' : ''}}" data-year="{{item.year}}" data-month="{{item.month}}" data-date="{{item.date}}">{{item.date}}</view>
</view>
</view>
搞懂前面的逻辑,再来看这个方案就会很明白了。
网友评论