使用React的大列表性能 [英] Big list performance with React

查看:89
本文介绍了使用React的大列表性能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用React实现可过滤列表。列表的结构如下图所示。





PREMISE



以下是它应该如何工作的说明:




  • 州位于最高级别的组件搜索组件中。

  • 状态为描述如下:



 
{
visible:boolean,
files:array ,
过滤:数组,
查询:字符串,
currentSelectedIndex:整数
}




  • files 是一个可能非常大的数组,包含文件路径(10000个条目是合理的数字)。

  • filtered 是用户输入至少2个字符后的过滤数组。我知道它是衍生数据,因此可以将其存储在状态中,但是需要

  • currentSelectedIndex 这是筛选列表中当前所选元素的索引。


  • 用户在输入<中键入2个以上的字母/ code>组件,数组被过滤,对于过滤后的数组中的每个条目,呈现结果组件


  • 每个结果组件显示部分匹配查询的完整路径,并突出显示路径的部分匹配部分。例如,Result组件的DOM,如果用户键入'le',则类似于:



    < li> this /是/ a / fi< strong> le< / strong> / path< / li>


  • 如果用户按下向上或向下键而输入组件的重点是 currentSelectedIndex 根据过滤数组。这会导致与索引匹配的 Result 组件被标记为已选中,从而导致重新渲染



问题



最初我用一个足够小的文件数组测试了这个,使用React的开发版本,一切正常。



当我不得不处理一个大到10000个条目的文件数组时出现问题。在输入中键入2个字母会产生一个大的列表,当我按下向上和向下键进行导航时,它会非常滞后。



起初我没有 Result 元素的已定义组件,我只是在搜索的每个渲染上动态制作列表组件,如下:

  results = this.state.filtered.map(function(file,index){
var start,end,matchIndex,match = this.state.query;

matchIndex = file.indexOf(match);
start = file.slice(0,matchIndex);
end = file.slice(matchIndex + match.length);

return(
< li onClick = {this.handleListClick}
data-path = {file}
className = {(index === this.state.currentlySelected)?有效选择:有效}
key = {file}>
{start}
< ; span className =marked> {match}< / span>
{end}
< / li>
);
} .bind(this));

正如你所知,每次 currentSelectedIndex 更改,它会导致重新渲染,每次都会重新创建列表。我认为,因为我在每个 li 元素上设置了一个 key 值,React会避免每次重新呈现 c c $ c> li 没有 className 更改的元素,但显然不是这样。



我最后为 Result 元素定义了一个类,它明确检查每个 Result 元素应根据先前是否已选择并基于当前用户输入重新呈现:

  var ResultItem = React。 createClass({
shouldComponentUpdate:function(nextProps){
if(nextProps.match!== this.props.match){
return true;
} else {
return(nextProps.selected!== this.props.selected);
}
},
render:function(){
return(
< li onClick = {this.props.handleListClick}
data-path = {this.props.file}
c lassName = {
(this.props.selected)? 有效选择:有效
}
key = {this.props.file}>
{this.props.children}
< / li>
);
}
});

此列表现在是这样创建的:

  results = this.state.filtered.map(function(file,index){
var start,end,matchIndex,match = this.state.query,selected;

matchIndex = file.indexOf(match);
start = file.slice(0,matchIndex);
end = file.slice(matchIndex + match.length);
selected =(index === this.state.currentlySelected)?true:false

return(
< ResultItem handleClick = {this.handleListClick}
data- path = {file}
selected = {selected}
key = {file}
match = {match}>
{start}
< span className = 已标记> {match}< / span>
{end}
< / ResultItem>
);
} .bind(this));
}

这使得性能略微更好,但它仍然是还不够好。事情是,当我在React的生产版本上测试时,工作黄油顺利,没有任何延迟。



BOTTOMLINE



开发和生产之间是否存在明显的差异React版本正常吗?



当我考虑React如何管理列表时,我是否理解/做错了什么?



更新2016年11月14日



我找到了Michael Jackson的演示文稿,在那里他解决了与此问题非常相似的问题:


I am in the process of implementing a filterable list with React. The structure of the list is as shown in the image below.

PREMISE

Here's a description of how it is supposed to work:

  • The state resides in the highest level component, the Search component.
  • The state is described as follows:

