How Tubes Uses a Reactive Data System to Optimize Multi‑Screen Rendering
The article explains how Tubes employs a reactive data system to handle multi‑screen rendering, compares loop‑based and reactive approaches, details classic and hash‑table implementations, and demonstrates significant performance gains, offering valuable insights for frontend developers seeking efficient rendering architectures.
This article details the use of a reactive data system in Tubes, a terminal rendering solution for C‑end scenarios, and explains three different designs and principles of the reactive data system.
Tubes is a terminal rendering solution for C‑end scenarios, supporting flexible expansion, extreme performance, and high stability, widely used in Alibaba platforms such as Double 11, 618 events, and the mobile Taobao homepage.
1. Introduction
A reactive data system means that a program automatically subscribes to the data it uses; when the subscribed data changes, the program responds to the change.
Reactivity is Vue.js's most distinctive feature (formerly called reactive, Vue 3 translates it as reactivity), but Tubes's reactivity principle differs slightly from Vue.js.
2. Role of Reactive Data System in Tubes
Tubes needs a reactive data system to solve the screen‑split rendering problem, i.e., the design issue of multiple Tube executions.
If you are unfamiliar with Tubes, think of it as solving the multiple‑execution problem of Express.js middleware or Webpack loaders.
Split‑screen rendering means that the page first renders first‑screen modules, then second‑screen modules. In Tubes, executing from the first Tube to the last completes one render, similar to the Express middleware chain.
To render the second screen, the Tube execution loop needs to run a second round. Two design schemes were created:
2.1 Scheme 1: Loop‑based implementation
Based on a loop system, Tubes‑Engine provides properties and methods (e.g., current loop count, once) so developers can declare whether a Tube needs to run only once or multiple times.
The issues are that not all Tubes need multiple executions, developers must explicitly declare once, handle loop logic, and deeply understand the loop mechanism.
Only Tubes closely related to first‑screen/second‑screen rendering need multiple executions; other Tubes must explicitly declare they run only once.
Pros: Simple internal implementation, less error‑prone.
Cons:
Need to use once for single‑execution Tubes.
Multiple‑execution Tubes must manage loop count and notify the engine.
Developers must deeply understand the loop mechanism.
Each Tube must handle loop‑related logic.
This scheme is straightforward but imposes high usage cost on developers.
2.2 Scheme 2: Reactive data system implementation
A Tube is an idempotent execution unit: its output becomes the next Tube's input, forming a pipeline.
Tube is an execution unit for rendering; Tubes' design uses simple units to progressively compute results rather than a complex process.
Idempotency means identical input yields identical output; only when input changes does the Tube need to re‑execute.
The key idea is to decide which Tube to re‑execute based on input/output changes, using a reactive data system.
Only when the data a Tube uses changes does the Tube re‑execute in order; if multiple Tubes share the same data, they execute sequentially.
For example, after rendering the first screen, a data Tube requests second‑screen data and stores it; this update triggers the dependent Tubes to re‑execute, forming a dependency‑based execution chain.
Advantages and disadvantages:
Pros:
No extra burden on Tubes (no new methods or attributes).
Tubes are unaware of multiple executions in first‑screen/second‑screen scenarios.
Only Tubes that need to run are executed.
Cons: Complex internal implementation, prone to errors.
Based on these benefits, the reactive data system was chosen.
3. Design and Principles of the Reactive Data System
Historically, three versions were designed; this section focuses on the second and third versions.
3.1 Classic implementation
The three core aspects are reactive data, dependency collection, and dependency triggering.
3.1.1 Reactive Data
The two core components are listeners and dependency storage.
3.1.1.1 Listeners
Common interception methods are Getter/Setter, Proxy, and custom Set/Get API. Tubes uses the custom API to let developers modify raw data without triggering reactivity until a later point.
const data = this.store.get('data'); // bind dependency here
const name = data.name; // this read should not trigger Getter
data.name = name + '!'; // this set should not trigger Setter
this.store.set('data', data); // trigger reactivity here3.1.1.2 Dependency storage
Dependencies are stored in a __dep__ property attached to the data.
3.1.2 Collecting Dependencies
When to collect: during data reads. Who: the subscribers, which are Tubes (or groups of parallel Tubes). How: example code shows how the current Tube and its group are recorded when ctx.store.get is called.
function get(keypath) {
this.ctx.tb.store.effect = effect;
this.ctx.tb.store.tube = tube;
const res = this.ctx.tb.store.get(keypath);
this.ctx.tb.store.tube = null;
this.ctx.tb.store.effect = null;
return res;
}Where: dependencies are stored on the data itself, as well as on all parent and child nodes to ensure that changes propagate correctly.
In Tubes, the subscriber is actually a group of parallel Tubes; if only one Tube in the group uses a piece of data, the group contains that single Tube, otherwise it contains multiple Tubes.
3.1.3 Reading Dependencies
Dependencies are triggered when data is modified; the found dependencies are sent to the scheduler for execution.
3.2 Hash‑table based Reactive Data System
This approach stores dependencies in a hash table, improving speed because operations no longer depend on data size.
3.2.1 Why Use a Hash Table
Using a hash table decouples dependency operations from data size, avoiding costly traversal of all child nodes on large data structures.
3.2.2 Storing Dependencies
The keypath serves as the hash key, and the value is a list of dependencies with deduplication.
3.2.3 Reading Dependencies
When a key changes, dependencies for the key, its parents, and its children must be retrieved from the hash table and deduplicated before execution.
3.2.4 Performance Improvement
On low‑end Android devices, read speed improved by 1,349.8× (from 64.79 ms to 0.048 ms) and first‑screen rendering time decreased by 452.6 ms. Detailed benchmark data:
Read speed: 1,349.8× faster (64.79 ms → 0.048 ms).
Write speed: unchanged.
Time complexity: Old algorithm O(N), new algorithm O(1).
Impact on first‑screen speed: improved by 452.6 ms (old 2,924.7 ms → new 1,917.5 ms).
Test environment: low‑end device (Redmi 7, Android 9) on the activity center page.
3.2.5 Summary
The hash‑table approach differs from the classic implementation only in how dependencies are stored, leading to more efficient algorithms.
4. Conclusion
This article introduced the application of a reactive data system in Tubes, described three design versions, and noted that while the project is internal to Alibaba, the concepts are valuable for developers to study and apply.
References
[1] Activity Center page: https://pages.tmall.com/wow/a/act/tmall/dailygroup/2035/wupr?wh_pid=daily-211925
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
