啸达同学刚写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就是其中的一个勾子函数,它会在异步工作执行回调的时候触发。
  • onInvokeonInvoke会在咱们手动执行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队列中期待时钟达到。

hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
truefalse0true-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);是用来调用真正的回调函数的。除了这行,咱们能够看到在回调之前先后别离还各有一个办法:onEnteronLeave

onEnter

onEnter执行过程中,_nesting会自增,示意了以后新增一个待执行工作。当有工作要执行时,之前的稳固状态会被突破,同时触发一个onUnstable事件。这个onUnstable事件被ApplicationRef订阅,ApplicationRef会依据这个事件同步批改它本身的稳固状态(ApplicationRef的代码前面解说)。

传送门

function onEnter(zone: NgZonePrivate) {  zone._nesting++;  if (zone.isStable) {    zone.isStable = false;    zone.onUnstable.emit(null);  }}
hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
truefalse1false-1
onLeave

onLeave函数执行的时候,阐明MarcoTask的回调曾经执行结束,_nesting会执行一次自减操作。接下来又执行了checkStable函数。

传送门

function onLeave(zone: NgZonePrivate) {  zone._nesting--;  checkStable(zone);}

checkStable函数十分要害!每当执行到checkStable的时候,都是变更检测执行的要害。以至于这个函数的每一行都值得拿出来讲一下,我在代码中标记了序号,这样不便前面走读代码。

  1. checkStable既然是判断是否进行变更检测要害,那么1标识的if子句就是判断的要害。代码的意思大略就是,只有确保以后没有任何待执行的工作,同时以后状态为不稳固状态的时候才须要触发变更检测。
  2. 代码2标识了一个成对的_nesting自增、自减操作。这里这么做的起因是代码3这里抛出了事件,对该事件的订阅实际上也是一个异步工作。所以这里通过_nesting的自增、自减操作阐明这里是有一个异步工作的。
  3. 代码3就是变更检测的要害了,AppliactionRef会订阅onMicrotaskEmpty事件,每当onMicrotaskEmpty触发后,AppliactionRef就会执行一次变更检测。
  4. 代码4这里大家可能会有疑难,为什么在这里还要对hasPendingMicrotasks进行一次判断?这是因为在代码3这里,对onMicrotaskEmpty的订阅者有可能会在订阅回调中再执行一些异步工作,就像上面这样。此时,并不能保障在checkStable的过程中,不会有新的工作进入到待执行队列。所以这里,又对hasPendingMicrotasks的状态做了一次判断。确保在状态变为稳固之前,工作队列中不存在工作微工作。
zone.onMicrotaskEmpty.subscribe(() => {    Promise.resolve(0).then(console.log);});
  1. 代码5是对外触发一个状态稳固的事件,这个事件跟OnEnter函数中那个onUnstable绝对。然而你可能会好奇,这里为什么要在runOutsideAngular中执行。我这里解释下,仅代表个人见解。onStableonMicrotaskEmpty存在一样的问题,因为都是可察看对象,所以存在订阅者在回调继续执行异步工作的问题。如果在onStable的订阅中执行异步工作,那NgZone的状态马上有会变成非稳固的,这将会陷入一个有限的死循环中,NgZone会在稳固和不稳固状态之间来回切换,永不进行。所以这里应用runOutsideAngular,让zone.js放弃对这里的代码进行跟踪。这样,依据上一讲咱们学过的内容,runOutsideAngular中执行异步不会触发变更检测,当然也不会触发NgZone的状态变动。
  2. 扭转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会把本身状态批改为稳固态。

hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
truefalse0true-1
onHasTask

整个setTimeout跟踪的最初一步还是这个勾子,这次,勾子函数中会把hasPendingMacrotasks置为false。此时,几个状态曾经复原为最后的问题状态,Angular也在这个过程中执行了一次变更监测。

hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
falsefalse0true-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;     // ...   }   // ...  }},
hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
falsetrue0true-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

能够看到onInvokeonInvokeTask函数的内容是差不多的。onEnteronLeave的调用也基本一致,所以这里就不专门剖析了。

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);  }},

