When it comes to C++, a 38-year-old language, everyone knows more or less about it,"Object-orientedProcedural programmingThese words immediately pop into my mind. “High performanceHigh complexity"These two labels, along with C++, have been unique among many languages for many years.
And we found in the development process of the actual project, the same function, comprehensive considerationPre-developmentLate bugswithUI restorationand other stages of manpower input, useWeb technology stackTo achieve front-end pages, the R&D efficiency is aboutNative development of the platformof2 to 3 times。The difference in development efficiency makes us curious to go deeper into the reasons.
The three major front-end frameworks that have risen in recent years, Angular, React, and Vue, are supportedComponentizationwithResponsiveDevelopment, which brings a rich ecosystem to the front-end, greatly simplifies the process of web development, and makes it easy to develop large web applications. On the other hand, C++ has made very few improvements in recent yearsDevelopment process and philosophyThe so-called Modern C++ has only added a lot of obscure content in the eyes of many, and has raised the bar for development with little interest.
You may have also been exposed to and learned about the front-endComponentizationwithResponsiveI develop, but whether I want to be able to do it somedayImplemented in C++
Give the following design draft, try to roughly assess, how much time can be done?
*Pavilion design draft.
Don't rush to see the answer, let's analyze this typical list interface:
Widgets: Requiredtableviewmethod layout, each row has an avatar, name, status dots, a list of works and a button. Avatars use URLsAsynchronous**, consider potential cell reuse issues. The color of the status dot, the text of the button, and the disabled state should follow the status of the taskReal-time updates。Layout: YesAdaptable to different sizesThe screen, avatar and buttons are separated from the left and rightRemaining spaceLeave a list of names and works. Functional: Clicking on the button will make**The status has been transferred, perform the ** operation andUpdatedot and button, and trigger again after a failed finishUpdate。Do you have a number in your heart, the following answer is revealed:
pageref musiclibrary::listpage(reactive> items)
auto statecolor = computed([=]
return cell(
row().width(fillparent).padding(12).align(middle).child(
image(item.**atar).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),space(12),button(actiontext, 14_pt).primary().enabled([=]).ontap([=]);
sepindent(72);
This shouldn't be the most familiar style you're familiar with, but if you've used a reactive development framework, it's not too unfamiliar. It only took dozens of lines to complete the development of such an interface, and it has itReal-time updatesIsn't it fragrant?
* It's all a bit of a data-driven credit for being so concise. The framework can intelligently track and establish the relationship between the data and the interfaceData changesThe interface is updated without the need for developers to manually manage it.
Digest it first, and then look at the next onesSmall surprisesbar. You don't need to change a line**, you can get the same macOS native version as a gift, buy one get one free. :-
*Pavilion macOS version.
To put it simply, data-driven is a programming idea, programmaticThe status is determined by the data, through the interface providedOperational datato controlProgram logic, and it is not recommended to manipulate UI components directly. In addition to the web technology stack, data-driven shadows can be found on the popular client-side development frameworks such as Flutter and SwiftUI.
Developers only need to use ** or other meansDescribe the relationship between individual interface elements and dataThe flow of data and the maintenance of the interface will be automatically handled by the framework, greatly simplifying what programmers need to pay attention to.
A lot of people don't understand the principle of reactive implementation, and I used to think that C++ as a statically compiled language couldn't be usedRunning periodCollected, it should have beenCompilation periodto be informedDependencies。After all, conditional branches that are not executed do not exist at runtime.
Until I read vueJS source code, I understand how dependencies are inRuntime collection maintenanceof
There are two core points:
Initialization is performed once and collectedInitialDependencies are performed every timeRecollectOne of the overlooked aspects of dependencies here is that if ** will be executed to another branch, it will have to beCurrent dependencieswill change. Therefore, it is not necessary to collect the complete dependencies at once, just make sure to collect the dependencies of the current path.
It's as simple as when a function is triedReadWhen a reactive data is filed, it is recorded that the function has a dependency on that data. Responsive data has:Update, traverse all of its dependent functions, re-execute, and thenCollect againNew dependencies.
Since C++ is a compiled language, it's difficult to do dynamic hook proxies for data like Vue, which Klee provides directlyReactive data encapsulation, which is used to replace common data types during the development phase.
Responsive dataUse types in the Klee frameworkreactive
Representation, it is allowed to be dependent, only the read interface is exposed, and the polymorphic implementation is adopted internally.
create a read-only reactive data with a constant reactive score = readonly(60); std::cout name = "tibberswang";Set the value.
std::cout can also be used in this way, creating a reactive variable that specifies an initial value.
Note: The reactive method has a special overload of const char*, and the output type is std::string
auto /* value */ name = reactive("tibberswang");
Calculate the dataPassedcomputed
The method is generated, and the return type remains externalreactive
, the content of which is computed by a lambda (C++) or block (Objective-C), and the result of the calculation will be usedCaching。Using reactive data in the body of the function that calculates the data, willAutomatically establish dependencies, if a dependency changes, the computed property will be marked as dirty and set in theIt is used next timeorThe next message loopTrigger a recalculation.
auto /* reactive */ namelength = computed([=]()else
auto title = computed([=]
if (user.**ailable())else if (conv.**ailable() hasnickname(conv, user)) else if (config.**ailable() config->preferschinesename) else
std::string corpname;
if (corp.**ailable())else
else return combinename(username, corpname);
return i18n(lang, unknownsendername);
As you can see, ** is very clear and concise, and it has itCachingLazy loadingStabilization and deduplicationRequest aggregationand other optimization strategies, often better than handwriting**Better performanceAfter reading the previous example, do you feel that something is missing? Yes, the above function ended up returning only onereactive
, used in **?
That's what we're going to talk about in this sectionComponent-based developmentFinish. Of course, if you only want to use reactive programming for development, you can:
uilabel *label = [uilabel new];
label.font = [uifont systemfontofsize:14];
label.textcolor = [uicolor redcolor];
label kl_bindtext:getdisplayname(user_id, corp_id)];
foruilabel
Classification methods providedkl_bindtext:
The role isData bindingIt's here. callkl_bindtext:
, if the reactive data changes, the framework will display theBefore the next drawingRe-evaluate the reactive data and then invokesettext:
Approach changeslabel
and triggers the view tree'sRelayoutThere are still four lines **, which is still a bit cumbersome. Klee deliveredDeclarativeThe development model can be written like this:
label(14_pt, 0xff0000_rgb, getdisplayname(user_id, corp_id));
Note: The PT and RGB suffixes above are custom literals implemented using the user-defined literals feature of C++.label is a built-in text display component provided by the klee framework, and the parameters at the time of construction support passing in strings, attribute strings, fonts, and colors at the same time, and the parameters are allowedAdd or subtract or swap the order at will, e.g. this is also ok:
label(getdisplayname(user_id, corp_id), 17_pt);
These parameters are responsive, if neededModify colors dynamically, then the parameter passes in a reactive data that represents the color
auto vipcolor = computed([=] else
label(getdisplayname(user_id, corp_id),vipcolor);
As part of the performance optimization, if the label only changes color, the frame does not consider it necessary to recalculate the size of the labelWon't triggerThe view tree is re-arranged.
useComponent-based developmentto complete the writing of the entire cell.
Wechat message bubbles.
widgetref messagecell(msg) {
auto sendername = getdisplayname(msg.senderid, msg.convid);
return row().padding(16).child(
*atar(msg.senderid).size(40, 40),space(8),column().child(
label(14_pt, 0x0_rgb, sendername).padding(0, 8, 0, 0),bubble(msg).padding(4, 0, 0, 0)
You taste, you taste. Just a few lines**, take advantage of a variety ofA combination of foundational componentsto complete the configuration and layout of various complex interface functions. There is no inheritance, no method override, and no listeners and observers. Based onflexboxThe layout model can be on its ownAdapts to a wide range of screen widthsIn order to be able toProgressiveBy integrating klee into a project, klee can be integrated with existing native development patternsMix and matchuse, and does not require a complete renovation of the project.
The view component provided by klee allows for implicit conversion to native views, directly participating in the development of the original native pattern.
uilabel * label = label(name, 17_pt); // ios
nstextfield *label = label(name, 17_pt); // macos
Contains layout componentswidgetref
Objects can be implicitly converted toklwidgetview
orklwidgetscrollview
to participate in the development of the original native model.
The difference between klwidgetview and klwidgetscrollview is whether or not it supports scrolling.
klwidgetview *view = messagecell(msg);
self.view addsubview:view];
The project is based onuiview
ornsview
can also be added directly to klee **remember the **atar component used above? Wrap the native view object once with the view component, and you can accept the layout management of the klee framework.
Let's assume that there is already @interface my**atarview : uiview in the project
my**atarview **atarview = [[my**atarview alloc] initwithuserid:msg.senderid];
component **atar = view(**atarview).size(40, 40);
You can also write a very simple wrapper function, and My**AtarView immediately becomes a Klee-style **atar component.
component **atar(userid) {
return view([[my**atarview alloc] initwithuserid:userid]);
Klee currently offers three types of foundational components:
Layout componentsManages the position and size of subassemblies that do not participate in drawing and do not appear in the final view tree. For examplestack
row
column
Wait.
View componentThe runtime will produce a corresponding native view, which will complete the actual drawing and interaction. For examplelabel
image
button
checkbox
etc., useview
Any native view can be encapsulated. shape
Components are used to produce a variety of visual and graphic elements. list
The components encapsulate the most commonly usedtableview
You can quickly build a list interface that supports view reuse. In addition, there are:page
, which is benchmarked against iOSuiviewcontroller
or Android'sactivity
Devise.
Logical componentsProvides basic structuring capabilities, through:if/then/else
withfor
The basic components that enable simple conditions and loops.
Three types of components can be taken furtherCombined nestingto form composite components.
As a native data-driven development framework, Klee's design ideas are different from mainstream frameworks such as Rxswift. Let's ignore the differences between the capabilities of C++ and Swift themselves, and only make some comparative analysis of the framework design itself.
Klee's recommended development practice is to define independent Model and ViewModel structures to store reactive data, and then bind them to UI controls, which is more convenient for cross-platform development and reuse**.
Rxswift usually uses UI controls as the data source, and the controls directly generate a listening sequence, which is more concise, but to be cross-platform, there are many changes.
The ** developed by klee is a number of fragments that receive inputs and output outputs, and the developers will not strictly describe the logical relationship, as long as the input of each fragment is satisfied, the process will be executed in parallel.
Rxswift has a relatively clear data flow and needs to describe the dependencies between processes, but it also means that developers need to sort out the process themselves to ensure that the logic is correct and the best performance is achieved.
Since klee's dependencies are automatically established by the framework and don't need to be maintained by developers, it's still very concise in the case of multiple input sources.
The rxswift single input source** is concise and clear, but the multi-input source scenario requires developers to use various operators to connect to generate new sequences, which is a slightly higher learning curve.
klee is the control subscription data, so the listener's lifecycle naturally follows the control and destroys along with it; And the reactive data referenced is all from the model, and there is no circular reference problem.
Rxswift is a data-bound control, so developers need to manually specify a disposebag to control the listener's lifecycle, and a single wrong self capture in a function can have catastrophic consequences.
Klee is currently at TencentInternal open source, which is applied in some functions of WeCom, iOS, Android, macOS, and other terminals. Practice has shown that the amount of developing the same feature is only about 60% of that of traditional development methods, and it has better readability and reusability.
After the framework has been tested on a larger scale and the API remains stable, it will be open sourced.
The Klee reactive kernel is written entirely in C++ and is currently cross-platform on iOS, macOS, and Android, and can be compiled and used on Windows with some additional modifications. If the upper layer of Android needs to be accessed, it also provides a set of J**A layer interface encapsulation.
The componentized part currently only provides iOS and macOS implementations, which can already be compatible with two platforms. As long as a native implementation of a set of basic components is provided for each platform, this development model can be further extended to Android and WindowsMost** cross-platform reuse
Component-based development, ideal for passingWhat you see is what you getway to build. This ability not only benefits development colleagues, but also product and design colleagues can make their own products, quickly experience the interface effect, and even directly deliver** instead of prototypes and design drafts.