IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 前端高性能渲染大型树形结构组件(附全部代码React、Vue) -> 正文阅读

[JavaScript知识库]前端高性能渲染大型树形结构组件(附全部代码React、Vue)

问题:

使用一般的tree组件渲染大量数据(如几千个树节点)的时候会非常卡顿,主要原因是页面中绘制的大量的Dom,滚动或展开、收起不断造成页面重绘、回流,使得性能不佳。


解决思路:

Step1:将树形数据拍平成一般的List

Step2:采用padding缩进的方式营造树形结构

Step3:在结合虚拟列表高效渲染长列表

?虚拟列表大致原理:当列表data中有n个item项,我们只渲染可视区域(比如10条)的item,页面滚动时获取到scrollTop,scrollTop / itemHeight = startIndex(当前滚动了多少条的索引),可视区域的数据 = data.slice(startIndex, startIndex + 10)),将可视区域数据渲染到页面即可。

数据说明:

列表项高度固定:itemHeight

列表数据:data,源数据

当前滚动位置:scrollTop

可视区域的数据:visibleData?,就是你要真实渲染的数据

列表真实长度:itemHeight * data.length,制造滚动条

接着监听的scroll事件,获取滚动位置scrollTop

计算当前可视区域起始数据索引(startIndex = Math.floor(scrollTop / itemHeight) )

计算当前可视区域结束数据索引(endIndex = startIndex + visibleCount)

计算当前可视区域的数据 (visibleData?= data.slice(startIndex,endIndex))

计算startIndex对应的数据在整个列表中的偏移量offset并设置到列表上


实际效果:



完整代码:

import React, { useCallback, useEffect, useRef, useState } from 'react';

import './index.css';

import { originData } from './mockData';

// 配置项
const options = {
  defaultExpand: 1,
  itemHeight: 30,
  visibleCount: 15,
};

// 将树形数据转成普通列表数据(我用的是中国省-市-区的树形数据)
function flattenData() {
  function flatten(tree, childKey = 'children', level, parent = null) {
    let res = [];
    tree.forEach((item) => {
      item.level = level;
      item.expand = level === 1;
      item.parent = parent;

      if (item.visible === undefined) {
        item.visible = true;
      }
      if (!parent.visible || !parent.expand) {
        item.visible = false;
      }
      res.push(item);

      if (item[childKey] && item[childKey].length) {
        res.push(...flatten(item[childKey], childKey, level + 1, item));
      }
    });
    return res;
  }
  return flatten(originData, 'children', 1, {
    level: 0,
    visible: true,
    expand: true,
    value: '中国',
    children: originData,
  });
}

// 定义组件
function VirtualTree() {
  // 如果是vue把data、visibleData...等定义在data() {}里,把setXxx定义在methods里即可
  const treeRef = useRef();
  const [data, setData] = useState([]);
  const [visibleData, setVisibleData] = useState([]);
  const [contentHeight, setContentHeight] = useState(10000);
  const [offset, setOffset] = useState(0);

  // 模拟获取接口数据
  const getData = useCallback(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(flattenData());
      }, 500);
    });
  }, []);

  // 挂载时获取原始数据,相当于vue的 mounted()
  useEffect(() => {
    getData().then((res) => {
      setData(res);
    });
  }, []);

  // data变化更新可视数据,相当于vue的watch data
  useEffect(() => {
    if (data.length) {
      updateVisibleData();
    }
  }, [data]);

  // 可视数据变化更新容器高度, 相当于vue的watch visibleData
  useEffect(() => {
    setContentHeight(visibleData.length * options.itemHeight);
  }, [visibleData]);

  // 获取所有可视数据
  function getAllVisibleData() {
    return data.filter((item) => item.visible);
  }

  // 滚动页面时更新 visibleData、offset
  function updateVisibleData(scrollTop = 0) {
    const start = Math.floor(scrollTop / options.itemHeight);
    const end = start + options.visibleCount * 2;
    const allVisibleData = getAllVisibleData();
    const _visibleData = allVisibleData.slice(start, end);

    setVisibleData(_visibleData);
    setOffset(scrollTop);
  }

  function handleScroll() {
    const { scrollTop } = treeRef.current;
    updateVisibleData(scrollTop);
  }

  function recursionVisible(children = [], status) {
    children.forEach((node) => {
      // 如果是折叠-->折叠所有子项; 如果是展开-->显示下一级
      node.visible = status;
      if (!status) {
        node.expand = false;
      }
      if (node.children && node.children.length && !status) {
        recursionVisible(node.children, status);
      }
    });
  }

  // 折叠与展开
  function toggleExpand(item) {
    const isExpand = !item.expand;
    item.expand = isExpand;
    recursionVisible(item.children, isExpand);

    // 更新视图
    handleScroll();
  }

  return (
    <div ref={treeRef} className="tree" onScroll={handleScroll}>
      {/* tree-phantom是用于制造滚动条 ,= 所有可视item的高度之和 */}
      <div className="tree-phantom" style={{ height: contentHeight }}></div>
      <div
        className="tree-content"
        style={{ transform: `translateY(${offset}px)` }}
      >
        {visibleData.map((item, index) => {
          return (
            <div
              key={item.value + item.parent.value}
              className="tree-list"
              style={{
                paddingLeft:
                  15 * (item.level - 1) + (item.children ? 0 : 15) + 'px',
                height: options.itemHeight + 'px',
              }}
            >
              {item.children && item.children.length && (
                <span
                  onClick={(e) => {
                    e.stopPropagation();
                    toggleExpand(item);
                  }}
                >
                  <i className={item.expand ? 'tree-expand' : 'tree-close'} />
                </span>
              )}
              <span>{item.label}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default VirtualTree;

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-07 11:56:25  更:2021-12-07 11:57:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:43:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码