前端组件的滚动懒加载是一个很有用的功能。这里的懒加载概念不是路由分割拆包那种,那是以文件资源为单位进行拆分,一般需要 Webpack 支持,常用的有 @loadable/component 这样的第三方库。
本文讲解的 Lazy Load 是面向页面组件级别的,是为了解决这类问题:
这种滚动到视口范围内才进行加载的思路,某些场景可以极大的提高页面性能,特别是对长列表和图片,可以提高首屏加载速度,避免过度渲染。
当然,这种思路在很早以前的 jQuery 时代就有了。本文讲解 React 社区的 React Lazy Load 库的实现思路,以供参考。
React Lazy Load 用法很简单,只要包裹一下你需要懒加载的组件,这样它只会在视口中 render:
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';
const App = () => {
return (
<div className="list">
<LazyLoad height={200}>
<img src="tiger.jpg" /> /*
Lazy loading images is supported out of box,
no extra config needed, set `height` for better
experience
*/
</LazyLoad>
<LazyLoad height={200} once >
/* Once this component is loaded, LazyLoad will
not care about it anymore, set this to `true`
if you're concerned about improving performance */
<MyComponent />
</LazyLoad>
<LazyLoad height={200} offset={100}>
/* This component will be loaded when it's top
edge is 100px from viewport. It's useful to
make user ignorant about lazy load effect. */
<MyComponent />
</LazyLoad>
<LazyLoad>
<MyComponent />
</LazyLoad>
</div>
);
};
ReactDOM.render(<App />, document.body);
React Lazy Load 本身就是一个 React 组件,参考它的源码,我们来捋一下它的思路:
import React from 'react'
const listeners = []
let finalLazyLoadHandler = null
function lazyLoadHandler() {
// 对每一个组件进行监听
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
checkVisible(listener)
}
}
function checkVisible(component) {
let visible = false
// 对 component 是否出现在视口里进行判断
// 可以利用 component.ref 来检测
// 如果可见了,则更新
if (visible) {
component.visible = true
component.forceUpdate()
}
}
class LazyLoad extends React.Component {
constructor(props) {
this.visible = false
this.ref = React.createRef()
}
componentDidMount() {
// 设立滚动元素
const scrollport = window
// 确定滚动事件,使用 debounce 来优化性能
if (!finalLazyLoadHandler) {
finalLazyLoadHandler = debounce(lazyLoadHandler)
}
// 绑定滚动事件,只在 listeners 为空的时候绑定
if (listeners.length === 0) {
on(scrollport, 'scroll', finalLazyLoadHandler, passiveEvent)
on(scrollport, 'resize', finalLazyLoadHandler, passiveEvent)
}
// 放入 listener 监听器中
listeners.push(this)
// 检查是否可见
checkVisible(this);
}
componentWillUnmount() {
// 卸载组件和事件
const index = listeners.indexOf(this)
if (index !== -1) {
listeners.splice(index, 1)
}
if (listeners.length === 0) {
off(window, 'scroll', finalLazyLoadHandler, passiveEvent)
off(window, 'resize', finalLazyLoadHandler, passiveEvent)
}
}
render() {
const {
visible,
placeholder,
height,
children
} = this.props;
return (
<div ref={this.ref} className="lazyload-wrapper">
{
visible
? children
: (
placeholder
? placeholder
: <div className="placeholder" style={{height: height}}></div>
)
}
</div>
);
}
}
这是实现的“伪代码”,它的流程是这样的:
在 componentDidMount 阶段
然后滚动的时候,不停地调用 lazyLoadHandler
至于如何判断当前组件是否“可见”,涉及到利用 React ref 和 DOM 结构的判定,可以参考checkNormalVisible,这里就不啰嗦了。
在项目中,我们可以结合 @loadable/component 来使用:
import React from 'react'
import loadable from '@loadable/component'
import LazyLoad from 'react-lazyload'
const LazyComponent = loadable(() => import(/* webpackChunkName: 'Component' */ './Component'))
class App extends React.Component {
render() {
<LazyLoad>
<LazyComponent />
</LazyLoad>
}
}
组件懒加载和资源懒加载的优势相结合,可以将页面性能优化到极致。