uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。
相信许多开发者都有遇到过 uni-app 中的 复选框组件(checkbox)和开关组件(switch)在改变 checked 属性会有无效的问题。本篇文章将分析问题的产生和解决方法。
先来一段示例源码
<template>
<view>
<view style="padding: 15px;">
<button size="mini" type="primary" @tap="change(true)">开</button>
<button size="mini" type="warn" @tap="change(false)">关</button>
</view>
<view>
<switch :checked="flag"></switch>
</view>
</view>
</template>
<script>
export default {
data() {
return {
flag:true
}
},
methods: {
change(flag){
this.flag = flag;
}
}
}
</script>
<style>
</style>
如上示例代码,开关组件 默认为选中状态,通过点击 开按钮 和 关按钮 来修改 flag 变量是可以正确设置 开关组件的 checked 状态的。
有些时候点击了开关组件切换了状态之后,再通过按钮修改 flag 变量时,开关组件是不会发生状态改变的,也就是出现了文章开篇所说的 checked 属性无效的问题。
那么产生该问题的原因又是什么呢?
经测试发现,当 checked 属性的值发生改变时才会重新更新视图(在vue.js中,通过检测值发生改变后,vue.js会自动修改对应的视图)。这句话有点让人费解。开关组件 的 checked 属性 是可以控制状态(开和关)的。但是点击开关组件本身。该组件是直接切换的组件状态,并没有同步更新 checked 属性 所绑定的值。所以开关组件默认 checked 属性为 true 的时候,点击组件本身会将组件的状态切换为关,但是checked 属性的并没有改变。再通过按钮设置checked 属性的为true时,因checked 属性的值 falg变量还是之前的true。vue.js认为数据并没有发生改变。所以才会出现设置checked 属性无效的问题。
介于上面的说明,对代码做如下修改,问题得到解决:
// html 代码修改如下
<switch :checked="flag" @change="changeSwitch"></switch>
// js 新增 changeSwitch方法 代码如下
changeSwitch(e){
this.flag = e.detail.value; //组件点击后获得当前组件状态更新flag变量的值
}
通过增加上面的代码,前述问题得到解决。但是该方法存在许多弊端
- 开关组件的状态经常需要 请求异步接口 后再设置状态,上述代码能解决问题,但在页面上会有来回切换的BUG;
- 这种方式无法解决 复选框组件(checkbox组件) 的问题,因为复选框组件没有 @change 事件,checkbox-group组件拥有 @change 事件,事件返回的结果是当前 复选框组中已经勾选的值。要从值重新找出点击的是那一个复选框的代码过程明显有点不合理。
为了更好的适应多种需求和方法的通用性,小编给出第二种解决方案。
该问题BUG的产生是 组件的点击事件修改状态后并未同步修改checked 属性造成的,那么放弃组件本身的点击切换功能,由自己的代码逻辑控制 checked 属性 就能从根本上解决该问题。
方案二示例代码:
<template>
<view>
<view style="padding: 15px;">
<button size="mini" type="primary" @tap="change(true)">开</button>
<button size="mini" type="warn" @tap="change(false)">关</button>
</view>
<view class="switch_box">
<switch :checked="flag"></switch>
<view class="switch_shade" @tap="changeSwitch"></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
flag:true
}
},
methods: {
change(flag){
this.flag = flag;
},
changeSwitch(){
this.flag = !this.flag;
}
}
}
</script>
<style>
.switch_box{
position: relative;
display: inline-block;
}
.switch_shade{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
}
</style>
如上示例代码,做了2件事情:
- 设置一个遮罩元素 ".switch_shade" 挡住 开关组件来阻止 开关组件 本身的 点击事件触发。
- 将原 开关组件的 @change 事件 改为 遮罩元素的 @tap 事件。
通过点击遮罩,修改 checked 属性 来达到切换组件状态的目的。
若使用 复选框组件 实现 “全选/反选”等功能遇到本篇文章所说的问题时,可以采用方案二的方式解决问题。请各位读者们自行实践。
小编有发布过相同示例的插件,完整源码获取方式:
#1 uni-app 插件市场:异步switch组件 - DCloud 插件市场
#2 gitee 代码仓库:uniapp-extend: uni-app 插件 / 模板分享
更多干货,请持续关注我的博客更新。
作者:黄河爱浪 QQ:1846492969,邮箱:helang.love@qq.com
本文原创,著作权归作者所有,转载请注明原链接及出处。