VUE的某些常见问题及解决方法
双向绑定数据,界面未渲染?
最近做vue项目的过程中,经常碰到会有双向绑定数据,data中定义的数据进行变动时,页面实际展示层未改变。常见的应用场景有下面几种:
- form表单里面,修改表单的时候,分明预置了数据,但是没有显示
- element-ui常用的select组件,下拉选择了一个元素,但是选中框中并没有显示
- 自己写选中切换的组件,点击后触发active的样式,触发的字段为checked,但是checked分明自己已经改变了值,样式就是没有改变
上诉几种情况,实际原因都是vue的双向绑定机制,对于对象的定义,对应的字段必须预先定义,否则无法触发vue的监听机制。由于我之前使用angular框架,自然养成了习惯就是对表单数据保存,会直接用formData:{}
但是这在vue中是行不通的。
如果表单中有例如名称、电话号码两个字段,必须声明formData:{name:'',tel:''}
这才是vue中的data正常的声明定义方式。所以上诉问题点1、2都是该问题造成的。
上诉情况三,我们一般进行代码书写方式会如下:
// 获取预定义数据
let arr = [{id:1,name:'选项一'},{id:2,name:'选项二'}];
// 默认选中第一个
arr[0].checked = true;
// 选中项的函数
clickItem(data){
// 取消其他所有项的选中状态,选中当前项
arr.forEach(item=>{
item.checked = false;
if(data.id === item.id){
item.checked = true;
}
})
}
上述方式就会造成,进行点击选中项切换时,由于arr里面的不是每一项都有checked字段,造成数据变动时,界面渲染未改变的情况。处理方法也很简单,对arr数组数据进行处理,保证初始化时都有checked字段就行了。
所以,一定不要偷懒,遵照官方规范进行开发,哈哈~~~
使用element-ui的弹窗组件,第三方地图、图表没有正常显示
实际项目中,很多时候会用到在弹窗里面实现一些操作dom的功能,比如常见的弹窗里面进行地图选点,或者在弹窗中进行echarts的图表渲染。如果你使用element-ui的情况下,你会发现为什么第一次打开弹窗的时候想要的地图或者图表并没有渲染出来?
实际你看了element的源码就知道了,它对el-dialog弹窗组件进行封装的时候,组件模板的这一行代码<div class="el-dialog__body" v-if="rendered"><slot></slot></div>
弹窗内容渲染条件用的是v-if,v-if和v-show的区别大家应该都了解,就不赘述了(实在不知道百度~~~),v-if为false的时候,dom并不会实际存在,当你使用document.getElementById()去查找弹窗体的用于展示地图、图表的dom元素也是获取不到的。
那么问题找到了,怎么解决?自己写要保持样式一致,浪费开发效率,最简单的方法就是将element-ui源码的dialog组件代码,在自己的项目中写一个自定义组件,将v-if改成v-show就行了。下面是我自己修改的代码:
<template>
<transition name="dialog-fade">
<div class="el-dialog__wrapper" v-show="visible" @click.self="handleWrapperClick">
<div
class="el-dialog"
:class="[{ 'is-fullscreen': fullscreen, 'el-dialog--center': center }, customClass]"
ref="dialog"
:style="style">
<div class="el-dialog__header">
<slot name="title">
<span class="el-dialog__title">{{ title }}</span>
</slot>
<button
type="button"
class="el-dialog__headerbtn"
aria-label="Close"
v-if="showClose"
@click="handleClose">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" v-show="rendered"><slot></slot></div>
<div class="el-dialog__footer" v-show="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</transition>
</template>
<script>
import Popup from 'element-ui/src/utils/popup';
import Migrating from 'element-ui/src/mixins/migrating';
import emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'SelfDialog',
mixins: [Popup, emitter, Migrating],
props: {
title: {
type: String,
default: ''
},
modal: {
type: Boolean,
default: true
},
modalAppendToBody: {
type: Boolean,
default: true
},
appendToBody: {
type: Boolean,
default: false
},
lockScroll: {
type: Boolean,
default: true
},
closeOnClickModal: {
type: Boolean,
default: true
},
closeOnPressEscape: {
type: Boolean,
default: true
},
showClose: {
type: Boolean,
default: true
},
width: String,
fullscreen: Boolean,
customClass: {
type: String,
default: ''
},
top: {
type: String,
default: '15vh'
},
beforeClose: Function,
center: {
type: Boolean,
default: false
}
},
data() {
return {
closed: false
};
},
watch: {
visible(val) {
if (val) {
this.closed = false;
this.$emit('open');
this.$el.addEventListener('scroll', this.updatePopper);
this.$nextTick(() => {
this.$refs.dialog.scrollTop = 0;
});
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
} else {
this.$el.removeEventListener('scroll', this.updatePopper);
if (!this.closed) this.$emit('close');
}
}
},
computed: {
style() {
let style = {};
if (this.width) {
style.width = this.width;
}
if (!this.fullscreen) {
style.marginTop = this.top;
}
return style;
}
},
methods: {
getMigratingConfig() {
return {
props: {
'size': 'size is removed.'
}
};
},
handleWrapperClick() {
if (!this.closeOnClickModal) return;
this.handleClose();
},
handleClose() {
if (typeof this.beforeClose === 'function') {
this.beforeClose(this.hide);
} else {
this.hide();
}
},
hide(cancel) {
if (cancel !== false) {
this.$emit('update:visible', false);
this.$emit('close');
this.closed = true;
}
},
updatePopper() {
this.broadcast('ElSelectDropdown', 'updatePopper');
this.broadcast('ElDropdownMenu', 'updatePopper');
}
},
mounted() {
if (this.visible) {
this.rendered = true;
this.open();
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
}
},
destroyed() {
// if appendToBody is true, remove DOM node after destroy
if (this.appendToBody && this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
}
};
</script>
把组件名自定义,我改的是SelfDialog,是不是很简单?