为什么要写这个?
因为在写小程序的时候首页功能比较多,造成渲染的dom有很多,一直setdata跳转到其他页面或者一直滑动就会卡顿,白屏。官方文档上那个不适用于瀑布流。官方文档
理解
刚开始在写这个的时候,就在想微信小程序的开发有虚拟dom这个概念吗,查看文档,自己实践,才大致理解一点,总结出来就是微信小程序其实并没有直接使用虚拟DOM这个概念,但提供了类似的操作DOM的方法,并且也可以根据自己的需求探索实现类似虚拟DOM的功能,所以就开始自己准备。
其实我是看了俩篇博文然后经过改造成自己的数据,然后进行理解,最后融汇贯通。我会一步一步按照自己的理解进行再代码中注释,直接复制就可以用。
第一篇
第二篇有代码片段
已附代码(如果没贴相关的代码就代表不需要写)
components->virtualItem->virtualItem.wxml
<view id="{{virtualId}}">
<block wx:if="{{isShow}}">
<slot></slot>
</block>
<view wx:else style="height: {{ height }}px"></view>
</view>
components->virtualItem->virtualItem.js
// components/virtualItem/virtualItem.js
Component({
properties: {
// 当前虚拟子模块唯一ID
virtualId: {
type: String,
value: '',
observer() {
this.getCurrentItemHeight()
}
},
// 父级滚动容器ID,如果需要以指定容器处理
wrapId: {
type: String,
value: '',
},
// 离可见区域的距离,单位px
observeDistance: {
type: Number,
value: 1500,
}
},
data: {
height: 0,
isShow: true,
},
methods: {
getCurrentItemHeight() {
const query = this.createSelectorQuery();
const { virtualId } = this.data;
query.select(`#${virtualId}`).boundingClientRect()
query.exec((res) => {
this.setData({
height: res[0].height
}, this.observePage())
})
},
observePage() {
const { virtualId, observeDistance, wrapId } = this.data
let IntersectionObserver = wx.createIntersectionObserver(this);
(wrapId ? IntersectionObserver.relativeTo(`#${wrapId}`) : IntersectionObserver.relativeToViewport({ top: observeDistance, bottom: observeDistance }))
.observe(`#${virtualId}`, ({ intersectionRatio }) => {
this.setData({
isShow: intersectionRatio > 0,
})
})
},
}
})
page->index->index.wxml
<view>
<scroll-view class="virtualScrollView" eventhandle scroll-y bindscrolltolower="onScrollLower">
<block wx:for="{{ listData }}" wx:key="screenIndex" wx:for-index="screenIndex" wx:for-item="screenItem">
<VirtualItem virtualId="virtual_{{pageIndex}}">
<view class="fall">
<block wx:for="{{ screenItem.columns }}" wx:key="columnIndex" wx:for-index="columnIndex" wx:for-item="column" >
<view style="margin-top: -{{screenItem.columnOffset[columnIndex]}}px;" class="fallCol">
<view wx:for="{{column}}" style="height: {{ item.height }}px; background-color: {{ item.color }};" wx:key="index" wx:for-item="item" wx:for-index="index">
screen: {{ screenIndex }}, column: {{ columnIndex }}
这里边就可以直接写你需要的数据,不需要使用css设置瀑布流,直接就是现成的
</view>
</view>
</block>
</view>
</VirtualItem>
</block>
</scroll-view>
</view>
.virtualScrollView {
height: 100vh;
}
.fall {
position: relative;
display: flex;
}
.fallCol {
flex: 1;
display: flex;
flex-direction: column;
}
.cell {
margin: 5px;
}
Page({
data: {
listQuery: {
pageIndex: 1,
pageSize: 10,
}, // 列表请求参数
listData: [], // 列表数据
column: 2, // 列数。需要三列可以改为3
columnsHeights: [ 0, 0], // 每列高度如果column为3的话那么这个值就是[0, 0, 0]
},
onLoad() {
this.getList();
},
async getList() {
let { listQuery: { pageIndex }, column, columnsHeights } = this.data;
const columns = [];
// 上一组的高度数据,用于计算偏移值
const lastHeights = [...columnsHeights];
// 获取数据 这个就是你的真实数据,调取接口然后赋值给list。当然会异步
// 所以可以使用Promise和async/await来确保在你调取接口之后能赋值给list成功。代码已在最底下写出
await this.getData(); // 自己的真实数据调取的接口
const list = this.data.getdata;
// const list = this.getListData();
// 初始化当前屏数据
for (let i = 0; i < column; i++ ) {
columns.push([]);
}
// 遍历新数据,分配至各列
for (let i = 0; i < list.length; i++) {
const position = this.computePosition(columnsHeights);
columns[position].push(list[i]);
// 如果使用自己的真实数据报错的话就把Number(list[i].height)改为自己喜欢的数字100或者100多
columnsHeights[position] += Number(list[i].height);
}
this.setData({
[`listData[${pageIndex}]`]: {
columns,
columnOffset: this.computeOffset(lastHeights),
}
});
this.data.listQuery.pageIndex = pageIndex + 1;
this.data.columnsHeights = columnsHeights;
},
/**
* 获取列表数据。模拟的假数据
*/
getListData() {
const result = [];
for (let i = 0; i < this.data.listQuery.pageSize; i++) {
const height = Math.floor(Math.random() * 300);
const item = {
height: height < 150 ? height + 150 : height,
color: this.randomRgbColor(),
};
result.push(item);
}
return result;
},
/**
* 随机生成RGB颜色
*/
randomRgbColor() {
var r = Math.floor(Math.random() * 256); //随机生成256以内r值
var g = Math.floor(Math.random() * 256); //随机生成256以内g值
var b = Math.floor(Math.random() * 256); //随机生成256以内b值
return `rgb(${r},${g},${b})`; //返回rgb(r,g,b)格式颜色
},
/**
* 获取最小高度列下标
*/
computePosition(heights) {
const min = Math.min(...heights);
return heights.findIndex((item) => item === min);
},
/**
* 计算偏移量
*/
computeOffset(heights) {
const max = Math.max(...heights);
return heights.map((item) => max - item);
},
onScrollLower() {
// 用自己的调取接口拿到的total的总数和pageindex做判断大于总数就不掉用接口了
this.getList();
}
// 封装起来的Promise
httpGet(url, params) {
return new Promise((resolve, reject) => {
http.Get(url, params, function (res) {
if (res.status == 0) {
resolve(res.data);
} else {
reject(new Error('请求失败'));
}
});
});
},
})
有好多人还有更好的思路,有的可以评论大家一起探讨。微信小程序的代码片段。有真实数据的。
代码片段打开报错的话把该换的数据换了就可以了。