啸达同学刚写zone.js系列就说过,NgZone影响着Angular中的变更检测,历时一个多月的笔耕不辍,终于到了他首次下笔时的目的地~
zone.js系列
- zone.js由入门到放弃之一——通过一场游戏意识zone.js
- zone.js由入门到放弃之二——zone.js API大练兵
- zone.js由入门到放弃之三——zone.js 源码剖析【setTimeout篇】
- zone.js由入门到放弃之四——Angular对zone.js的利用
初见NgZone
其实在上一篇文章中,大家曾经初步窥探过NgZone的芳容了。而且咱们也晓得了,在NgZone中保护了OuterZone和InnerZone两个Zone。明天的这篇文章,咱们次要剖析一下InnerZone,并看一下InnerZone是如何跟Angular的变更检测分割到一起的。
InnerZone四办法
NgZone中InnerZone的创立是通过forkInnerZoneWithAngularBehavior
实现的,创立过程的简化版如下,其中又能看到很多相熟的勾子函数。这里简略温习一下这几个勾子的意义:
onInvokeTask
:zone.js会在初始化的时候将异步办法都Pathc成ZoneTask,从而跟踪异步工作的执行状况的。onInvokeTask
就是其中的一个勾子函数,它会在异步工作执行回调的时候触发。onInvoke
:onInvoke
会在咱们手动执行zone.run()的时候执行。onHasTask
:是针对整个工作队列状态扭转的监听,当检测工作队列中有工作进入、或是有工作执行完出队列的时候会被执行。onHandleError
:当有异样抛出时被执行
InnerZone对异步工作的管制精髓基本上就全副稀释在这几个勾子函数中了,与此同时,为了更好地配合对异步工作的跟踪,NgZone中还定义了很多状态监控字段。只有理清这些字段的含意能力持续往下深刻代码。
不相熟zone.js原理的能够回看一下zone.js由入门到放弃之一和zone.js由入门到放弃之二(链接见文首)
传送门
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) { zone._inner = zone._inner.fork({ name: 'angular', properties: <any>{'isAngularZone': true}, onInvokeTask: (...): any => { ... }, onInvoke: (...): any => { ... }, onHasTask: (...): any => { ... }, onHandleError: (...): any => { ... }, });}
InnerZone五状态
接下来这几个状态属性会贯通在前面的源码剖析的全副过程中,咱们也会通过对这几个状态的跟踪理解一下InnerZone事件跟踪的原理。
- hasPendingMacrotasks: boolean 队列中是否有待执行的宏工作
- hasPendingMicrotasks: boolean 队列中是否有待执行的微工作
- _nesting: number 队列中待执行工作的个数
- isStable: boolean 当工作队列中既没有待执行的宏工作,也没有待执行的微工作时,isStable为ture,示意以后是个稳固的状态。反之则代表非稳固状态。
- lastRequestAnimationFrameId: number 这个状态有些特地,它是一个延时器,前面会开展解释。
代码走读
后面在介绍zone.js的时候咱们说过,zone.js把异步工作分为MacroTask、MicroTask和Event三种。明天咱们就别离把这三种工作都按流程剖析一遍。从难易水平上看,MacroTask最简略,Event绝对最简单。接下来,咱们就依照这个程序解说。
MacroTask
之前在zone.js由入门到放弃之三中,具体介绍过zone.js对setTimeout的Patch过程,如果不理解具体过程的强烈建议先浏览一下那篇文章。
这一次,咱们还是通过个setTimeout事件来跟踪NgZone的处理过程,测试代码很简略,如下所示。
export class AppComponent implements OnInit { title = 'ngzone-process'; ngOnInit(): void { setTimeout(() => { console.log('[setTimeout] run in next 5s'); }, 5000); } ngDoCheck() { console.log('rendering...'); }}
因为zone.js能够感知到工作队列的变动状况,所以当setTimeout
执行时,它能够晓得以后有一个宏工作来了,同时会触发onHasTask勾子。
onHasTask
当onHasTask
"检测"到有宏工作到来时,会把hasPendingMacrotasks
设置为true。
传送门
onHasTask:(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => { delegate.hasTask(target, hasTaskState); if (current === target) { // ... } else if (hasTaskState.change == 'macroTask') { zone.hasPendingMacrotasks = hasTaskState.macroTask; } }},
此时,NgZone中的几个状态值大略是这个样子的,hasPendingMacrotasks变为true,示意以后有一个待执行的MacroTask。
接下来,zone.js会通过调用scheduleFn
,并把封装后的回调函数放在Timer队列中期待时钟达到。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
true | false | 0 | true | -1 |
onInvokeTask
过后钟达到当前,事件循环会把封装后的回调函数放在工作队列中期待执行。当执行到回调时,回调会触发task.invoke
函数,接下来就会唤醒onInvokeTask勾子函数。
传送门
onInvokeTask:(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, applyArgs: any): any => { try { onEnter(zone); // 执行真正的回调 return delegate.invokeTask(target, task, applyThis, applyArgs); } finally { // ... onLeave(zone); }},
delegate.invokeTask(target, task, applyThis, applyArgs);
是用来调用真正的回调函数的。除了这行,咱们能够看到在回调之前先后别离还各有一个办法:onEnter
和onLeave
。
onEnter
onEnter
执行过程中,_nesting
会自增,示意了以后新增一个待执行工作。当有工作要执行时,之前的稳固状态会被突破,同时触发一个onUnstable
事件。这个onUnstable
事件被ApplicationRef订阅,ApplicationRef会依据这个事件同步批改它本身的稳固状态(ApplicationRef的代码前面解说)。
传送门
function onEnter(zone: NgZonePrivate) { zone._nesting++; if (zone.isStable) { zone.isStable = false; zone.onUnstable.emit(null); }}
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
true | false | 1 | false | -1 |
onLeave
onLeave
函数执行的时候,阐明MarcoTask的回调曾经执行结束,_nesting
会执行一次自减操作。接下来又执行了checkStable
函数。
传送门
function onLeave(zone: NgZonePrivate) { zone._nesting--; checkStable(zone);}
checkStable
函数十分要害!每当执行到checkStable的时候,都是变更检测执行的要害。以至于这个函数的每一行都值得拿出来讲一下,我在代码中标记了序号,这样不便前面走读代码。
checkStable
既然是判断是否进行变更检测要害,那么1标识的if子句就是判断的要害。代码的意思大略就是,只有确保以后没有任何待执行的工作,同时以后状态为不稳固状态的时候才须要触发变更检测。- 代码2标识了一个成对的
_nesting
自增、自减操作。这里这么做的起因是代码3这里抛出了事件,对该事件的订阅实际上也是一个异步工作。所以这里通过_nesting
的自增、自减操作阐明这里是有一个异步工作的。 - 代码3就是变更检测的要害了,AppliactionRef会订阅
onMicrotaskEmpty
事件,每当onMicrotaskEmpty
触发后,AppliactionRef就会执行一次变更检测。 - 代码4这里大家可能会有疑难,为什么在这里还要对
hasPendingMicrotasks
进行一次判断?这是因为在代码3这里,对onMicrotaskEmpty
的订阅者有可能会在订阅回调中再执行一些异步工作,就像上面这样。此时,并不能保障在checkStable
的过程中,不会有新的工作进入到待执行队列。所以这里,又对hasPendingMicrotasks
的状态做了一次判断。确保在状态变为稳固之前,工作队列中不存在工作微工作。
zone.onMicrotaskEmpty.subscribe(() => { Promise.resolve(0).then(console.log);});
- 代码5是对外触发一个状态稳固的事件,这个事件跟
OnEnter
函数中那个onUnstable
绝对。然而你可能会好奇,这里为什么要在runOutsideAngular
中执行。我这里解释下,仅代表个人见解。onStable
和onMicrotaskEmpty
存在一样的问题,因为都是可察看对象,所以存在订阅者在回调继续执行异步工作的问题。如果在onStable
的订阅中执行异步工作,那NgZone的状态马上有会变成非稳固的,这将会陷入一个有限的死循环中,NgZone会在稳固和不稳固状态之间来回切换,永不进行。所以这里应用runOutsideAngular
,让zone.js放弃对这里的代码进行跟踪。这样,依据上一讲咱们学过的内容,runOutsideAngular
中执行异步不会触发变更检测,当然也不会触发NgZone的状态变动。 - 扭转zone的状态为稳固。
传送门
function checkStable(zone: NgZonePrivate) { // 1 if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) { try { // 2 zone._nesting++; // 3 zone.onMicrotaskEmpty.emit(null); } finally { // 2 zone._nesting--; // 4 if (!zone.hasPendingMicrotasks) { try { // 5 zone.runOutsideAngular(() => zone.onStable.emit(null)); } finally { // 6 zone.isStable = true; } } } }}
这里我多补充一点常识,我之前看到这里的代码的时候也感觉有点绕,所以我在这里做了大量的测试。后果发现,如果在onStable的订阅回调中再应用zone.run执行异步工作的时候就会造成一个有限的死循环。这里是我的最小实现仓,够胆的能够试试,你的浏览器会在霎时解体。当然,我也给官网提了issue,原作者也证实了这确实是个问题,感兴趣的能够跟踪一下,继续关注。
截止到这里,咱们再看一下NgZone的几个状态指标。此时队列中不存在待执行的工作,NgZone会把本身状态批改为稳固态。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
true | false | 0 | true | -1 |
onHasTask
整个setTimeout跟踪的最初一步还是这个勾子,这次,勾子函数中会把hasPendingMacrotasks置为false。此时,几个状态曾经复原为最后的问题状态,Angular也在这个过程中执行了一次变更监测。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
false | false | 0 | true | -1 |
MicroTask
MicroTask和MacroTask的行为大抵上统一,只不过因为zone.js在解决MicroTask和MacroTask时有一丢丢的区别,导致这里解决也会有一点不同,这个我会在下文做专门解释。当然,如果你还想关注zone.js在解决MicroTask和MacroTask时到底有什么不一样的,能够关注一下我的下一篇文章(如果有的话),外面会像本系列的第三期一样,具体解释zone.js解决Promise的技术细节。
onHasTask
onHasTask跟之前没什么区别,执行过后,状态如下。与MacroTask不同,这次是hasPendingMicrotasks变为true,示意队列中有一个待执行的微工作。
传送门
onHasTask:(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => { delegate.hasTask(target, hasTaskState); if (current === target) { if (hasTaskState.change == 'microTask') { zone._hasPendingMicrotasks = hasTaskState.microTask; // ... } // ... }},
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
false | true | 0 | true | -1 |
onInvokeTask
MicroTask和MacroTask在这个勾子中的处理过程基本上是雷同的。然而MicroTask在回调执行的时候和MacroTask还是有一点差别的。后面局部,咱们讲MacroTask的时候,delegate.invokeTask(target, task, applyThis, applyArgs);
这句会间接触发setTimeout的回调函数执行。然而在MicroTask中,微工作的回调内部还会包装一层zone.run
,导致MicroTask的回调会通过onInvoke
勾子执行。
传送门
onInvokeTask:(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, applyArgs: any): any => { try { onEnter(zone); // 执行真正的回调 return delegate.invokeTask(target, task, applyThis, applyArgs); } finally { // ... onLeave(zone); }},
onInvoke
能够看到onInvoke
和onInvokeTask
函数的内容是差不多的。onEnter
和onLeave
的调用也基本一致,所以这里就不专门剖析了。
onInvoke:(delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, applyThis: any, applyArgs?: any[], source?: string): any => { try { onEnter(zone); return delegate.invoke(target, callback, applyThis, applyArgs, source); } finally { onLeave(zone); }},
当这个函数执行完结后,几个状态值变动如下。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
false | true | 0 | true | -1 |
onHasTask
最初一个执行的还是onHasTask
函数,这个函数执行结束后,几个状态又回到初始状态。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
false | false | 0 | true | -1 |
Event
Event的执行形式跟MacroTask和MicroTask都不太一样。还记得之前咱们在讲NgZone的5大状态的时候,有一个lastRequestAnimationFrameId
始终没有用到。那么,在Event的处理过程中,咱们会看到它的作用。
onInvokeTask
Event的解决入口是onInvokeTask
而不是onHasTask
,onEnter
和delegate.invokeTask
与之前都差不多,然而在finally子句中,你会发现Event的解决中多了一个delayChangeDetectionForEventsDelegate
函数。其实从函数的函数名大略能猜个七七八八,这个是一个事件延时解决的函数。
onInvokeTask:(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, applyArgs: any): any => { try { onEnter(zone); return delegate.invokeTask(target, task, applyThis, applyArgs); } finally { if ((zone.shouldCoalesceEventChangeDetection && task.type === 'eventTask') || zone.shouldCoalesceRunChangeDetection) { // Event的非凡解决逻辑 delayChangeDetectionForEventsDelegate(); } onLeave(zone); }},
delayChangeDetectionForEventsDelegate
其实咱们在上一期解说中曾经介绍了一些通过NgZone进行性能调优的伎俩,那么这个函数的产生实际上也是用于性能上的优化。咱们晓得,浏览器很多事件诸如mousemove、scroll这些都会在短时间内产生大量的事件。如果每个这样的事件都会触发一次Angular的变更检测的话,那么对性能上的要求是很大的。所以,NgZone也须要在外部对于这些浏览器的事件做一些非凡解决,让大量的事件积攒一段时间后再对立做一次变更检测。
那么delayChangeDetectionForEventsDelegate
中理论调用的办法是delayChangeDetectionForEvents
,所以咱们重点关注一下delayChangeDetectionForEvents
函数的源码。
- 代码1这里,咱们第一次见到对lastRequestAnimationFrameId的判断,当第一个Event到来时,这里的lastRequestAnimationFrameId还是初始值-1
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
false | false | 1 | false | -1 |
zone.nativeRequestAnimationFrame
的调用实际上调用的是Window.requestAnimationFrame
。这里,NgZone实际上是心愿通过requestAnimationFrame
收集这一帧内的所有事件,在这一帧完结后,再对立执行一次变更检测。requestAnimationFrame
执行的返回值会赋值给lastRequestAnimationFrameId
,这样,在接下来代码每次进入到代码1处的时候,函数会间接返回。updateMicroTaskStatus
被用来更新微工作状态的。那么这里执行之后,状态值中的hasPendingMicrotasks会变为true。这里这么做是为了收集Event的时候能够阻塞微工作触发变更检测,这么做的起因是为了确保Event事件的执行程序不会被微工作打乱。这里要具体介绍又会有很大篇幅,感兴趣的能够本人看下这个issue;不想关注的能够先跳过这里。
hasPendingMacrotasks | hasPendingMicrotasks | _nesting | isStable | lastRequestAnimationFrameId |
---|---|---|---|---|
false | true | 1 | false | 一个正整数返回值 |
- 当以后帧执行结束、下一帧要执行的时候会调用一次
checkStable
函数。这个函数在后面讲过,它是触发Angular变更检测的要害。通过执行该办法,Angular会通过ApplicationRef执行变更检测动作。
传送门
function delayChangeDetectionForEvents(zone: NgZonePrivate) { // 1 if (zone.isCheckStableRunning || zone.lastRequestAnimationFrameId !== -1) { return; } // 2 zone.lastRequestAnimationFrameId = zone.nativeRequestAnimationFrame.call(global, () => { if (!zone.fakeTopEventTask) { zone.fakeTopEventTask = Zone.root.scheduleEventTask('fakeTopEventTask', () => { zone.lastRequestAnimationFrameId = -1; updateMicroTaskStatus(zone); zone.isCheckStableRunning = true; // 4 checkStable(zone); zone.isCheckStableRunning = false; }, undefined, () => {}, () => {}); } zone.fakeTopEventTask.invoke(); }); // 3 updateMicroTaskStatus(zone);}
再见ApplicationRef
上一节中,咱们讲过一点ApplicationRef相干的常识,这一次,咱们重点看下ApplicationRef跟变更检测相干的代码。
变更检测
后面说到,NgZone在checkStable
中,如果发现以后曾经没有待执行的工作的时候,会触发一个onMicrotaskEmpty
事件。在这里,这个事件会被ApplicationRef所捕捉。捕捉后,会执行ApplicationRef.tick
,而这个tick就是变更检测的入口。
传送门
this._onMicrotaskEmptySubscription = this._zone.onMicrotaskEmpty.subscribe({ next: () => { this._zone.run(() => { this.tick(); }); },});
tick
在tick
办法中,咱们能够看到ApplicationRef通过调用视图的detectChanges
办法,让组件实现自上而下的变更检测。上一篇文章中,咱们介绍过一些手动执行变更检测的办法,其中有提到过ChangeDetectorRef.detectChanges()
这个办法。这个办法能够对以后组件以及以后组件的子组件进行进行变更检测。那么这里看到的view.detectChanges()
跟ChangeDetectorRef.detectChanges()
又有什么关系?
其实从_views
类型的继承链能够发现,_views
的类型InternalViewRef
继承自ViewRef
,ViewRef
又继承自ChangeDetectorRef
。所以调用view.detectChanges()
就相当于调用了ChangeDetectorRef.detectChanges()
,从而实现一次自上而下的变更检测。
传送门
tick(): void { // ... try { this._runningTick = true; for (let view of this._views) { // 组件的变更检测 view.detectChanges(); } if (typeof ngDevMode === 'undefined' || ngDevMode) { for (let view of this._views) { view.checkNoChanges(); } } } catch (e) { // Attention: Don't rethrow as it could cancel subscriptions to Observables! this._zone.runOutsideAngular(() => this._exceptionHandler.handleError(e)); } finally { this._runningTick = false; }}
以上就是NgZone和ApplicationRef之间的配合关系。咱们整体再回顾一下整个系列课程的内容。zone.js通过Monkey Patch对所有异步办法进行打包;打包后的异步办法被植入了很多勾子函数,而这些勾子函数能够被zone.js的上下文检测到,从而实现对异步工作的监控。
NgZone是对zone.js的一个应用案例,NgZone通过保护InnerZone和OuterZone两个Zone实现了对Angular利用中的异步工作的监控和去监控。NgZone同时在外部也保护了几个对异步工作监控的状态信息,通过这些信息实现了和ApplicationRef之间的“通信”,最终由ApplicationRef实现对Angular利用的监控。
本文小结
到这里,明天的内容就介绍的差不多了。最初,这里还须要像读者阐明一点,在NgZone中跟踪Task的运行是一件比拟难的事件,本文所有这些Task的举例其实都是理想化的。比如说,在举例setTimeout的时候,你会发现当你想在Angular利用中对异步Task跟踪的时候,会有很多其它Task同时在执行着,这些Task常常会在你调试跟踪的时候对你造成“烦扰”。所以,本文的这些举例只是心愿让大家看过后,能大抵对每种不同工作在NgZone中流程有个意识,而实在的过程会远比我明天讲的内容简单的多。这同时也从侧面反映出,zone.js默默对Angular作出多大的奉献。
大完结
本系列分享历时将近1个多月,加上后期的一些剖析和总结,我集体大略继续关注zone.js有两个多月了。最初的最初,我也分享几点集体感触:
- 有人说zone.js是暴力美学,我个人感觉可能美的中央更多一些吧。作为Angular变更检测的外围,Angular的变更检测在三大框架中是独一份的存在。我感觉比起其它两个通过数据劫持和虚构Dom的形式进行数据绑定的形式,zone.js显得还是要温顺一些的。毕竟数据劫持是间接“净化”了数据的,而zone.js“革新”的是工具。我没法说谁更好,只是集体更偏差于后者。
- 截止到当初,我集体也没有齐全看完zone.js的源码,然而我心愿我会在后续的工作中继续关注这个产品。同时我也看到JiaLi(zone.js作者)为了他的作品一直地对zone.js进行改良。所以,请他加油,我心愿zone.js能够越来越好!不过话说回来,JiaLi想在Angular社区实现一个PR是不是太难了点啊。我看了他好多的批改,常常要等良久能力审核通过,有点疼爱他。
其实最开始的时候,我只是想本人学学zone.js的,并没有布局这个系列分享。然而,我在学习源码的时候,苦于能找到的材料太旧又太少,所以就筹备本人写一个有史以来最艰深、最全面、也最适宜中国人学的zone.js资料。当然,前两个“最”我可能还不配;然而第三个最,我感觉还是能够搏一搏的✌。
OpenTiny Vue招募贡献者啦!
OpenTiny Vue 正在招募社区贡献者,欢送退出咱们
你能够通过以下形式参加奉献:
- 在 issue 列表中抉择本人喜爱的工作
- 浏览贡献者指南,开始参加奉献
你能够依据本人的爱好认领以下类型的工作:
- 编写单元测试
- 修复组件缺点
- 为组件增加新个性
- 欠缺组件的文档
如何奉献单元测试:
- 在
packages/vue
目录下搜寻it.todo
关键字,找到待补充的单元测试 - 依照以上指南编写组件单元测试
- 执行单个组件的单元测试:
pnpm test:unit3 button
如果你是一位经验丰富的开发者,想承受一些有挑战的工作,能够思考以下工作:
- ✨ [Feature]: 心愿提供 Skeleton 骨架屏组件
- ✨ [Feature]: 心愿提供 Divider 分割线组件
- ✨ [Feature]: tree树形控件能减少虚构滚动性能
- ✨ [Feature]: 减少视频播放组件
- ✨ [Feature]: 减少思维导图组件
- ✨ [Feature]: 增加相似飞书的多维表格组件
- ✨ [Feature]: 增加到 unplugin-vue-components
- ✨ [Feature]: 兼容formily
参加 OpenTiny 开源社区奉献,你将播种:
间接的价值:
- 通过参加一个理论的跨端、跨框架组件库我的项目,学习最新的
Vite
+Vue3
+TypeScript
+Vitest
技术 - 学习从 0 到 1 搭建一个本人的组件库的整套流程和方法论,包含组件库工程化、组件的设计和开发等
- 为本人的简历和职业生涯添彩,参加过优良的开源我的项目,这自身就是受面试官青眼的亮点
- 结识一群优良的、酷爱学习、酷爱开源的小伙伴,大家一起打造一个平凡的产品
久远的价值:
- 打造集体品牌,晋升集体影响力
- 造就良好的编码习惯
- 取得华为云 OpenTiny 团队的荣誉和定制小礼物
- 受邀加入各类技术大会
- 成为 PMC 和 Committer 之后还能参加 OpenTiny 整个开源生态的决策和长远规划,造就本人的治理和布局能力
- 将来有更多机会和可能
其余阐明
OpenTiny 是一套企业级组件库解决方案,适配 PC 端 / 挪动端等多端,涵盖 Vue2 / Vue3 / Angular 多技术栈,领有主题配置零碎 / 中后盾模板 / CLI 命令行等效率晋升工具,可帮忙开发者高效开发 Web 利用。
外围亮点:
- 跨端跨框架: 应用 Renderless 无渲染组件设计架构,实现了一套代码同时反对 Vue2 / Vue3,PC / Mobile 端,并反对函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强
- 组件丰盛:PC 端有100+组件,挪动端有30+组件,蕴含高频组件 Table、Tree、Select 等,内置虚构滚动,保障大数据场景下的晦涩体验,除了业界常见组件之外,咱们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP地址输入框、Calendar 日历、Crop 图片裁切等
- 配置式组件: 组件反对模板式和配置式两种应用形式,适宜低代码平台,目前团队曾经将 OpenTiny 集成到外部的低代码平台,针对低码平台做了大量优化
- 周边生态齐全: 提供了基于 Angular + TypeScript 的 TinyNG 组件库,提供蕴含 10+ 实用功能、20+ 典型页面的 TinyPro 中后盾模板,提供笼罩前端开发全流程的 TinyCLI 工程化工具,提供弱小的在线主题配置平台 TinyTheme
欢送退出 OpenTiny 开源社区。增加微信小助手:opentiny-official 一起参加交换前端技术~
OpenTiny 官网:opentiny.design/
OpenTiny 代码仓库:https://github.com/opentiny/
Vue组件库:opentiny.design/tiny-vue
Angular组件库:opentiny.design/tiny-ng
欢送进入代码仓库 StarTinyVue、TinyNG、TinyCLI~
如果你也想要共建,能够进入代码仓库,找到 good first issue
标签,一起参加开源奉献~
往期文章举荐
- 必不可少的UI组件一——组件的基础知识
- OpenTiny Vue 3.10.0 版本公布:组件 Demo 反对 Composition 写法,新增4个新组件
- 前端Vuer,请收好这份《Vue组件单元测试》宝典
- OpenTiny 前端组件库正式开源啦!面向未来,为开发者而生
- 从自研走向开源的 TinyVue 组件库
- 我要做开源,提交我的第一个PR