当谈到C++这种已有38年历史的语言时,每个人都或多或少地知道它。面向对象程序编程这些话立即出现在我的脑海中。 “高性能复杂度高“这两个标签,以及C++,多年来在许多语言中一直是独一无二的。
而我们在实际项目的开发过程中发现,功能一模一样,综合考虑前期开发后期错误跟UI 恢复等阶段人力投入、使用Web 技术堆栈要实现前端页面,研发效率约为平台的原生开发之2 至 3 次。开发效率的差异让我们好奇地想更深入地了解其中的原因。
支持近年来兴起的三大前端框架,Angular、React 和 Vue组件化跟响应开发,为前端带来了丰富的生态系统,大大简化了 Web 开发的流程,使开发大型 Web 应用程序变得容易。 另一方面,C++近年来几乎没有改进发展历程及理念所谓的现代 C++ 在许多人眼中只是增加了许多晦涩难懂的内容,并且提高了开发标准,而兴趣不大。
您可能也接触过并了解了前端组件化跟响应我发展了,但我是否希望有一天能够做到这一点用 C++ 实现
给出下面的设计草稿,试着粗略评估一下,能做多少时间?
*展馆设计草稿。
不要急于看到答案,我们来分析一下这个典型的列表界面:
小部件:必填tableview方法布局,每行都有一个头像、名称、状态点、作品列表和一个按钮。 头像使用 URL异步**,考虑潜在的细胞重用问题。 状态点的颜色、按钮的文本和禁用状态应遵循任务的状态实时更新。布局:是适应不同尺寸屏幕、头像和按钮从左右分开剩余空间留下姓名和作品列表。 功能:单击按钮将使**状态已转移,执行 ** 操作和更新点和按钮,并在完成失败后再次触发更新。你心里有没有数字,下面答案就揭晓了:
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);
这应该不是你最熟悉的风格,但如果你使用过反应式开发框架,它就不会太陌生。 只用了几十行就完成了这样一个接口的开发,它做到了实时更新是不是很香?
* 这完全是数据驱动的功劳,因为它如此简洁。 框架可以智能地跟踪和建立数据与接口之间的关系数据更改界面无需开发人员手动管理即可更新。
先消化它,然后再看下一个小惊喜酒吧。 无需换行**,即可获得同款macOS原生版本作为礼物,买一送一。 :-
*Pavilion macOS版本。
简单地说,数据驱动是一种编程理念,编程化状态由数据决定,通过提供的接口运营数据控制程序逻辑,不建议直接操作 UI 组件。 除了 Web 技术堆栈之外,还可以在流行的客户端开发框架(如 Flutter 和 SwiftUI)上找到数据驱动的影子。
开发者只需使用**或其他方式即可描述各个界面元素和数据之间的关系数据的流动和接口的维护将由框架自动处理,大大简化了程序员需要注意的事情。
很多人不理解反应式实现的原理,我曾经认为C++作为一种静态编译的语言是不能使用的运行周期收集,它应该是编译期被告知依赖。毕竟,未执行的条件分支在运行时并不存在。
直到我读到 vueJS源码,我了解依赖关系是怎么回事运行时集合维护之
有两个核心点:
初始化执行一次并收集初每次都执行依赖项记得这里忽略的依赖关系的一个方面是,如果 ** 将执行到另一个分支,它必须当前依赖关系会改变。 因此,没有必要一次性收集完整的依赖,只需确保收集当前路径的依赖即可。
这就像尝试一个函数一样简单读当提交反应性数据时,会记录该函数对该数据具有依赖性。 响应式数据具有:更新,遍历其所有依赖函数,重新执行,然后再次收集新的依赖项。
由于 C++ 是一种编译语言,因此很难像 Klee 直接提供的 Vue 那样为数据做动态钩子代理反应式数据封装,用于在开发阶段替换常见数据类型。
响应式数据在 Klee 框架中使用类型reactive
表示,允许依赖,只暴露读取接口,内部采用多态实现。
创建一个只读反应式数据,其反应式分数常数 = readonly(60); std::cout name = "tibberswang";设置值。
std::cout 也可以以这种方式使用,创建一个指定初始值的反应式变量。
注意:反应式方法具有 const char* 的特殊重载,输出类型为 std::string
auto /* value */ name = reactive("tibberswang");
计算数据通过computed
生成该方法,并且返回类型仍为外部reactive
,其内容由lambda(C++)或块(Objective-C)计算,并将使用计算结果缓存。在计算数据的函数主体中使用反应式数据,将自动建立依赖关系,如果依赖项发生更改,则计算属性将被标记为脏属性,并在下次再用或下一个消息循环触发重新计算。
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);
正如你所看到的,**非常清晰简洁,它有它缓存延迟加载稳定和重复数据删除请求聚合等优化策略,往往比手写更好**更好的性能看完前面的例子,你觉得少了点什么吗? 是的,上述函数最终只返回一个reactive
,用于**?
这就是我们将在本节中讨论的内容基于组件的开发完成。 当然,如果你只想使用响应式编程进行开发,你可以:
uilabel *label = [uilabel new];
label.font = [uifont systemfontofsize:14];
label.textcolor = [uicolor redcolor];
label kl_bindtext:getdisplayname(user_id, corp_id)];
为uilabel
提供的分类方法kl_bindtext:
角色是数据绑定它就在这里。 叫kl_bindtext:
,如果反应式数据发生变化,框架将显示在下一张图纸之前重新评估反应数据,然后调用settext:
方法变更label
并触发视图树的重新布局还有四行**,还是有点繁琐。 可莉交付声明开发模型可以这样写:
label(14_pt, 0xff0000_rgb, getdisplayname(user_id, corp_id));
注意:上面的 PT 和 RGB 后缀是使用 C++ 的用户定义文本功能实现的自定义文本。label 是 klee 框架提供的内置文本展示组件,构建时的参数支持同时传入字符串、属性字符串、字体、颜色,参数是允许的随意加减或交换订单,例如,这也没关系:
label(getdisplayname(user_id, corp_id), 17_pt);
如果需要,这些参数是响应式的动态修改颜色,然后该参数传入表示颜色的反应式数据
auto vipcolor = computed([=] else
label(getdisplayname(user_id, corp_id),vipcolor);
作为性能优化的一部分,如果标签仅更改颜色,则框架认为没有必要重新计算标签的大小不会触发视图树将重新排列。
用基于组件的开发完成整个单元格的写入。
微信消息气泡。
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)
你尝,你尝。 短短几行**,利用各种基础组件的组合完成各种复杂接口功能的配置和布局。 没有继承,没有方法重写,也没有侦听器和观察器。 基于flexbox布局模型可以独立使用适应各种屏幕宽度为了能够进步通过将 klee 集成到项目中,klee 可以与现有的原生开发模式集成混搭使用,并且不需要对项目进行全面翻新。
klee 提供的视图组件允许隐式转换为原生视图,直接参与原始原生模式的开发。
uilabel * label = label(name, 17_pt); // ios
nstextfield *label = label(name, 17_pt); // macos
包含布局组件widgetref
对象可以隐式转换为klwidgetview
或klwidgetscrollview
参与原始原生模型的开发。
klwidgetview 和 klwidgetscrollview 的区别在于它是否支持滚动。
klwidgetview *view = messagecell(msg);
self.view addsubview:view];
该项目基于uiview
或nsview
也可以直接添加到 klee **还记得上面使用的**atar组件吗? 用 view 组件包装一次原生视图对象,就可以接受 klee 框架的布局管理了。
假设项目中已经有@interface my**atarview : uiview
my**atarview **atarview = [[my**atarview alloc] initwithuserid:msg.senderid];
component **atar = view(**atarview).size(40, 40);
你也可以写一个非常简单的包装函数,My**AtarView立刻就变成了一个Klee风格的**atar组件。
component **atar(userid) {
return view([[my**atarview alloc] initwithuserid:userid]);
Klee 目前提供三种类型的基础组件:
布局组件管理不参与绘图且未出现在最终视图树中的子组件的位置和尺寸。 例如stack
row
column
等。
查看组件运行时将生成相应的原生视图,该视图将完成实际的绘制和交互。 例如label
image
button
checkbox
等等,使用view
任何本机视图都可以封装。 shape
组件用于生成各种视觉和图形元素。 list
这些组件封装了最常用的tableview
您可以快速构建支持视图复用的列表接口。 此外,还有:page
,以 iOS 为基准uiviewcontroller
或 Android 的activity
设计。
逻辑组件通过以下方式提供基本的结构化功能:if/then/else
跟for
支持简单条件和循环的基本组件。
可以进一步采用三种类型的组件组合嵌套形成复合组件。
作为一个原生的数据驱动开发框架,Klee的设计思路与Rxswift等主流框架不同。 我们暂且忽略C++和Swift本身能力的差异,只对框架设计本身做一些对比分析。
Klee 推荐的开发实践是定义独立的 Model 和 ViewModel 结构来存储反应式数据,然后将它们绑定到 UI 控件上,这样更方便跨平台开发和复用**。
Rxswift 通常使用 UI 控件作为数据源,控件直接生成监听序列,比较简洁,但要跨平台,变化很多。
klee 开发的 ** 是接收输入和输出输出的多个片段,开发人员不会严格描述逻辑关系,只要满足每个片段的输入,流程就会并行执行。
Rxswift 的数据流相对清晰,需要描述进程之间的依赖关系,但这也意味着开发者需要自己梳理流程,以确保逻辑正确,达到最佳性能。
由于 klee 的依赖项是由框架自动建立的,不需要开发人员维护,因此在多个输入源的情况下,它仍然非常简洁。
rxswift 单输入源**简洁明了,但多输入源场景需要开发者使用各种算子连接生成新序列,学习曲线略高。
klee 是控件订阅数据,因此监听器的生命周期自然会跟随控件并随控件一起销毁; 并且引用的响应数据全部来自模型,不存在循环引用问题。
Rxswift 是一个数据绑定控件,因此开发人员需要手动指定一个 disposebag 来控制监听器的生命周期,函数中一次错误的自我捕获可能会造成灾难性的后果。
可莉目前就职于腾讯内部开源,应用于企业微信、iOS、Android、macOS等终端的部分功能。 实践表明,开发相同功能的量仅为传统开发方法的60%左右,具有更好的可读性和可复用性。
在框架经过更大规模的测试并且 API 保持稳定后,它将开源。
Klee 反应式内核完全用 C++ 编写,目前在 iOS、macOS 和 Android 上是跨平台的,并且可以通过一些额外的修改在 Windows 上编译和使用。 如果需要接入 Android 的上层,它还提供了一套 J**A 层接口封装。
组件化部分目前仅提供 iOS 和 macOS 实现,它们已经可以兼容两个平台。 只要为每个平台提供一组基本组件的原生实现,这种开发模式就可以进一步扩展到 Android 和 Windows大多数**跨平台重用
基于组件的开发,非常适合通过所见即所得建造方式。 这种能力不仅使开发同事受益,而且产品和设计同事也可以制作自己的产品,快速体验界面效果,甚至直接交付**,而不是原型和设计草稿。