likes
comments
collection
share

对于已经开发好的长列表,如何做简易版性能优化

作者站长头像
站长
· 阅读数 48
  1. 使用react-window改造渲染省市区的长列表
  2. 简易版性能优化方法

使用react-window改造渲染省市区的长列表

  1. 不使用虚拟滚动渲染省市区列表
  2. 使用虚拟滚动改造
  3. 虚拟滚动参数分析
  4. 处理数据结构,修改渲染逻辑

不使用虚拟滚动渲染省市区列表

首先确定数据结构,这里只列出一个省市区

[    {        "label": "北京市",        "value": "北京市",        "level": "one",        children: [            "label": "北京市",            "value": "北京市",            "level": "two"            "children": [{                "label": "东城区",                "value": "东城区",                "level": "three"            }]
        ]
    }
]

这里需要用到递归,有children的话我们就继续渲染

import React from 'react'
import areaList from './province.json'
import './index.css'

const Page = () => {
    const renderArea = (list) => {
        return list.map(item => {
            return (
                <div key={item.label} className={item.level}>
                    { item.label }
                    { item.children && renderArea(item.children) }
                </div>
            )
        })
    }

    return (
        <div className="wrapper">
            { renderArea(areaList) }
        </div>
    )
}

export default Page

效果: 对于已经开发好的长列表,如何做简易版性能优化

使用虚拟滚动改造

先看下虚拟滚动组件需要的参数

const Row = ({ index, style }) => {
    return (
        <div style={style}></div>
    )
}

<AutoSizer>
    {({ height, width }) => (
        <FixedSizeList
            width={width} //列表可视区域的宽度
            height={height} // 列表可视区域的高度
            itemCount={1} // 列表数据长度
            itemSize={40} // 列表行高
        >
            { Row }
        </FixedSizeList>
    )}
</AutoSizer>

虚拟滚动参数分析

itemCount:虚拟滚动项的数量,在这里就是省市区的数量。 Row组件:每一个虚拟滚动项要渲染的内容。Row会接收参数index,我们可以通过index知道正在渲染哪一项,再通过省市区List[index]来得到要渲染的内容。 目标:得到一个省市区List,这个List是一个一维数组,形如下面的结构才能满足我们的需求。

[    { "label": "北京市", "value": "北京市", level: "one" },    { "label": "北京市", "value": "北京市", level: "two" },    { "label": "东城区", "value": "东城区", level: "three" },]

处理数据结构,修改渲染逻辑

遍历树形省市区列表,转为一维数组


const getFlatList = (originList, target) => {
    originList.forEach(item => {
        const { children, ...rest } = item
        target.push({
            ...rest
        })

        if (children) {
            getFlatList(children, target)
        }
    })
}
    
const list = []
getFlatList(provinceList, list)
console.log('list', JSON.stringify(list))

得到一维数组后可以放到本地json文件,得到一维数组后,根据Row组件给的index参数就可以知道当前应该渲染的内容了。

const Row = ({ index, style }) => {
    const currentItem = provinceList[index]
    return (
        <div style={style} className={currentItem.level}>
            {currentItem.label}
        </div>
    )
}

<div style={{ height: '400px'}} className="wrapper">
    <AutoSizer>
        {({ height, width }) => (
            <FixedSizeList
                width={width} //列表可视区域的宽度
                height={height} // 列表可视区域的高度
                itemCount={provinceList.length} // 列表数据长度
                itemSize={40} // 列表行高
            >
                { Row }
            </FixedSizeList>
        )}
    </AutoSizer>
</div>

效果: 对于已经开发好的长列表,如何做简易版性能优化

现在使用react-window改造完成了,可以看到,我们原本写的递归遍历渲染地区的方法已经不需要了,取而代之的是一个接收index来渲染内容的方法,同时我们需要处理数据结构,树形结构的数据要扁平化为一维数组。总体看下来就是要处理数据结构,并且修改渲染逻辑,如果在业务复杂的情况修改渲染逻辑那成本就很大了。

简易版性能优化方法

