具有关联数据和双向绑定的不良 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 性能的主要内容,如果未能解决你的问题,请参考以下文章