{
    visible : boolean,
    files : array,
    filtered : array,
    query : string,
    currentlySelectedIndex : integer
}

  • files is a potentially very large, array containing file paths (10000 entries is a plausible number).
  • filtered is the filtered array after the user types at least 2 characters. I know it's derivative data and as such an argument could be made about storing it in the state but it is needed for
  • currentlySelectedIndex which is the index of the currently selected element from the filtered list.

  • User types more than 2 letters into the Input component, the array is filtered and for each entry in the filtered array a Result component is rendered

  • Each Result component is displaying the full path that partially matched the query, and the partial match part of the path is highlighted. For example the DOM of a Result component, if the user had typed 'le' would be something like this :

    <li>this/is/a/fi<strong>le</strong>/path</li>

  • If the user presses the up or down keys while the Input component is focused the currentlySelectedIndex changes based on the filtered array. This causes the Result component that matches the index to be marked as selected causing a re-render

PROBLEM

Initially I tested this with a small enough array of files, using the development version of React, and all worked fine.

The problem appeared when I had to deal with a files array as big as 10000 entries. Typing 2 letters in the Input would generate a big list and when I pressed the up and down keys to navigate it it would be very laggy.

At first I did not have a defined component for the Result elements and I was merely making the list on the fly, on each render of the Search component, as such:

results  = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query;

     matchIndex = file.indexOf(match);
     start = file.slice(0, matchIndex);
     end = file.slice(matchIndex + match.length);

     return (
         <li onClick={this.handleListClick}
             data-path={file}
             className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
             key={file} >
             {start}
             <span className="marked">{match}</span>
             {end}
         </li>
     );
}.bind(this));

As you can tell, every time the currentlySelectedIndex changed, it would cause a re-render and the list would be re-created each time. I thought that since I had set a key value on each li element React would avoid re-rendering every other li element that did not have its className change, but apparently it wasn't so.

I ended up defining a class for the Result elements, where it explicitly checks whether each Result element should re-render based on whether it was previously selected and based on the current user input :

var ResultItem = React.createClass({
    shouldComponentUpdate : function(nextProps) {
        if (nextProps.match !== this.props.match) {
            return true;
        } else {
            return (nextProps.selected !== this.props.selected);
        }
    },
    render : function() {
        return (
            <li onClick={this.props.handleListClick}
                data-path={this.props.file}
                className={
                    (this.props.selected) ? "valid selected" : "valid"
                }
                key={this.props.file} >
                {this.props.children}
            </li>
        );
    }
});

And the list is now created as such:

results = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query, selected;

    matchIndex = file.indexOf(match);
    start = file.slice(0, matchIndex);
    end = file.slice(matchIndex + match.length);
    selected = (index === this.state.currentlySelected) ? true : false

    return (
        <ResultItem handleClick={this.handleListClick}
            data-path={file}
            selected={selected}
            key={file}
            match={match} >
            {start}
            <span className="marked">{match}</span>
            {end}
        </ResultItem>
    );
}.bind(this));
}

This made performance slightly better, but it's still not good enough. Thing is when I tested on the production version of React things worked buttery smooth, no lag at all.

BOTTOMLINE

Is such a noticeable discrepancy between development and production versions of React normal?

Am I understanding/doing something wrong when I think about how React manages the list?

UPDATE 14-11-2016

I have found this presentation of Michael Jackson, where he tackles an issue very similar to this one: https://youtu.be/7S8v8jfLb1Q?t=26m2s

The solution is very similar to the one proposed by AskarovBeknar's answer, below

UPDATE 14-4-2018

Since this is apparently a popular question and things have progressed since the original question was asked, while I do encourage you to watch the video linked above, in order to get a grasp of a virtual layout, I also encourage you to use the React Virtualized library if you do not want to re-invent the wheel.

解决方案

As with many of the other answers to this question the main problem lies in the fact that rendering so many elements in the DOM whilst doing filtering and handling key events is going to be slow.

You are not doing anything inherently wrong with regards to React that is causing the issue but like many of the issues that are performance related the UI can also take a big percentage of the blame.

If your UI is not designed with efficiency in mind even tools like React that are designed to be performant will suffer.

Filtering the result set is a great start as mentioned by @Koen

I've played around with the idea a bit and created an example app illustrating how I might start to tackle this kind of problem.

This is by no means production ready code but it does illustrate the concept adequately and can be modified to be more robust, feel free to take a look at the code - I hope at the very least it gives you some ideas...;)

https://github.com/deowk/react-large-list-example

这篇关于使用React的大列表性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