具有关联数据和双向绑定的不良 react.js 性能

Posted

技术标签:

【中文标题】具有关联数据和双向绑定的不良 react.js 性能【英文标题】:Bad react.js performance with associated data and two-way-binding 【发布时间】:2015-06-16 16:19:31 【问题描述】:

为了熟悉 react.js,我编写了一个类似于 Facebook 的新闻流,包括用户 cmets。我尝试使用户名可更改,因此我在用户对象和提要组件之间使用了双向绑定。但它的性能不是很好,我不知道这是一个反应问题还是我有一个概念问题。

问题案例:

Here 是 JSFiddle 上的一个简单示例。在页面顶部,您可以更改用户名并响应更新所有名称。

我从自己的 API 获取数据,格式如下:


    "feeds": [
        
            "_id": "feed_1",
            "user": "user_1",
            "text": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
            "comments": [
                
                    "_id": "comment_1",
                    "user": "user_2",
                    "content": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."
                ,
                ...
            ]
        ,
        ...
    ],
    "users": [
        
            "_id": "user_1",
            "name": "Allan Moreno"
        ,
        
            "_id": "user_2",
            "name": "Edward Mendez"
        
        ...
    ]

所以我有一个包含提要的数组和一个包含用户的数组。我遍历提要以生成反应提要组件。提要和 cmets 从我作为 react 属性传递的 getUserById 函数获取用户。

var StreamContainer = React.createClass(
    getUserById: function(userId) 
        for(var i=0; i<this.state.users.length; i++) 
            if(this.state.users[i]._id === userId) 
                return this.state.users[i];
            
        
    ,

    getInitialState: function() 
        return 
            "feeds": [...],
            "users": [...]
        ;
    ,

    render: function() 
        var getUserById = this.getUserById;

        return (
            <div className="stream-container">
                <FeedList getUserById=getUserById feeds=this.state.feeds />
            </div>
        );
    
);

var FeedList = React.createClass(
    render: function() 
        var getUserById = this.props.getUserById;

        var feedNodes = this.props.feeds.map(function(feed) 
            return [<Feed key=feed._id feed=feed getUserById=getUserById />];
        );

        return (
            <div>
                <strong>Feeds:</strong>
                <ol className="stream-items">
                    feedNodes
                </ol>
            </div>
        );
    
);

var Feed = React.createClass(
    render: function() 
        var getUserById = this.props.getUserById;

        return (
            <li className="stream-item">
                <strong>getUserById(this.props.feed.user).name:</strong> <em>this.props.feed.text</em>
                <FeedCommentList comments=this.props.feed.comments getUserById=getUserById />
            </li>
        );
    
);

React.render(
    <StreamContainer />,
    document.getElementsByTagName('body')[0]
);

现在我已经尝试让它在一些性能测试中具有一点交互性。我添加了所有用户的列表以动态更改他们的名称。对于双向绑定,我使用ReactLink。

var StreamContainer = React.createClass(
    mixins: [React.addons.LinkedStateMixin],

    ...

    render: function() 
        ...

        var valueLink = this.linkState('users');
        var handleChange = function(e) 
            valueLink.requestChange(e.target.value);
        ;

        return (
            <div className="stream-container">
                <UserList users=this.state.users valueLink=valueLink />
                <FeedList getUserById=getUserById feeds=this.state.feeds />
            </div>
        );
    
);

var UserList = React.createClass(
    render: function() 
        var valueLink = this.props.valueLink;

        var users = this.props.users.map(function(user, index) 
            var thisValueLink = ;

            var handleChange = function(newValue) 
                valueLink.value[index].name = newValue;
                valueLink.requestChange(valueLink.value);
            ;

            thisValueLink.requestChange = handleChange;
            thisValueLink.value = valueLink.value[index].name;

            return [<User key=user._id user=user valueLink=thisValueLink />];
        );

        return (
            <div>
                <strong>Users:</strong>
                <ul>
                    users
                </ul>
            </div>
        );
    
);

var User = React.createClass(
    render: function() 
        return (
            <li>                            
                <input type="text" valueLink=this.props.valueLink /> this.props.user.name 
            </li>
        );
    
);

但现在我遇到了性能问题。如果我更改用户的名称,则必须检查所有名称以进行更新。因此,如果我有 100 个提要,您可以看到输入和更新之间的延迟。

有没有更好的方法将用户传递给子组件?或者也许是一种更高效的方式来处理双向绑定?

【问题讨论】:

getUserById 需要在每次调用时遍历每个用户(意思是,为每个用户渲染所有用户遍历每个用户)可能很慢。您最好创建一个由用户 ID 键入的用户对象的对象。因此,您只需遍历每个用户一次。那么 getUserById 就是return users[id] 感谢您的回答。好点子。我已经测试了一个带有键控对象的版本。问题在于,我无法使用 this.props.users.map() 迭代对象。如果我使用 React.Children.map(this.props.users, function(user, index) ... ) 对象将像users[0] = "user_1"; users[1] = "Allan Moreno"; users[2] = "user_2"; users[3] = "Edward Mendez"; ... 一样扁平化 您尝试过浏览器分析器吗?我会从那里开始。 @Johann:我并不是说您的 JSON 数据必须是一个对象。只是 getUserById 函数应该缓存作为键控对象的数据版本(在闭包中或作为 StreamContainer 的私有成员) @slebetman:好的,这是有道理的。我认为这会快一点。但在我看来,我的 react.js 代码仍然存在两个性能问题:#1 所有用户都必须调用getUserById()。我不知道是否有另一种可能性将用户直接作为 props 传递给提要,而不是 getUserById 函数。 #2 双向绑定:每次更改时,react 都会调用this.setState(users: newUsers) 来更新整个用户对象。有没有办法只更新一个属性,比如this.setState(users[2].name: newUserName) 【参考方案1】:

您可以在 StreamContainer 渲染中创建一个用户名字典对象,以传递给需要查找用户名的孩子:

var userNames = ;
this.state.users.forEach(function(user) 
    userNames[user._id] = user.name;
);

<FeedList usernames=userNames feeds=this.state.feeds />

那么它只是一个简单的键查找,例如:

var Feed = React.createClass(
    render: function() 
        return (
            <li className="stream-item">
                <strong>this.props.usernames[this.props.feed.user]:</strong> <em>this.props.feed.text</em>
                <FeedCommentList comments=this.props.feed.comments usernames=this.props.usernames />
            </li>
        );
    
);



var FeedCommentList = React.createClass(
    render: function() 
        var _this = this;
        var commentNodes = this.props.comments.map(function(comment) 
            return [
                <FeedComment 
                    key=comment._id 
                    username=_this.props.usernames[comment.user] 
                    comment=comment />
            ];
        );

...

updated jsfiddle

【讨论】:

这看起来像是一种处理键控用户对象的优雅方式。我已将其包含在我的新闻流中,但它仍然很慢。 This react example 有 100 个提要(500 个用户观察者),您可以看到很大的输入延迟。也许 react.js 观察者不是那么快,但在 AngularJS 中,我可以处理 2000 个观察者而不会有很大的延迟。

以上是关于具有关联数据和双向绑定的不良 react.js 性能的主要内容,如果未能解决你的问题,请参考以下文章

Vue.js与React的全面对比

夺命雷公狗-----React---8--react官方提供的组建实现双向绑定

两种方式数据绑定和反应性有啥区别?

Svelte:双向绑定触发反应性两次

关于什么是MVVM?几种双向数据绑定的方式

如何初始化片段中的绑定属性以使双向数据绑定工作