Can C++ Deliver Reactive UI Like Web Frameworks? A Deep Dive into Klee
This article explores how the C++‑based Klee framework brings component‑based, data‑driven, and reactive UI development to native mobile and desktop apps, comparing its efficiency and architecture with modern web frameworks and RxSwift, and showcasing practical code examples.
When we mention C++, a language with 38 years of history, terms like "object‑oriented", "procedural", "high performance" and "high complexity" immediately come to mind, and it has long stood out among programming languages.
In our projects we found that implementing the same feature with a Web technology stack can be 2–3 times faster in development effort than native platform development, considering early development, later bug fixing, and UI reconstruction.
The rise of front‑end frameworks such as Angular, React, and Vue, which support componentization and responsive development, has greatly simplified large‑scale Web app creation. In contrast, recent C++ advances have offered little improvement in development workflow; "Modern C++" often adds obscure features that raise the entry barrier.
Overview
Given the following design mockup, how long would it take to implement?
Analyzing this typical list interface:
Controls: Use a TableView layout with avatar, name, status dot, work list, and download button per row. Avatar images are downloaded asynchronously, requiring handling of cell reuse. Status dot color and button text must update in real time with download state.
Layout: Adapt to different screen sizes, placing avatar and button on opposite sides while the remaining space holds the name and work list.
Functionality: Clicking the button triggers a download, updates the status dot and button, and refreshes after success or failure.
Now the solution:
PageRef MusicLibrary::ListPage(Reactive<std::vector<Item>> items) {
return Page("音乐馆",
List(items).cell([](const Item& item)->CellRef{
auto actionText = computed([=]{
switch (item.state) {
case Downloading: return "下载中";
case Downloaded: return "已下载";
default: return "下载";
}
});
auto stateColor = computed([=]{
switch (item.state) {
case Downloading: return 0x4378be_rgb;
case Downloaded: return 0x31c27c_rgb;
default: return 0xaaaaaa_rgb;
}
});
return Cell(
Row().width(FillParent).padding(12).align(Middle).child(
Image(item.avatar).size(48),
Space(12),
Column().flex(1).child(
Label(item.name, 15_pt_B),
Space(4),
Row().align(Middle).interspacing(4).child(
Shape(stateColor).size(6).radius(3),
For(item.works, [](const std::string& work)->WidgetRef{
return Label(work, 12_pt, 0x666666_rgb);
})
)
),
Space(12),
Button(actionText, 14_pt).primary().enabled([=]{ return *item.state == Undownloaded; }).onTap([=]{
item.state = Downloading;
PerformDownload(item, [=](bool success){
item.state = success ? Downloaded : Undownloaded;
});
})
).sepIndent(72);
})
);
}This concise, data‑driven code automatically updates the UI when the underlying data changes.
Data‑driven programming means the program state is determined by data; you manipulate data through provided interfaces, and the framework updates the UI accordingly, eliminating manual UI management. Similar concepts appear in Flutter and SwiftUI.
Reactive Programming
Many wonder how a statically compiled language like C++ can collect runtime dependencies that are usually known only at compile time. The answer lies in the runtime collection of dependencies, as demonstrated in Vue.js.
The core ideas are:
Collect initial dependencies on first execution.
Re‑collect dependencies on each subsequent execution.
If a code path changes, the current dependencies change accordingly; only the dependencies of the executed path need to be tracked.
How Dependencies Are Collected
When a function reads a reactive value, the framework records that dependency. When the reactive data updates, all dependent functions are re‑executed, re‑collecting any new dependencies.
Because C++ lacks dynamic hooks, the Klee framework provides reactive data wrappers that replace ordinary types during development.
Reactive data in Klee is represented by Reactive<T>, which can be read but not directly written.
// Create a read‑only reactive integer
Reactive<int> score = readonly(60);
std::cout << *score; // prints 60Writable reactive variables use Value<T>, which can be implicitly converted to Reactive<T> for read‑only access.
Value<std::string> name;
name = "tibberswang"; // set value
std::cout << *name; // reads "tibberswang"
auto name = reactive("tibberswang"); // shortcutComputed data is created with computed, returning a Reactive<T> that caches its result and automatically tracks dependencies.
auto namelength = computed([=](){
return name->size(); // namelength depends on name
});
std::cout << *namelength; // prints length
name = "tibbers"; // change dependency
std::cout << *namelength; // recomputed lengthKlee also supports asynchronous computed data with available() and state() to manage async status.
auto asyncData = computed([=](Resolver<std::string> r){
DownloadURL(url, [=](int errcode, const std::string& data){
if (!errcode) {
r.resolve(data);
} else {
r.reject(errcode);
}
});
});
auto title = computed([=]{
return asyncData.available() ? "已下载" : "未下载";
});Component‑Based Development
Using the reactive data, you can bind it directly to UI components. For example, binding a display name to a UILabel:
UILabel *label = [UILabel new];
label.font = [UIFont systemFontOfSize:14];
label.textColor = [UIColor redColor];
[label kl_bindText:GetDisplayName(user_id, corp_id)];Klee also offers a declarative API:
Label(14_pt, 0xFF0000_rgb, GetDisplayName(user_id, corp_id));Note: the _pt and _rgb suffixes use C++ user‑defined literals.
Components can be combined to build complex UI with just a few lines of code, leveraging layout components like Row, Column, and Stack, and view components such as Label, Image, Button, and CheckBox.
Comparison with RxSwift
Both Klee and RxSwift are data‑driven native UI frameworks, but they differ in design philosophy. Klee encourages separating Model and ViewModel, making cross‑platform code reuse easier, while RxSwift often uses UI controls as data sources, which can be more concise but harder to share across platforms.
Klee automatically builds dependency graphs, simplifying multi‑input scenarios, whereas RxSwift requires explicit operators to combine multiple streams.
Lifecycle management in Klee ties observers to the view, avoiding manual disposal, while RxSwift relies on DisposeBag and careful self‑capture handling.
Future Outlook
Open‑Source Plans
Klee is currently open‑sourced internally at Tencent and used in the Enterprise WeChat client on iOS, Android, and macOS. Early results show a roughly 40% reduction in code size compared to traditional approaches, with better readability and reusability.
After broader validation and API stabilization, the team plans to release it publicly.
Cross‑Platform Capability
The reactive core is pure C++ and already runs on iOS, macOS, and Android. With minor adjustments it can compile for Windows, and a Java wrapper would enable Android integration.
Component implementations currently exist for iOS and macOS; extending them to Android and Windows would allow most code to be shared across platforms.
Visual UI Builder
Because component‑based development is declarative, it lends itself to a WYSIWYG builder where designers and product managers can assemble interfaces and even generate code directly, reducing reliance on static mockups.
WeChat Client Technology Team
Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.
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.
