简介
1 .虚拟化列表能力的hook,用于展示海量数据渲染时首屏渲染缓慢和卡顿的问题
扩展
1 .和无限列表相结合,每次滚动到最下方,就拉取一些新的元素
import React,{useEffect,useState,useRef,useMomo} from 'react'
import {useVirtualList,useInViewport} from 'ahooks'
export default function Drag(props){
const containerRef=useRef()
const wrapperRef=useRef()
const footerRef=useRef()
const [origin,setOrigin]=useState([1,2,3,4,5,6,7,8,9,10])
const [inViewport]=useInViewport(footerRef)
const [list]=useVirtualList(origin,{
containerTarget:containerRef,
wrapperTarget:wrapperRef,
itemHeight:60,
overscan:1,
})
function handleAdd(){
let data=[]
for(let i=origin.length+1;i<origin.length+5;i++){
data.push(i)
}
// 假设在请求资源
setTimeout(()=>{
setOrigin([...origin,...data])
console.log(origin,'当前')
},2000)
}
useEffect(()=>{
console.log(inViewport,'outer加一组数据')
if(inViewport){
console.log(inViewport,'inner加一组数据')
handleAdd()
}
},[inViewport])
return (
<div>
<div ref={containerRef} style={{height:'200px',overflow:'auto',border:'1px solid #eee'}}>
<div ref={wrapperRef}>
{list.map((el)=>{
if(el.index===origin.length-1){
return <div ref={footerRef} style={{color: inViewport ? '#87d068' : '#f50' ,
height: 52,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid #e8e8e8',
marginTop: 8,
}}
onClick={handleAdd}
// 万一没有触发,就加一个手动的点击
>
{inViewport?"正在加载":"更多"}
</div>
}else{
return <div
key={el.index}
style={{
height: 52,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid #e8e8e8',
marginTop: 8,
}}
>{JSON.stringify(el)}</div>
}
})}
</div>
</div>
<button onClick={handleAdd}>{inViewport?'visible':'hidden'}</button>
</div>
)
}
2 .问题1,const [inViewport]=useInViewport(footerRef) 发现元素出现的时候会弹好几次参数
image.png
1 .margin的时候就算出现了,不精确
3 .问题2,const [inViewport]=useInViewport(footerRef)首次没渲染的时候,有这个标记,就会里面拉取一下.导致一开始就有问题
这里这个可以当作loading界面,初次加载的时候这里数据默认为空,一到这个页面,发现在里面就去加载数据吧,加载了新数据把他顶到下面,看不见
1 .首次加载的时候他的值变化是undefind,true,false
3 .问题2 ,之前最麻烦的时候东西就是动态高度.这就要求.请求到数据,算出来他的高度,然后把高度加到原来的里面,这时候算完,再把他当成一个知道高度的放到原来的里面.看别的库的api或者思路来说,是会有一个固定高度的,先用这个,然后大小发生改变的时候,算出改变之后的高度,数据也是动态增加的,不是一直就有的
MutationObserver API
import React,{useEffect,useState,useRef} from 'react'
import {useVirtualList,useInViewport} from 'ahooks'
export default function Drag(props){
const [obj,setObj]=useState({})
const id='1'
useEffect(()=>{
setTimeout(()=>{
setObj(Object.assign({},{name:'刘华强',age:20}))
},1000)
let observer=new MutationObserver(()=>{
const {height} =document.getElementById(id).getBoundingClientRect()
console.log('发生了变化',height)
// 然后把这个值给广播出去
})
observer.observe(document.getElementById(id),{
childList:false,
// 子节点的新增,删除,更改能否被检测
attributes:false,
//属性的变动能否被检测
characterData:true,
// 节点内容或节点文本的边动能否被检测:我们这里是只会有文字新增,所以这里选这个,别的检测都关掉
subtree:true,
// 是否应用于该节点的所有后代节点:为啥必须是这个
})
return ()=>{
observer.disconnect()
// 组件消除的时候取消观察
}
},[])
return (
<div>
<div id={id} style={{lineHeight:20}}>
{JSON.stringify(obj)}
</div>
</div>
)
}
//方法2:在数据变化的时候,求大小,但是这不是加在回调里面,感觉可能不会很准,虽然现在能看到值,不是很保险,但是简单
const [obj,setObj]=useState({})
const id='1'
useEffect(()=>{
setTimeout(()=>{
setObj(Object.assign({},{name:'刘华强',age:20}))
},1000)
const node=document.getElementById(id)
const height=node.getBoundingClientRect().height
console.log(height)
},[])
return (
<div>
<div id={id} style={{lineHeight:20}}>
{JSON.stringify(obj)}
</div>
</div>
)
问题3 .添加数据的时候,进度条高度会有闪动,不是很稳定,用react-window来实现一下这个吧
import React,{useEffect,useState,useRef} from 'react'
import { VariableSizeList as List } from 'react-window'
export default function Drag(props){
// 高度不确定
// 动态增加数据
const data=[]
//全部的数据,会动态增加
const rowSize=[]
// 全部item的高度
function getItemSize(index){
console.log('idx',index,rowSize[index-1])
if(rowSize[index]){
return rowSize
}else{
return 21.97
}
}
function Row({index,style}){
const rowRef=useRef()
useEffect(()=>{
let observer=new MutationObserver(()=>{
const {height} =rowRef.current.getBoundingClientRect()
rowSize[index]=height
console.log('触发了变化',rowSize)
})
rowSize[index]=rowRef.current.getBoundingClientRect().height
console.log(rowRef.current,'初始的高度',rowSize)
observer.observe(rowRef.current,{
childList:false,
// 子节点的新增,删除,更改能否被检测
attributes:false,
//属性的变动能否被检测
characterData:true,
// 节点内容或节点文本的边动能否被检测:我们这里是只会有文字新增,所以这里选这个,别的检测都关掉
subtree:true,
// 是否应用于该节点的所有后代节点:为啥必须是这个
})
return ()=>{
observer.disconnect()
// 组件消除的时候取消观察
}
},[])
// setTimeout(()=>{
// setValue(index+Math.random()*1000)
// },1000)
return (
<div style={style} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} ref={rowRef}>
{/* 子项里面的style,必须要绑定,可以看成是需要从父元素继承他需要算的一些东西 */}
Row {index}<br/>
{index%2?<span>加一行</span>:""}
</div>
)
}
const Example=()=>(
<List
height={200}
itemCount={20}
width={500}
itemSize={getItemSize}
>
{Row}
</List>
)
return (
<div>
<Example />
</div>
)
}
问题4 .useRef无效的问题:结果是绑定错了
import React, { useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const observed = useRef(null);
useEffect(() => {
console.log(observed.current);
}, [observed]);
return (
<div ref={observed} className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
react-window不支持动态高度的
const rowHeights = new Array(1000)
.fill(true)
.map(() => 25 + Math.round(Math.random() * 50));
const getItemSize = index => rowHeights[index];
1 .最关键的是这种每一个元素的动态高度是拿不到的.如何提前知道呢,难道是没显示的时候就先渲染出来么..感觉也是不可能的
2 .还有就是渲染出来告诉父组件这个现在的高度,然后重新刷新.但是问题来了
<div style={style} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} ref={rowRef}>
{/* 子项里面的style,必须要绑定,可以看成是需要从父元素继承他需要算的一些东西 */}
Row {index}<br/>
{index%2?<span>加一行</span>:""}
</div>
//子组件需要接收style,这个style就是父组件传下来的每一个子元素的css样式,已经定死了height,搞得你没法算最新的高度,而且就算算出来也不一定能更新出来
useVirtualList实现动态高度
import React, { useMemo, useRef,useEffect,useCallback,useState} from 'react';
import { useVirtualList } from 'ahooks';
export default function Drag (){
const containerRef = useRef();
const wrapperRef = useRef();
const originalList = useMemo(() => Array.from(Array(20).keys()), []);
const itemHeightArray=useRef([])
//存储了所有的高度
const lastComputed=useRef(0)
const [list,] = useVirtualList(originalList, {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: (i) =>{
// if(itemHeightArray.current[i]){
// return itemHeightArray.current[i]
// }else{
// return 21.99
// }
return 42
},
overscan: 10,
});
function Item(props){
const itemRef=useRef()
useEffect(()=>{
if(props.ele.index<lastComputed.curent){
return ()=>{
console.log('已经做过计算了')
}
}else if(props.index>lastComputed.current){
lastComputed.current=props.ele.index
}
let observer=new MutationObserver(()=>{
console.log('发生了变化')
})
// 初次加载的时候更新一波高度
itemHeightArray.current[props.ele.index]=itemRef.current.getBoundingClientRect().height
observer.observe(itemRef.current,{
childList:false,
attributes:false,
characterData:true,
subtree:true,
})
return ()=>{
observer.disconnect()
}
},[])
return (
<div
ref={itemRef}
style={{
height: props.ele.index % 2 === 0 ? 42 :42,
// 高度做成自定义的
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid #e8e8e8',
marginBottom: 8,
lineHeight:1.5,
}}
key={props.ele.index}
>
Row: {props.ele.data}<br/>
{props.ele.index % 2 === 0 ? 'small' : ''}
</div>
)
}
return (
<div>
<div ref={containerRef} style={{ height: '300px',
overflow: 'auto',
width:400 }}>
<div ref={wrapperRef}>
{list.map((ele) => (
<Item ele={ele}/>
))}
</div>
</div>
</div>
);
};
性能分析
image.png1 .脚本执行花费了那么多时间吗?去掉计算动态高度的代码,也是这么多脚本执行时间,问题出在哪里呢
image.png
2 .不新开函数立马就下来了,难道还和这个有关系?离谱啊
function It(){
return (
<div>
测试1
</div>
)
}
//竟然真的是这样,这么简单的一个组件,里面没有任何逻辑,按照理解应该是和上面的一样啊,竟然脚本计算时间瞬间就上去了.但是在官方的demo里面这样改就没问题,脚本计算没这个爆炸.为啥呢
3 .用新函数开一个item性能这么差么
4 .普通渲染的差距看下,在普通的列表里面,是没关系的.那就是这个组件里面的差距,这感觉要翻下源码.
5 .组件的时候scroll事件占了大部分的时间
6 .不用组件的时候,出现的事件确是whell事件.还有指针事件,算了,看下他的源码吧,为什么框架里面只绑定了scroll事件,但是会触发各种各样的事件呢.pointerout,mouseout,scroll
最后
1 .目前是想要的效果实现了,帧数有时候会跑不满60,需要优化
2 .还有就是会出现布局抖动的问题,这个是看浏览器出现的,后续也要研究下
网友评论