前言:
在一个表格中,需要展示100条数据,当每条数据里面需要承载的内容很多,需要渲染的元素也很多的时候,容易造成页面加载的速度很慢,不能给用户提供很好的体验时,懒加载是优化页面加载速度的方法之一。
策略:
前端在接受到api返回的数据的时候,可以先根据数据总的条数进行遍历,给每一项初始化简单的dom进行渲染占位,通过IntersectionObserver对每一项元素进行监听,当初始dom出现在页面视口的时候,需要替换掉初始dom,渲染真实dom。这样可以实现:在初始dom出现在视口内时,替换掉初始dom,渲染真实dom,并取消对该dom的监听,实现只需加载一次,首次加载多少个真实dom,取决于可视区域跟初始dom的高度。
React中使用LazyBuilder实现页面懒加载方法一
与方法一不同之处:
方法一:元素出现在可视区域内,即渲染真实dom,一旦消失在可视区域内,即渲染初始dom
方法二:元素只要出现在可视区域内一次,即渲染真实dom,并且取消对该dom的监听,只需加载一次
LazyBuilder.jsx
import React, { Component, createRef } from "react";
class LazyBuilder extends Component {
static defaultProps = {
initComponent: null,
initHeight: null,
controller: null,
className: null,
style: null,
}
/**
* @param {Object} props
* @param {JSX.Element} [props.initComponent] - 默认组件
* @param {Number} [props.initHeight] - 组件高度
* @param {LazyController} [props.controller] - LazyController
*/
constructor(props) {
super(props);
this._ref = createRef();
this.controller = this.props.controller instanceof LazyController ? this.props.controller : new LazyController();
this.state = {
isLoading: true,
initStyle: {
width: "100%",
height: props.initHeight
},
key: `lazy_${Math.random().toString(16).slice(2)}`,
}
}
componentDidMount() {
// 页面初始化时,对所有元素进行绑定监听
this.controller.observe(this._ref.current, this.updateShowElement.bind(this));
}
// 组件销毁时
componentWillUnmount() {
const { autoDispose } = this.props;
if (autoDispose && this.controller.size() === 0) {
this.controller.dispose();
return;
}
this.controller.unobserve(this._ref.current);
}
updateShowElement = () => {
// 元素出现在视口以内
this.setState({
isLoading: false,
initStyle: null,
});
}
render () {
const { children, initComponent } = this.props;
const { isLoading, initStyle } = this.state;
const className = ["lazy-builder-item", this.props.className].filter(item => typeof item === "string").join("\s");
return (
<div id={this.state.key} ref={this._ref} className={className} style={Object.assign({}, initStyle, this.props.style)}>
{
isLoading ? initComponent : children
}
</div>
);
}
}
class LazyController {
constructor(){
// 定义map来存储所有的dom项
this._map = new Map();
// IntersectionObserver 对每一项元素进行监听
this.observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
// isIntersecting: true - 出现在视口 false - 隐藏(视口以外)
if (entry.isIntersecting) {
const updateShowElement = this._map.get(entry.target);
if (typeof updateShowElement === "function") {
updateShowElement()
}
this.unobserve(entry.target);
}
}
});
}
// 观察指定DOM
observe = (target, callback) => {
if (this.observer && !this.has(target)) {
this._map.set(target, callback);
this.observer.observe(target);
}
}
// 取消对指定DOM的观察
unobserve = (target) => {
if (this.observer && this.has(target)) {
this.observer.unobserve(target);
this._map.delete(target);
}
}
// 判断一个DOM是否正在被观察
has = (target) => {
return this._map.has(target);
}
// 返回正在观测中的DOM数量
size = () => {
return this._map.size();
}
// 停止对所有DOM的观测并销毁IntersectionObserver实例
dispose = () => {
if (this.observer == null) {
throw new Error("observer未初始化");
}
this._map.clear();
this.observer.disconnect();
this.observer = null;
}
}
export {
LazyBuilder,
LazyController,
}
Cp.jsx
export default class Cp extends Component {
constructor(props){
super(props)
// 创建controller
this.controller = new LazyController();
this.state = {
// 模拟数据
dataList: new Array(100).fill().map((item, index) => index + 1)
}
}
componentWillUnmount() {
this.controller.dispose();
}
render(){
const {dataList} = this.state
return (
<div>
{
Array.isArray(dataList) && dataList.length > 0
? dataList.map((item, index) => {
return <LazyBuilder
key={index}
initHeight={200} // 初始dom高度
controller={this.controller} // controller
>
<div style={{width: '100%', height: '200px'}}>{`第${item}个元素`}</div>
</LazyBuilder>
})
: null
}
</div>
)
}
}