当这个函数执行完结后,几个状态值变动如下。

hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
falsetrue0true-1
onHasTask

最初一个执行的还是onHasTask函数,这个函数执行结束后,几个状态又回到初始状态。

hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
falsefalse0true-1

Event

Event的执行形式跟MacroTask和MicroTask都不太一样。还记得之前咱们在讲NgZone的5大状态的时候,有一个lastRequestAnimationFrameId始终没有用到。那么,在Event的处理过程中,咱们会看到它的作用。

onInvokeTask

Event的解决入口是onInvokeTask而不是onHasTaskonEnterdelegate.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. 代码1这里,咱们第一次见到对lastRequestAnimationFrameId的判断,当第一个Event到来时,这里的lastRequestAnimationFrameId还是初始值-1
hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
falsefalse1false-1
  1. zone.nativeRequestAnimationFrame的调用实际上调用的是Window.requestAnimationFrame。这里,NgZone实际上是心愿通过requestAnimationFrame收集这一帧内的所有事件,在这一帧完结后,再对立执行一次变更检测。requestAnimationFrame执行的返回值会赋值给lastRequestAnimationFrameId,这样,在接下来代码每次进入到代码1处的时候,函数会间接返回。
  2. updateMicroTaskStatus被用来更新微工作状态的。那么这里执行之后,状态值中的hasPendingMicrotasks会变为true。这里这么做是为了收集Event的时候能够阻塞微工作触发变更检测,这么做的起因是为了确保Event事件的执行程序不会被微工作打乱。这里要具体介绍又会有很大篇幅,感兴趣的能够本人看下这个issue;不想关注的能够先跳过这里。
hasPendingMacrotaskshasPendingMicrotasks_nestingisStablelastRequestAnimationFrameId
falsetrue1false一个正整数返回值
  1. 当以后帧执行结束、下一帧要执行的时候会调用一次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继承自ViewRefViewRef又继承自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有两个多月了。最初的最初,我也分享几点集体感触:

  1. 有人说zone.js是暴力美学,我个人感觉可能美的中央更多一些吧。作为Angular变更检测的外围,Angular的变更检测在三大框架中是独一份的存在。我感觉比起其它两个通过数据劫持和虚构Dom的形式进行数据绑定的形式,zone.js显得还是要温顺一些的。毕竟数据劫持是间接“净化”了数据的,而zone.js“革新”的是工具。我没法说谁更好,只是集体更偏差于后者。
  2. 截止到当初,我集体也没有齐全看完zone.js的源码,然而我心愿我会在后续的工作中继续关注这个产品。同时我也看到JiaLi(zone.js作者)为了他的作品一直地对zone.js进行改良。所以,请他加油,我心愿zone.js能够越来越好!不过话说回来,JiaLi想在Angular社区实现一个PR是不是太难了点啊。我看了他好多的批改,常常要等良久能力审核通过,有点疼爱他。
  3. 其实最开始的时候,我只是想本人学学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 开源社区奉献,你将播种:

间接的价值:

  1. 通过参加一个理论的跨端、跨框架组件库我的项目,学习最新的Vite+Vue3+TypeScript+Vitest技术
  2. 学习从 0 到 1 搭建一个本人的组件库的整套流程和方法论,包含组件库工程化、组件的设计和开发等
  3. 为本人的简历和职业生涯添彩,参加过优良的开源我的项目,这自身就是受面试官青眼的亮点
  4. 结识一群优良的、酷爱学习、酷爱开源的小伙伴,大家一起打造一个平凡的产品

久远的价值:

  1. 打造集体品牌,晋升集体影响力
  2. 造就良好的编码习惯
  3. 取得华为云 OpenTiny 团队的荣誉和定制小礼物
  4. 受邀加入各类技术大会
  5. 成为 PMC 和 Committer 之后还能参加 OpenTiny 整个开源生态的决策和长远规划,造就本人的治理和布局能力
  6. 将来有更多机会和可能

其余阐明

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