Can a React-Redux app really scale, as well as, say, Backbone? Even with a second choice. On mobile devices

In Redux, every change to the repository launches notify for all connected components. This makes things very easy for the developer, but what if you have an application with N related components and N is very large?

Each change in the repository, even if it is not associated with the component, still runs shouldComponentUpdate with a simple test === in the reselect ed path of the repository. It's fast, right? Of course, maybe once. But N times, for every change? This fundamental change in design makes me question the true scalability of Redux.

As an additional optimization, you can make all notify calls using _.debounce . However, testing N === for each store change and processing other logic, such as presentation logic, seems like a means to complete.

I’m working on mobile and mobile apps for mobile and mobile apps with millions of users and moving from Backbone to Redux . In this application, the user is provided with a swipeable interface that allows them to navigate between different presentation stacks, like Snapchat, except that each stack has infinite depth. In the most popular type of view, the endless scroller efficiently handles loading, rendering, attaching, and disconnecting feed items, such as a message. For an involved user, hundreds or thousands of messages are often scrolled, then a user feed, another user channel, etc. are entered. Even with great optimization, the number of connected components can be very large.

Now, on the other hand, the Backbone design allows each species to accurately listen to models that influence it, reducing N to a constant.

Am I missing something, or is Redux fundamentally messed up for a large application?

+48
performance mobile reactjs redux
Jan 14 '16 at 5:22
source share
2 answers

This is not a problem inherent in Redux IMHO.

By the way, instead of trying to display 100k components at the same time, you should try to fake it with lib, for example react-infinite or something similar, and display only the visible (or close to it) elements of your list. Even if you manage to display and update the list of 100 thousand, it still does not work and requires a lot of memory. Here are a few LinkedIn Recommendations

This anwser will consider that you are still trying to display 100k updatable items in your DOM and that you do not want 100k listeners ( store.subscribe() ) to be called on every change.




2 schools

When developing a user interface application in a functional way, you basically have 2 options:

Always output from the highest level

It works well, but includes more templates. This is not quite a suggested Redux method, but achievable, with some drawbacks . Note that even if you manage to connect to the same reduction connection, you still have to name a lot of shouldComponentUpdate in many places. If you have an infinite stack of views (e.g. recursion), you will have to display all intermediate views as a virtual dom, and shouldComponentUpdate will be called on many of them. Thus, it is not very effective, even if you have one connection.

If you do not plan to use the React life cycle methods, but use only pure rendering functions, then you should probably consider other similar parameters that will focus only on this task, for example deku (which can be used with Redux)

In my own experience, this with React is not effective enough for older mobile devices (e.g. Nexus4), especially if you associate text inputs with your atom state.

Connecting data to child components

This is what react-redux offers using connect . Therefore, when the state changes, and it is associated only with a deeper child, you only visualize this child and do not have to display top-level components every time, as context providers (abbreviation / intl / custom ...), as well as the main application layout. You also do not call shouldComponentUpdate for other children, because it is already baked in the listener. Calling a lot of very fast listeners is probably faster than rendering every time the intermediate components respond, and it also reduces the number of templates that go through the props, so for me it makes sense when used with React.

Also note that identity comparisons are very fast, and you can easily perform them with every change. Remember Angular dirty check: some people managed to create real applications with this! And identity comparisons are much faster.




Understanding your problem

I'm not sure I understand your whole problem, but I understand that you have views with similar elements of 100,000, and you are wondering if you should use connect with all of these 100k elements, because calling 100k listeners with each change seems expensive .

This problem seems intrinsic to the execution of functional programming with the user interface: the list has been updated, so you need to re-display the list, but unfortunately it is a very long list and seems inefficient ... With Backbone, you could hack something so that only carry the child. Even if you bring this child out with React, you should invoke the rendering in an imperative order, rather than just declaring "when the list changes, redisplay it."




Solution to your problem

Obviously, connecting the elements of the 100k list seems convenient, but it is not indicative due to the call of the 100k response listeners, even if they are fast.

Now, if you connect a large list of 100 thousand elements instead of separate elements separately, you call only one rec-redux listener, and then you should effectively display this list.




Naive decision

Iterating over 100k elements to render them, which leads to 99999 elements returning false in shouldComponentUpdate and one re-rendering:

 list.map(item => this.renderItem(item)) 



Artist Solution 1: custom connect + storage amplifier

The React-Redux connect method is simply a Higher Order Component (HOC) that enters data into the wrapped component. To do this, it registers the store.subscribe(...) listener for each connected component.

If you want to connect 100k elements of one list, this is the critical path of your application that is worth optimizing. Instead of using the default connect , you can create your own.

  • Store Improver

Set an additional method store.subscribeItem(itemId,listener)

Wrap dispatch so that whenever an action associated with an element is dispatched, you call the registered listener (s) of that element.

A good source of inspiration for this implementation might be redux-batched-subscribe .

  1. User connection

Create a higher order component using the API, for example:

 Item = connectItem(Item) 

HOC may expect itemId property. It can use Redux extended storage from a React context, and then register its listener: store.subscribeItem(itemId,callback) . The source code for the original connect can serve as a basic inspiration.

  1. HOC will only lead to re-rendering if the item changes

Related answer: stack overflow

Reduction related issue: https://github.com/rackt/react-redux/issues/269




Implementing Solution 2: Vector Attempts

A more efficient approach involves using a constant data structure, such as vector trie :

Trie

If you present the list of your 100 thousand positions as three, each intermediate node has the ability to short-cut the rendering, which avoids the large number of shouldComponentUpdate in the children.

This method can be used with ImmutableJS , and you can find some experiments that I did with ImmutableJS: Real performance: rendering a large list using PureRenderMixin However, this has drawbacks, since libs like ImmutableJs do not yet provide public / stable APIs for of this ( issue ), and my solution pollutes the DOM with some useless intermediate <span> nodes ( issue ).

Here is a JsFiddle that demonstrates how to efficiently render a list of objects from 100,000 ImmutableJS objects. The initial rendering is quite long (but I think you are not initializing your application with 100 thousand Elements!), But after you notice that each update leads to a small amount of shouldComponentUpdate . In my example, I only update the first element every second, and you notice, even if there are 100 thousand Elements in the list, this only requires 110 calls to shouldComponentUpdate , which is much more acceptable! :)

Edit : It seems that ImmutableJS is not so good as to maintain its immutable structure for some operations, such as inserting / deleting elements in a random index. Here is a JsFiddle that demonstrates the performance you can expect according to the operation on the list. Surprisingly, if you want to add many elements to the end of a large list, calling list.push(value) over and over again seems to preserve much more tree structure than calling list.concat(values) .

By the way, it is documented that List is effective at changing edges. I don’t think that these bad indicators when adding / removing at a given index are related to my technique, but rather related to the main implementation of the ImmutableJs list.

Lists implement Deque with effective addition and removal from the end (push, pop) and begin (unshift, shift).

+81
Jan 14 '16 at 11:33
source share

This may be a more general answer than you are looking for, but overall:

  • The recommendation from the Redux docs is to associate React components with a fairly high level of component hierarchy. See this section. . This allows you to manage the number of connections, and you can simply transfer the updated details to the child components.
  • Part of the power and scalability of React is to prevent rendering of invisible components. For example, instead of setting the invisible class in a DOM element, in React we simply do not display the component at all. Retransmission of components that have not changed is also not a problem, since the virtual DOM process optimizes low-level DOM interactions.
+5
Jan 14 '16 at 6:38
source share



All Articles