You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

AngularJS+UI-Select大数据列表下拉框性能问题求助

针对AngularJS UI-Select大数据量性能问题的优化方案

刚巧之前在项目里踩过UI-Select处理5000+条数据的性能坑,结合你的情况(最小输入长度不适用、监听器暴增、双向绑定调整无效),给你几个亲测有效的优化方向:

1. 实现虚拟滚动,只渲染可见区域选项

UI-Select打开慢的核心原因之一是一次性渲染5000条DOM元素,连带产生大量监听器。虚拟滚动可以只渲染当前视口内的选项,彻底解决这个问题。

你可以给ui-select-choices自定义一个虚拟滚动指令:

  • 先计算单条选项的高度,根据下拉框的可视高度,算出能显示的选项数量
  • 维护一个截取后的子集数据(比如当前显示的20条),绑定到ng-repeat
  • 监听下拉框的滚动事件,根据滚动位置更新子集的起始索引,动态替换渲染的选项

示例代码思路:

<ui-select-choices 
  repeat="item in visibleItems track by item.id"
  ng-scroll="updateVisibleItems($event)">
  <div ng-bind="::item.name"></div>
</ui-select-choices>
$scope.visibleItems = [];
$scope.itemHeight = 30; // 单条选项的固定高度
$scope.visibleCount = Math.floor($('.ui-select-choices').height() / $scope.itemHeight);

$scope.updateVisibleItems = function(event) {
  const scrollTop = event.target.scrollTop;
  const startIndex = Math.floor(scrollTop / $scope.itemHeight);
  // 多渲染5条避免滚动时出现空白
  $scope.visibleItems = $scope.allItems.slice(startIndex, startIndex + $scope.visibleCount + 5); 
  $scope.$apply();
};

2. 用一次性绑定减少监听器数量

UI-Select默认会给每个选项的绑定添加watchers,大数据量下会导致监听器暴增。用AngularJS的一次性绑定语法::)可以让绑定只执行一次,不再持续监听变化:

<ui-select-choices repeat="item in filteredItems track by item.id">
  <!-- 用::实现一次性绑定 -->
  <div>{{::item.name}}</div>
  <small>{{::item.description}}</small>
</ui-select-choices>

同时一定要加上track by item.id,避免AngularJS重复创建DOM节点,进一步降低性能开销。

3. 手动实现防抖过滤,关闭默认实时过滤

UI-Select的默认实时过滤会在用户每输入一个字符就触发全量数组过滤,频繁的数组操作和DOM更新会拖慢速度。你可以关闭默认过滤,自己实现防抖过滤:

// 可以用lodash的防抖函数,或者自己写一个简单版
$scope.filteredItems = [];
$scope.debouncedFilter = _.debounce(function(query) {
  if (!query) {
    $scope.filteredItems = $scope.allItems.slice(0, 20); // 无查询时先显示前20条
    return;
  }
  // 手动执行过滤逻辑
  $scope.filteredItems = $scope.allItems.filter(item => 
    item.name.toLowerCase().includes(query.toLowerCase())
  );
  $scope.$apply();
}, 300); // 300ms防抖,避免频繁触发

然后在UI-Select中绑定搜索事件:

<ui-select ng-model="selectedItem" on-search="debouncedFilter($select.search)">
  <ui-select-match placeholder="选择项">{{::selectedItem.name}}</ui-select-match>
  <ui-select-choices repeat="item in filteredItems track by item.id">
    <div ng-bind="::item.name"></div>
  </ui-select-choices>
</ui-select>

4. 延迟加载选项(懒加载)

如果虚拟滚动实现起来有点麻烦,可以先采用懒加载方案:初始打开下拉框时只渲染前20条数据,当用户滚动到下拉框底部时,再追加下一批(比如20条)数据。这样初始打开时DOM元素少,速度快,监听器也不会瞬间暴增。

核心思路是监听下拉框的滚动事件,判断是否滚动到底部,然后更新filteredItems

$scope.loadMoreItems = function(event) {
  const element = event.target;
  // 接近底部时触发加载
  if (element.scrollTop + element.clientHeight >= element.scrollHeight - 10) {
    const currentLength = $scope.filteredItems.length;
    $scope.filteredItems = $scope.filteredItems.concat($scope.allItems.slice(currentLength, currentLength + 20));
    $scope.$apply();
  }
};

总结

优先尝试虚拟滚动+一次性绑定的组合,这两个方案能从根本上减少DOM元素和监听器数量,解决打开耗时久的问题。如果时间紧张,防抖过滤+懒加载的组合也能快速看到性能提升。

内容的提问来源于stack exchange,提问作者mkoala

火山引擎 最新活动