Frontend Development 12 min read

Optimizing List Rendering Performance in Taro3 Using a Custom Virtual List

This article examines the performance bottlenecks of a Taro3 mini‑program list page after migrating from Taro1, analyzes causes such as excessive node rendering and large setState payloads, evaluates backend pagination and the built‑in virtual list, and presents a custom virtual‑list implementation that reduces rendered nodes, uses screen‑height placeholders, and achieves roughly 45% faster rendering and 50% quicker interaction response.

Ctrip Technology
Ctrip Technology
Ctrip Technology
Optimizing List Rendering Performance in Taro3 Using a Custom Virtual List

After migrating a mini‑program project from Taro 1 to Taro 3, the team observed severe list‑page lag because Taro 3 uses a runtime architecture that renders all data at once, causing a large number of DOM nodes and heavy setState communication.

Root‑cause analysis identified three main problems: (1) an excessive number of page nodes that lengthen rendering time, (2) a massive setState payload that slows communication between the logic and rendering layers, and (3) repeated full re‑rendering when filter items are clicked.

Solution 1 – backend pagination – was quickly dismissed. The list API is shared with the app client, so changing it would require coordinated modifications, and even with pagination the page would still become sluggish after multiple loads.

Solution 2 – the official Taro 3.2.1 virtual list – was tried, but it suffered from variable‑height item calculations, occasional infinite‑load glitches, and long white‑screen periods during fast scrolling, making it unsuitable for the current use case.

Final solution – a custom virtual list – adopts the following strategy:

Only render nodes that are currently visible in the viewport.

Format the incoming flat list into a two‑dimensional array where each sub‑array represents one screen of data.

After a screen is rendered, record its total height and use a simple {height: xx} placeholder for that screen when it leaves the viewport.

Use Taro’s createIntersectionObserver to monitor the visibility of each screen and load or unload data accordingly.

Handle pull‑up loading via ScrollView ’s onScrollToLower to append the next screen’s data.

Key code snippets:

export default class VirtialList extends Component {
  constructor(props) {
    super(props);
    this.state = { twoList: [] };
  }
  componentDidMount() {
    const { list } = this.props;
    this.formatList(list);
  }
  formatList(list) {
    const { segmentNum } = this.props;
    let arr = [];
    const _list = [];
    list.forEach((item, index) => {
      arr.push(item);
      if ((index + 1) % segmentNum === 0) {
        _list.push(arr);
        arr = [];
      }
    });
    const restList = list.slice(_list.length * segmentNum);
    if (restList?.length) _list.push(restList);
    this.initList = _list;
    this.setState({ twoList: _list.slice(0, 1) });
  }
  setHeight() {
    const { wholePageIndex } = this.state;
    const query = Taro.createSelectorQuery();
    query.select(`.wrap_${wholePageIndex}`).boundingClientRect();
    query.exec(res => {
      this.pageHeightArr.push(res?.[0]?.height);
    });
  }
  observe = () => {
    const { wholePageIndex } = this.state;
    const scrollHeight = this.props.scrollViewProps?.style?.height || this.windowHeight;
    const observer = Taro.createIntersectionObserver(this.currentPage.page)
      .relativeToViewport({ top: 2 * scrollHeight, bottom: 2 * scrollHeight });
    observer.observe(`.wrap_${wholePageIndex}`, res => {
      const { twoList } = this.state;
      if (res?.intersectionRatio <= 0) {
        twoList[wholePageIndex] = { height: this.pageHeightArr[wholePageIndex] };
        this.setState({ twoList: [...twoList] });
      } else if (!twoList[wholePageIndex]?.length) {
        twoList[wholePageIndex] = this.initList[wholePageIndex];
        this.setState({ twoList: [...twoList] });
      }
    });
  }
  render() {
    const { twoList } = this.state;
    const { onRender, segmentNum } = this.props;
    return (
{twoList?.map((item, pageIndex) => (
{item?.length > 0 ? (
{item.map((el, index) => onRender?.(el, pageIndex * segmentNum + index, pageIndex))}
) : (
)}
))}
);
  }
}

Performance testing showed that after applying the custom virtual list, total list rendering time improved by nearly 45% and filter‑button response time increased by about 50%. The team plans to extend the same technique to the calendar component for further gains.

For more details, see the GitHub repository, npm package, and Taro material market links provided at the end of the article.

frontendperformance optimizationReactpaginationIntersectionObserverTarovirtual-list
Ctrip Technology
Written by

Ctrip Technology

Official Ctrip Technology account, sharing and discussing growth.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.