github

React-virtualized is a reactive component that efficiently renders a large table and an virtualized table

Typical Development problems

The student components are:

function Student({student}) {
    return <div>{student.name}</div>
}
Copy the code

If we render the entire list directly, the student list alone will generate 1000+ div tags.

Often, our student components will be:

functionStudent({student, ... rest}) {return( <div> ... <div>{student.name} .... </div> ... </div> ) }Copy the code

At this point, the number of DOM’s becomes unimaginable.

As we all know, if the DOM structure is too large, the user experience will be problematic, such as scrolling, clicking and other common operations. Meanwhile, the react virtual DOM calculation and the reflection of the virtual DOM to the real DOM will also be under great pressure. When the user clicks to switch classrooms, there will be a second lag.

Use react-Virtualized optimization

And then in the React ecology, the React-Virtualized existence as an optimization has been going on for a long time, and the community is constantly being maintained and discussed, which means that it is an ongoing issue! 😂

The core idea to solve the above problem is to load only components in the visible region

React-virtualized differentiates our scrolling scene into an internal scrolling in the viewport and an virtualized scrolling based on the viewport, which is an independent scrolling area on the page. This is internal scrolling, similar to iscroll’s scrolling, which makes scrolling part of the window’s scrolling (which is more common on mobile). Based on this, the components that currently need to be displayed are calculated.

The specific implementation

The student component is modified to:

functionStudent({student, style, ... rest}) {return( <div style={style}> ... <div>{student.name} .... </div> ... </div> ) }Copy the code

Student List component:

import React from 'react'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List as VList } from 'react-virtualized/dist/commonjs/List'

class StudentList extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            list: []
        }
    }
    getList = () => {
        api.getList.then(res => {
            this.setState({
                list: res
            })
        })
    }
    componentDidMount() {
        this.getList()
    }
    render() {
        const { list } = this.state  
        const renderItem = ({ index, key, style }) => {
            return <Student key={key} student={list[index]} style{style} />
        }
        return (
            <div style={{height: 1000}}>
                <AutoSizer>
                    {({ width, height }) => (
                        <VList
                            width={width}
                            height={height}
                            overscanRowCount={10}
                            rowCount={list.length}
                            rowHeight={100}
                            rowRenderer={renderItem}
                        />
                    )}
                </AutoSizer>
            </div>
        )
    }
}
Copy the code

(The height of the outer div is not required. If you have a Flex layout, you can use Flex: 1 to calculate the height and then it is an virtualized motion.)

At this point, if each Student is the same height, the problem is basically solved!

Then again, the problem is that sometimes our students are uncertain of the height and there are two ways to solve this problem. The React-Virtualized CellMeasurer component solution is recommended

Methods a

The student list component is changed to:

import React from 'react'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List as VList } from 'react-virtualized/dist/commonjs/List'
import { CellMeasurerCache, CellMeasurer } from 'react-virtualized/dist/commonjs/CellMeasurer'

class StudentList extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            list: []
        }
    }
    measureCache = new CellMeasurerCache({
        fixedWidth: true,
        minHeight: 58
    })
    getList = () => {
        api.getList.then(res => {
            this.setState({
                list: res
            })
        })
    }
    componentDidMount() {
        this.getList()
    }
    render() {
        const { list } = this.state  
        const renderItem = ({ index, key, parent, style }) => {
            return( <CellMeasurer cache={this.measureCache} columnIndex={0} key={key} parent={parent} rowIndex={index}> <Student key={key}  student={list[index]} /> </CellMeasurer> ) }return (
            <div style={{height: 1000}}>
                <AutoSizer>
                    {({ width, height }) => (
                        <VList
                            ref={ref => this.VList = ref}
                            width={width}
                            height={height}
                            overscanRowCount={10}
                            rowCount={list.length}
                            rowHeight={this.getRowHeight}
                            rowRenderer={renderItem}
                            deferredMeasurementCache={this.measureCache}
                            rowHeight={this.measureCache.rowHeight}
                        />
                    )}
                </AutoSizer>
            </div>
        )
    }
}
Copy the code

Method 2

Use the react-height or react-height method mentioned in the issue to calculate callbacks. Use react-height as an example:

The student list component is changed to:

import React from 'react'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List as VList } from 'react-virtualized/dist/commonjs/List'
import ReactHeight from 'react-height'

class StudentList extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            list: []
            heights = []
        }
    }
    getList = () => {
        api.getList.then(res => {
            this.setState({
                list: res
            })
        })
    }
    componentDidMount() {
        this.getList()
    }
    handleHeightReady = (height, index) => {
        const heights = [...this.state.heights]
        heights.push({
            index,
            height
        })
        this.setState({
            heights
        }, this.vList.recomputeRowHeights(index))
    }
    getRowHeight = ({ index }) => {
        const row = this.heights.find(item => item.index === index)
        return row ? row.height : 100
    }
    render() {
        const { list } = this.state  
        const renderItem = ({ index, key, style }) => {
            if (this.heights.find(item => item.index === index)) {
                return <Student key={key} student={list[index]} style{style} />
            }
            return (
                <div key={key} style={style}>
                    <ReactHeight
                        onHeightReady={height => {
                            this.handleHeightReady(height, index)
                        }}
                    >
                        <Student key={key} student={list[index]} />
                    </ReactHeight>
                </div>
            )
        }
        return (
            <div style={{height: 1000}}>
                <AutoSizer>
                    {({ width, height }) => (
                        <VList
                            ref={ref => this.VList = ref}
                            width={width}
                            height={height}
                            overscanRowCount={10}
                            rowCount={list.length}
                            rowHeight={this.getRowHeight}
                            rowRenderer={renderItem}
                        />
                    )}
                </AutoSizer>
            </div>
        )
    }
}
Copy the code

Now, if all your tabular data is obtained at once, you’re almost done!

What about scroll loading?

React-virtualized officially has an InfiniteLoader!

If put aside this classic case, the development is a chat box?

Chat box is the first time, according to a reverse order is loaded into the data, the position of the scroll bar should be located at the bottom, the react – the List of virtualized component exposes scrollToRow (index) method to implement to us, Student highly inconsistent with direct use of have a little problem, You can’t scroll to the bottom all at once. A temporary solution is:

scrollToRow = (): void => {
    const rowIndex = this.props.list.length - 1
    this.vList.scrollToRow(rowIndex)
    clearTimeout(this.scrollToRowTimer)
    this.scrollToRowTimer = setTimeout(() => {
        if (this.vList) {
            this.vList.scrollToRow(rowIndex)
        }
    }, 10)
}
Copy the code

Called when the data is first loaded

InfiniteLoader does not support the need for reverse loading, so you can only use the onScroll method to get the data and perform related operations. Note that if you use method 1 to return the data from the previous page, Need to do this. MeasureCache. The clear/clearAll, notify the react – virtualized computing again. The second method should add all the indexes in the state.heights array to the number of the data

getList = () => {
    api.getList.then(res => {
        const heights = [...this.state.heights]
        heights.map(item => {
            return {
                index: item.index + res.length,
                height: item.height
            }
        })
        this.setState({
            list: [...res, ...this.state.list],
            heights
        })
    })
}
Copy the code

And then the React-Virtualized mode has a lot of interesting features and the realization of the virtualized mode is also very valuable! And then you can go to React-Virtualized Github