简易版性能优化的方法,思路就是首次渲染时只渲染前面几十项,等滚动条移动到一定高度时,再往后渲染几十项。这样也可以避免首次加载过多的dom节点导致的页面卡顿。和虚拟滚动不同的是,当滚动条移动到下方时,我们并不会清掉之前渲染好的dom节点,但经测试渲染完后的页面即使页面节点多,在我们拖动滚动条时也并不会很卡,页面卡死只在首屏渲染时创建节点过多时发生,所以我们可以使用这种简易版性能优化方法。

  1. 减少首屏渲染数据
  2. 监听滚动条,到达一定高度时触发下次渲染

减少首屏渲染数据

我们先筛选出3个地区的数据,3个地区对比渲染所有地区已经能减少一定数量的dom节点,而且能保证出现滚动条,如果只渲染1个地区可能由于数据量不够而不出现滚动条,所以我们选择渲染3个地区。

import originList from './province.json'

const [areaList, setAreaList] = useState(originList.slice(0, 3))

const renderArea = (list) => {
    return list.map(item => {
        return (
            <div key={item.label} className={item.level}>
                { item.label }
                { item.children && renderArea(item.children) }
            </div>
        )
    })
}

<div className="wrapper">
    { renderArea(areaList) }
</div>

效果:

对于已经开发好的长列表,如何做简易版性能优化

监听滚动条,到达一定高度时触发下次渲染

当满足scrollTop + clientHeight >= documentHeight时代表滚动到底部,我们可以让滚动条滚动到文档总高度80%时就往数组再push一个地区,push之前可以先判断一下是否存在下一个地区。

const [areaList, setAreaList] = useState(originList.slice(0, 3))
const curAreaIdx = useRef(3)

useEffect(() => {
    window.addEventListener('scroll', function() {
        const clientHeight = document.documentElement.clientHeight
        const scrollTop = document.documentElement.scrollTop
        const documentHeight  = document.documentElement.scrollHeight
        if (
            (scrollTop + clientHeight >= documentHeight * 0.8) && (originList[curAreaIdx.current])
        ) {
            console.log('滚动到总高度的80%')
            const list = [...areaList, originList[curAreaIdx.current]]
            setAreaList(list)
            curAreaIdx.current += 1
        }
    })
}, [])

其中clientHeight、scrollTop、scrollHeight涉及兼容性的写法,这里就直接使用documentElement来获取了。curAreaIdx用来记录当前应该渲染哪一个地区,渲染结束后也要去更新这个变量。

效果:

对于已经开发好的长列表,如何做简易版性能优化

可以看到,第一次推入一个地区后,数组长度为4,再一次推入一个地区时数组长度仍然为4。这是因为const list = [...areaList, originList[curAreaIdx.current]]这句代码里我们是在useEffect里去获取areaList,而areaList永远是我们初始化时的三个地区的数组

useEffect里获取到旧的state的解决办法

即使我们已经重新渲染了视图,areaList的值已经变化,useEffect也获取不到最新的areaList,这里与函数组件依赖于闭包有关,在useEffect获取areaList获取到的是某次特定渲染的值。我们使用的是useEffect(() => {}, []),则areaList的值永远是挂载时的areaList的值。要解决这个问题可以使用useRef来保存已经渲染好的数组,因为useRef的值不受限于某次特定的渲染。

const [areaList, setAreaList] = useState(originList.slice(0, 3))
const curAreaList= useRef(originList.slice(0, 3))

useEffect(() => {
    window.addEventListener('scroll', function() {
        const clientHeight = document.documentElement.clientHeight
        const scrollTop = document.documentElement.scrollTop
        const documentHeight  = document.documentElement.scrollHeight
        if (
            (scrollTop + clientHeight >= documentHeight * 0.8) && (originList[curAreaList.current.length])
        ) {
            console.log('滚动到总高度的80%')
            const list = [...curAreaList.current, originList[curAreaList.current.length]]
            setAreaList(list)
            console.log('list', list)
            curAreaList.current = list
        }
    })
}, [])

我们使用curAreaList来保存渲染好的数组,每次推入新的地区后,再更新下curAreaList的值

效果:

对于已经开发好的长列表,如何做简易版性能优化

这样就完成了简易版的性能优化,不需要改数据结构,也不需要改原来的渲染逻辑,对比使用react-window开发成本更低。

转载自:https://juejin.cn/post/7373226679731142666
评论
请登录