fix(DropdownMenu): support set props dynamic & improve performance (#2454)

fix #2446
This commit is contained in:
rex 2019-12-04 16:54:53 +08:00 committed by GitHub
parent baac222b2f
commit 52e21213ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 159 additions and 136 deletions

View File

@ -9,6 +9,7 @@ VantComponent({
type: 'ancestor',
linked(target) {
this.parent = target;
this.updateDataFromParent();
},
unlinked() {
this.parent = null;
@ -16,13 +17,23 @@ VantComponent({
},
props: {
value: null,
title: String,
value: {
type: null,
observer: 'rerender'
},
title: {
type: String,
observer: 'rerender'
},
disabled: Boolean,
titleClass: String,
titleClass: {
type: String,
observer: 'rerender'
},
options: {
type: Array,
value: []
value: [],
observer: 'rerender'
}
},
@ -33,21 +44,30 @@ VantComponent({
displayTitle: ''
},
created() {
this.setData({ displayTitle: this.computedDisplayTitle(this.data.value) });
},
methods: {
computedDisplayTitle(curValue) {
const { title, options } = this.data;
rerender() {
wx.nextTick(() => {
this.parent && this.parent.updateItemListData();
});
},
if (title) {
return title;
updateDataFromParent() {
if (this.parent) {
const {
overlay,
duration,
activeColor,
closeOnClickOverlay,
direction
} = this.parent.data;
this.setData({
overlay,
duration,
activeColor,
closeOnClickOverlay,
direction
});
}
const match = options.filter(option => option.value === curValue);
const displayTitle = match.length ? match[0].text : '';
return displayTitle;
},
onClickOverlay() {
@ -56,30 +76,55 @@ VantComponent({
},
onOptionTap(event: Weapp.Event) {
let { value, displayTitle } = this.data;
const { option } = event.currentTarget.dataset;
const { value: optionValue } = option;
const { value } = option;
if (optionValue !== value) {
value = optionValue;
displayTitle = this.computedDisplayTitle(optionValue);
this.$emit('change', optionValue);
}
this.setData({ showPopup: false, value, displayTitle });
const shouldEmitChange = this.data.value !== value;
this.setData({ showPopup: false, value });
const time = this.data.duration || 0;
setTimeout(() => {
this.setData({ showWrapper: false });
}, time);
}, this.data.duration || 0);
// parent 中的 itemListData 是 children 上的数据的集合
// 数据的更新由 children 各自维护,但是模板的更新需要额外触发 parent 的 setData
this.parent.setData({ itemListData: this.parent.data.itemListData });
this.rerender();
if (shouldEmitChange) {
this.$emit('change', value);
}
},
toggle() {
const { childIndex } = this.data;
this.parent.toggleItem(childIndex);
toggle(show, options = {}) {
const { showPopup, duration } = this.data;
if (show == null) {
show = !showPopup;
}
if (show === showPopup) {
return;
}
if (!show) {
const time = options.immediate ? 0 : duration;
this.setData({ transition: !options.immediate, showPopup: show });
setTimeout(() => {
this.setData({ showWrapper: false });
}, time);
this.rerender();
return;
}
this.parent.getChildWrapperStyle().then((wrapperStyle: String = '') => {
this.setData({
transition: !options.immediate,
showPopup: show,
wrapperStyle,
showWrapper: true
});
this.rerender();
});
}
}
});

View File

@ -2,11 +2,8 @@ import { VantComponent } from '../common/component';
import { Weapp } from 'definitions/weapp';
import { addUnit } from '../common/utils';
interface ToggleOptions {
immediate?: Boolean;
}
let ARRAY: WechatMiniprogram.Component.TrivialInstance[] = [];
type TrivialInstance = WechatMiniprogram.Component.TrivialInstance;
let ARRAY: TrivialInstance[] = [];
VantComponent({
field: true,
@ -15,35 +12,26 @@ VantComponent({
name: 'dropdown-item',
type: 'descendant',
linked(target) {
this.children = this.children || [];
// 透传 props 给 dropdown-item
const { overlay, duration, activeColor, closeOnClickOverlay, direction } = this.data;
this.updateChildData(target, {
overlay,
duration,
activeColor,
closeOnClickOverlay,
direction,
childIndex: this.children.length
});
this.children.push(target);
// 收集 dorpdown-item 的 data 挂在 data 上
target &&
this.setData({
itemListData: this.data.itemListData.concat([target.data])
});
this.updateItemListData();
},
unlinked(target) {
this.children = this.children.filter((child: WechatMiniprogram.Component.TrivialInstance) => child !== target);
this.children = this.children.filter(
(child: TrivialInstance) => child !== target
);
this.updateItemListData();
}
},
props: {
activeColor: String,
activeColor: {
type: String,
observer: 'updateChildrenData'
},
overlay: {
type: Boolean,
value: true
value: true,
observer: 'updateChildrenData'
},
zIndex: {
type: Number,
@ -51,15 +39,18 @@ VantComponent({
},
duration: {
type: Number,
value: 200
value: 200,
observer: 'updateChildrenData'
},
direction: {
type: String,
value: 'down'
value: 'down',
observer: 'updateChildrenData'
},
closeOnClickOverlay: {
type: Boolean,
value: true
value: true,
observer: 'updateChildrenData'
},
closeOnClickOutside: {
type: Boolean,
@ -71,7 +62,10 @@ VantComponent({
itemListData: []
},
created() {
beforeCreate() {
const { windowHeight } = wx.getSystemInfoSync();
this.windowHeight = windowHeight;
this.children = [];
ARRAY.push(this);
},
@ -80,99 +74,67 @@ VantComponent({
},
methods: {
updateChildData(childItem: WechatMiniprogram.Component.TrivialInstance, newData, needRefreshList: Boolean = false) {
childItem.setData(newData);
if (needRefreshList) {
// dropdown-item data 更新,涉及到 title 的展示,触发模板更新
this.setData({ itemListData: this.data.itemListData });
}
},
toggleItem(active: Number) {
this.children.forEach((item: WechatMiniprogram.Component.TrivialInstance, index: Number) => {
const { showPopup } = item.data;
if (index === active) {
this.toggleChildItem(item);
} else if (showPopup) {
this.toggleChildItem(item, false, { immediate: true });
}
updateItemListData() {
this.setData({
itemListData: this.children.map((child: TrivialInstance) => child.data)
});
},
toggleChildItem(childItem: WechatMiniprogram.Component.TrivialInstance, show: boolean, options: ToggleOptions = {}) {
const { showPopup, duration } = childItem.data;
updateChildrenData() {
this.children.forEach((child: TrivialInstance) => {
child.updateDataFromParent();
});
},
if (show === undefined) show = !showPopup;
if (show === showPopup) {
return;
}
const newChildData = { transition: !options.immediate, showPopup: show };
if (!show) {
const time = options.immediate ? 0 : duration;
this.updateChildData(childItem, { ...newChildData }, true);
setTimeout(() => {
this.updateChildData(childItem, { showWrapper: false }, true);
}, time);
return;
}
this.getChildWrapperStyle().then((wrapperStyle: String = '') => {
this.updateChildData(
childItem,
{
...newChildData,
wrapperStyle,
showWrapper: true
},
true
);
toggleItem(active: number) {
this.children.forEach((item: TrivialInstance, index: number) => {
const { showPopup } = item.data;
if (index === active) {
item.toggle();
} else if (showPopup) {
item.toggle(false, { immediate: true });
}
});
},
close() {
this.children.forEach((item: WechatMiniprogram.Component.TrivialInstance) => {
this.toggleChildItem(item, false, { immediate: true });
this.children.forEach((child: TrivialInstance) => {
child.toggle(false, { immediate: true });
});
},
getChildWrapperStyle() {
const { windowHeight } = wx.getSystemInfoSync();
const { zIndex, direction } = this.data;
let offset = 0;
return this.getRect('.van-dropdown-menu').then(rect => {
const { top = 0, bottom = 0 } = rect;
if (direction === 'down') {
offset = bottom;
} else {
offset = windowHeight - top;
return this.getRect('.van-dropdown-menu').then(
(rect: WechatMiniprogram.BoundingClientRectCallbackResult) => {
const { top = 0, bottom = 0 } = rect;
const offset = direction === 'down' ? bottom : this.windowHeight - top;
let wrapperStyle = `z-index: ${zIndex};`;
if (direction === 'down') {
wrapperStyle += `top: ${addUnit(offset)};`;
} else {
wrapperStyle += `bottom: ${addUnit(offset)};`;
}
return wrapperStyle;
}
let wrapperStyle = `z-index: ${zIndex};`;
if (direction === 'down') {
wrapperStyle += `top: ${addUnit(offset)};`;
} else {
wrapperStyle += `bottom: ${addUnit(offset)};`;
}
return Promise.resolve(wrapperStyle);
});
);
},
onTitleTap(event: Weapp.Event) {
// item ---> dropdown-item
const { item, index } = event.currentTarget.dataset;
const { index } = event.currentTarget.dataset;
const child = this.children[index];
if (!item.disabled) {
// menuItem ---> dropdown-menu
if (!child.data.disabled) {
ARRAY.forEach(menuItem => {
if (menuItem && menuItem.data.closeOnClickOutside && menuItem !== this) {
if (
menuItem &&
menuItem.data.closeOnClickOutside &&
menuItem !== this
) {
menuItem.close();
}
});

View File

@ -1,10 +1,10 @@
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />
<view class="van-dropdown-menu van-dropdown-menu--top-bottom">
<view
wx:for="{{ itemListData }}"
wx:key="index"
data-item="{{ item }}"
data-index="{{ index }}"
class="{{ utils.bem('dropdown-menu__item', { disabled: item.disabled }) }}"
bind:tap="onTitleTap"
@ -14,7 +14,7 @@
style="{{ item.showPopup ? 'color:' + activeColor : '' }}"
>
<view class="van-ellipsis">
{{item.displayTitle}}
{{ computed.displayTitle(item) }}
</view>
</view>
</view>

View File

@ -0,0 +1,16 @@
/* eslint-disable */
function displayTitle(item) {
if (item.title) {
return item.title;
}
var match = item.options.filter(function(option) {
return option.value === item.value;
});
var displayTitle = match.length ? match[0].text : '';
return displayTitle;
}
module.exports = {
displayTitle: displayTitle
};