草庐IT

javascript - React 的大列表性能

coder 2023-07-04 原文

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

前提

这是它应该如何工作的描述:

  • 状态驻留在最高级别的组件中,即 Search 组件。
  • 状态描述如下:
{
    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));

如您所知,每次 currentlySelectedIndex 更改时,都会导致重新呈现,并且每次都会重新创建列表。我认为,因为我在每个 li 元素上设置了一个 key 值,React 会避免重新渲染所有其他没有它的 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}
                className={
                    (this.props.selected) ? "valid selected" : "valid"
                }
                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="marked">{match}</span>
            {end}
        </ResultItem>
    );
}.bind(this));
}

这使性能略微更好,但仍然不够好。事情是当我在 React 的生产版本上测试时,一切都非常顺利,没有任何延迟。

底线

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

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

2016 年 11 月 14 日更新

我找到了 Michael Jackson 的这个演讲,他在其中解决了一个与这个非常相似的问题:https://youtu.be/7S8v8jfLb1Q?t=26m2s

该解决方案与 AskarovBeknar 的 answer 提出的解决方案非常相似, 下面

2018 年 4 月 14 日更新

由于这显然是一个热门问题,并且自从提出原始问题以来事情已经取得了进展,同时我鼓励您观看上面链接的视频,为了掌握虚拟布局,我也鼓励您使用React Virtualized图书馆,如果你不想重新发明轮子。

最佳答案

与这个问题的许多其他答案一样,主要问题在于在过滤和处理关键事件的同时在 DOM 中呈现如此多的元素将会很慢。

对于导致问题的 React,您并没有做任何本质上的错误,但与许多与性能相关的问题一样,UI 也可能承担很大一部分责任。

如果您的 UI 设计时没有考虑到效率,即使像 React 这样旨在提高性能的工具也会受到影响。

如@Koen 所述,过滤结果集是一个很好的开始

我试了一下这个想法,并创建了一个示例应用程序来说明我可能会如何着手解决此类问题。

这绝不是 production ready 代码,但它确实充分说明了概念并且可以修改为更健壮,请随时查看代码 - 我希望至少它给了你一些想法......;)

react-large-list-example

关于javascript - React 的大列表性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38033442/

有关javascript - React 的大列表性能的更多相关文章

  1. ruby - RVM 使用列表[0] - 2

    是否有类似“RVMuse1”或“RVMuselist[0]”之类的内容而不是键入整个版本号。在任何时候,我们都会看到一个可能包含5个或更多ruby的列表,我们可以轻松地键入一个数字而不是X.X.X。这也有助于rvmgemset。 最佳答案 这在RVM2.0中是可能的=>https://docs.google.com/document/d/1xW9GeEpLOWPcddDg_hOPvK4oeLxJmU3Q5FiCNT7nTAc/edit?usp=sharing-知道链接的任何人都可以发表评论

  2. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  3. Ruby on Rails regexp equals-tilde 与 array include 用于检查选项列表 - 2

    我正在使用Rails3.2.3和Ruby1.9.3p0。我发现我经常需要确定某个字符串是否出现在选项列表中。看来我可以使用Ruby数组.includemethod:或正则表达式equals-tildematchshorthand用竖线分隔选项:就性能而言,一个比另一个好吗?还有更好的方法吗? 最佳答案 总结:Array#include?包含String元素,在接受和拒绝输入时均胜出,对于您的示例只有三个可接受的值。对于要检查的更大的集合,看起来Set#include?和String元素可能会获胜。如何测试我们应该根据经验对此进行测试

  4. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  5. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  6. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  7. ruby-on-rails - Ruby 长时间运行的进程对队列事件使用react - 2

    我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby​​脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几

  8. Ruby:如何将数组拼接成 Lisp 风格的列表? - 2

    这是我发现自己偶尔想做的事情。假设我有一个参数列表。在Lisp中,我可以像这样`(imaginary-function,@args)为了调用将数组从一个元素转换为正确数量的参数的函数。Ruby中是否有类似的功能?或者我只是在这里使用了一个完全错误的成语? 最佳答案 是的!它被称为splat运算符。a=[1,44]p(*a) 关于Ruby:如何将数组拼接成Lisp风格的列表?,我们在StackOverflow上找到一个类似的问题: https://stackov

  9. ruby-on-rails - Ruby on Rails 将列表拆分或切片为列 - 2

    @locations=Location.all#currentlistingall@locations=Location.slice(5)orLocation.split(5)使用Ruby,我试图将我的列表分成4列,每列限制为5个;然而,切片或拆分似乎都不起作用。知道我可能做错了什么吗?任何帮助是极大的赞赏。 最佳答案 您可能想使用in_groups_of:http://railscasts.com/episodes/28-in-groups-of这是RyanBates在railscast中的示例用法:

  10. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

随机推荐