ReactiveCocoa v2.5 源码解析 之 架构总览

ReactiveCocoa是一个iOS中的函数式响应式编程框架,它受Functional Reactive Programming的启发,是Justin Spahr-SummersJosh Abernathy在开发GitHub for Mac过程中的一个副产品,它提供了一系列用来组合和转换值流的API

Mattt Thompson大神是这样评价ReactiveCocoa的:

Breaking from a tradition of covering Apple APIs exclusively,this edition of NSHipster will look at an open source project that exemplifies this brave new era for Objective-C.

他认为ReactiveCocoa打破了苹果API排他性的束缚,勇敢地开创了Objective-C的新纪元,具有划时代的意义。不得不说,这对于一个第三方框架来说,已经是非常高的评价了。

关于ReactiveCocoa的版本演进历程,简单介绍如下:

  • <= v2.5Objective-C

  • v3.xSwift 1.2

  • v4.xSwift 2.x

:本文所介绍的均为ReactiveCocoa v2.5版本中的内容,这是Objective-C最新的稳定版本。另外,本文的目录结构如下:

  • 简介

  • 信号源

    • RACStream

    • RACSignal

    • RACSubject

    • RACSequence

  • 订阅者

    • RACSubscriber

    • RACMulticastConnection

  • 调度器

    • RACScheduler

  • 清洁工

    • RACDisposable

  • 总结

  • 参考链接

简介

ReactiveCocoa是一个非常复杂的框架,在正式开始介绍它的核心组件前,我们先来看看它的类图,以便从宏观上了解它的层次结构:

从上面的类图中,我们可以看出,ReactiveCocoa主要由以下四大核心组件构成:

  • 信号源:RACStream及其子类;

  • 订阅者:RACSubscriber的实现类及其子类;

  • 调度器:RACScheduler及其子类;

  • 清洁工:RACDisposable及其子类。

其中,信号源又是最核心的部分,其他组件都是围绕它运作的。

对于一个应用来说,绝大部分的时间都是在等待某些事件的发生或响应某些状态的变化,比如用户的触摸事件、应用进入后台、网络请求成功刷新界面等等,而维护这些状态的变化,常常会使代码变得非常复杂,难以扩展。而ReactiveCocoa给出了一种非常好的解决方案,它使用信号来代表这些异步事件,提供了一种统一的方式来处理所有异步的行为,包括代理方法、block回调、target-action机制、通知、KVO等:

//代理方法
[[self
rac_signalForSelector:@selector(webViewDidStartLoad:)
fromProtocol:@protocol(UIWebViewDelegate)]
subscribeNext:^(idx){
//实现webViewDidStartLoad:代理方法
}];

//target-action
[[self.avatarButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(UIButton*avatarButton){
//avatarButton被点击了
}

//通知
[[[NSNotificationCenterdefaultCenter]
rac_addObserverForName:kReachabilityChangedNotificationobject:nil]
subscribeNext:^(NSNotification*notification){
//收到kReachabilityChangedNotification通知
}];

//KVO
[RACObserve(self,username)subscribeNext:^(NSString*username){
//用户名发生了变化
}];

然而,这些还只是ReactiveCocoa的冰山一角,它真正强大的地方在于我们可以对这些不同的信号进行任意地组合和链式操作,从最原始的输入input开始直至得到最终的输出output为止:

[[[RACSignal
combineLatest:@[RACObserve(self,username),RACObserve(self,password)]
reduce:^(NSString*username,NSString*password){
return@(username.length>0&&password.length>0);
}]
distinctUntilChanged]
subscribeNext:^(NSNumber*valid){
if(valid.boolValue){
//用户名和密码合法,登录按钮可用
}else{
//用户名或密码不合法,登录按钮不可用
}
}];

因此,对于ReactiveCocoa来说,我们可以毫不夸张地说,阻碍它发挥的瓶颈就只剩下你的想象力了。

信号源

ReactiveCocoa中,信号源代表的是随着时间而改变的值流,这是对ReactiveCocoa最精准的概括,订阅者可以通过订阅信号源来获取这些值:

Streams of values over time.

你可以把它想象成水龙头中的水,当你打开水龙头时,水源源不断地流出来;你也可以把它想象成电,当你插上插头时,电静静地充到你的手机上;你还可以把它想象成运送玻璃珠的管道,当你打开阀门时,珠子一个接一个地到达。这里的水、电、玻璃珠就是我们所需要的值,而打开水龙头、插上插头、打开阀门就是订阅它们的过程。

RACStream

RACStreamReactiveCocoa中最核心的类,代表的是任意的值流,它是整个ReactiveCocoa得以建立的基石,下面是它的继承结构图:

事实上,RACStream是一个抽象类,通常情况下,我们并不会去实例化它,而是直接使用它的两个子类RACSignalRACSequence。那么,问题来了,为什么RACStream会被设计成一个抽象类?或者说它的抽象过程是以什么作为依据的呢?

是的,没错,看过我上一篇文章《Functor、Applicative 和 Monad》的同学,应该已经知道了,RACStream就是以Monad的概念为依据进行设计的,它代表的就是一个Monad

///Anabstractclassrepresentinganystreamofvalues.
///
///Thisclassrepresentsamonad,uponwhichmanystream-basedoperationscan
///bebuilt.
///
///WhensubclassingRACStream,onlythemethodsinthemain@interfacebodyneed
///tobeoverridden.
@interfaceRACStream:NSObject

///Lifts`value`intothestreammonad.
///
///Returnsastreamcontainingonlythegivenvalue.
+(instancetype)return:(id)value;

///Lazilybindsablocktothevaluesinthereceiver.
///
///Thisshouldonlybeusedifyouneedtoterminatethebindearly,orclose
///oversomestate.-flattenMap:ismoreappropriateforallothercases.
///
///block-AblockreturningaRACStreamBindBlock.Thisblockwillbeinvoked
///eachtimetheboundstreamisre-evaluated.Thisblockmustnotbe
///nilorreturnnil.
///
///Returnsanewstreamwhichrepresentsthecombinedresultofalllazy
///applicationsof`block`.
-(instancetype)bind:(RACStreamBindBlock(^)(void))block;

@end

有了Monad作为基石后,许多基于流的操作就可以被建立起来了,比如mapfilterzip等。

RACSignal

RACSignal代表的是未来将会被传送的值,它是一种push-driven的流。RACSignal可以向订阅者发送三种不同类型的事件:

  • nextRACSignal通过next事件向订阅者传送新的值,并且这个值可以为nil

  • errorRACSignal通过error事件向订阅者表明信号在正常结束前发生了错误;

  • completedRACSignal通过completed事件向订阅者表明信号已经正常结束,不会再有后续的值传送给订阅者。

注意ReactiveCocoa中的值流只包含正常的值,即通过next事件传送的值,并不包括errorcompleted事件,它们需要被特殊处理。通常情况下,一个信号的生命周期是由任意个next事件和一个error事件或一个completed事件组成的。

从前面的类图中,我们可以看出,RACSignal并非只有一个类,事实上,它的一系列功能是通过类簇来实现的。除去我们将在下节介绍的RACSubject及其子类外,RACSignal还有五个用来实现不同功能的私有子类:

  • RACEmptySignal:空信号,用来实现RACSignal+empty方法;

  • RACReturnSignal:一元信号,用来实现RACSignal+return:方法;

  • RACDynamicSignal:动态信号,使用一个block来实现订阅行为,我们在使用RACSignal+createSignal:方法时创建的就是该类的实例;

  • RACErrorSignal:错误信号,用来实现RACSignal+error:方法;

  • RACChannelTerminal:通道终端,代表RACChannel的一个终端,用来实现双向绑定。

对于RACSignal类簇来说,最核心的方法莫过于-subscribe:了,这个方法封装了订阅者对信号源的一次订阅过程,它是订阅者与信号源产生联系的唯一入口。因此,对于RACSignal的所有子类来说,这个方法的实现逻辑就代表了该子类的具体订阅行为,是区分不同子类的关键所在。同时,这也是为什么RACSignal中的-subscribe:方法是一个抽象方法,并且必须要让子类实现的原因:

-(RACDisposable*)subscribe:(id)subscriber{
NSCAssert(NO,@"Thismethodmustbeoverriddenbysubclasses");
returnnil;
}

RACSubject

RACSubject代表的是可以手动控制的信号,我们可以把它看作是RACSignal的可变版本,就好比NSMutableArrayNSArray的可变版本一样。RACSubject继承自RACSignal,所以它可以作为信号源被订阅者订阅,同时,它又实现了RACSubscriber协议,所以它也可以作为订阅者订阅其他信号源,这个就是RACSubject为什么可以手动控制的原因:

根据官方的Design Guidelines中的说法,我们应该尽可能少地使用它。因为它太过灵活,我们可以在任何时候任何地方操作它,所以一旦过度使用,就会使代码变得非常复杂,难以理解。

根据我的实际使用经验,在MVVM中使用RACSubject可以非常方便地实现统一的错误处理逻辑。比如,我们可以在viewModel的基类中声明一个RACSubject类型的属性errors,然后在viewController的基类中编写统一的错误处理逻辑:

[self.viewModel.errorssubscribeNext:^(NSError*error){
//错误处理逻辑
}

此时,假设在某个界面的viewModel中有三个用来请求远程数据的命令,分别是requestReadmeMarkdownCommandrequestBlobCommandrequestReadmeHTMLCommand,那么这个界面的错误处理逻辑就可以这么写:

[[RACSignal
merge:@[
self.requestReadmeMarkdownCommand.errors,self.requestBlobCommand.errors,self.requestReadmeHTMLCommand.errors
]]
subscribe:self.errors];

另外,RACSubject也有三个用来实现不同功能的子类:

  • RACGroupedSignal:分组信号,用来实现RACSignal的分组功能;

  • RACBehaviorSubject:重演最后值的信号,当被订阅时,会向订阅者发送它最后接收到的值;

  • RACReplaySubject:重演信号,保存发送过的值,当被订阅时,会向订阅者重新发送这些值。

RACSubject的功能非常强大,但是太过灵活,也正是因为如此,我们只有在迫不得已的情况下才会使用它。

RACSequence

RACSequence代表的是一个不可变的值的序列,与RACSignal不同,它是pull-driven类型的流。从严格意义上讲,RACSequence并不能算作是信号源,因为它并不能像RACSignal那样,可以被订阅者订阅,但是它与RACSignal之间可以非常方便地进行转换。

从理论上说,一个RACSequence由两部分组成:

  • head:指的是序列中的第一个对象,如果序列为空,则为nil

  • tail:指的是序列中除第一个对象外的其它所有对象,同样的,如果序列为空,则为nil

事实上,一个序列的tail仍然是一个序列,如果我们将序列看作是一条毛毛虫,那么headtail可表示如下:

同样的,一个序列的tail也可以看作是由headtail组成,而这个新的tail又可以继续看作是由headtail组成,这个过程可以一直进行下去。而这个就是RACSequence得以建立的理论基础,所以一个RACSequence子类的最小实现就是headtail

///Representsanimmutablesequenceofvalues.Unlessotherwisespecified,the
///sequences'valuesareevaluatedlazilyondemand.LikeCocoacollections,///sequencescannotcontainnil.
///
///MostinheritedRACStreammethodsthatacceptablockwillexecutetheblock
///_atmost_onceforeachvaluethatisevaluatedinthereturnedsequence.
///Sideeffectsaresubjecttothebehaviordescribedin
///+sequenceWithHeadBlock:tailBlock:.
///
///Implementedasaclasscluster.Aminimalimplementationforasubclass
///consistssimplyof-headand-tail.
@interfaceRACSequence:RACStream///Thefirstobjectinthesequence,ornilifthesequenceisempty.
///
///Subclassesmustprovideanimplementationofthismethod.
@property(nonatomic,strong,readonly)idhead;

///Allbutthefirstobjectinthesequence,readonly)RACSequence*tail;

@end

总的来说,RACSequence存在的最大意义就是为了简化Objective-C中的集合操作:

Simplifying Collection Transformations: Higher-order functions like map,filter,fold/reduce are sorely missing from Foundation.

比如下面的代码:

NSMutableArray*results=[NSMutableArrayarray];
for(NSString*strinstrings){
if(str.length<2){
continue;
}

NSString*newString=[strstringByAppendingString:@"foobar"];
[resultsaddObject:newString];
}

可以用RACSequence来优雅地实现:

RACSequence*results=[[strings.rac_sequence
filter:^BOOL(NSString*str){
returnstr.length>=2;
}]
map:^(NSString*str){
return[strstringByAppendingString:@"foobar"];
}];

因此,我们可以非常方便地使用RACSequence来实现集合的链式操作,直到得到你想要的最终结果为止。除此之外,使用RACSequence的另外一个主要好处是,RACSequence中包含的值在默认情况下是懒计算的,即只有在真正用到的时候才会被计算,并且只会计算一次。也就是说,如果我们只用到了一个RACSequence中的部分值的时候,它就在不知不觉中提高了我们应用的性能。

同样的,RACSequence的一系列功能也是通过类簇来实现的,它共有九个用来实现不同功能的私有子类:

  • RACUnarySequence:一元序列,用来实现RACSequence+return:方法;

  • RACIndexSetSequence:用来遍历索引集;

  • RACEmptySequence:空序列,用来实现RACSequence+empty方法;

  • RACDynamicSequence:动态序列,使用blocks来动态地实现一个序列;

  • RACSignalSequence:用来遍历信号中的值;

  • RACArraySequence:用来遍历数组中的元素;

  • RACEagerSequence:非懒计算的序列,在初始化时立即计算所有的值;

  • RACStringSequence:用来遍历字符串中的字符;

  • RACTupleSequence:用来遍历元组中的元素。

RACSequence为类簇提供了统一的对外接口,对于使用它的客户端代码来说,完全不需要知道私有子类的存在,很好地隐藏了实现细节。另外,值得一提的是,RACSequence实现了快速枚举的协议NSFastEnumeration,在这个协议中只声明了一个看上去非常抽筋的方法:

-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)stateobjects:(id__unsafe_unretained[])buffercount:(NSUInteger)len;

有兴趣的同学,可以看看RACSequence中的相关实现,我们将会在后续的文章中进行介绍。因此,我们也可以直接使用for in来遍历一个RACSequence

订阅者

现在,我们已经知道信号源是什么了,为了获取信号源中的值,我们需要对信号源进行订阅。在ReactiveCocoa中,订阅者是一个抽象的概念,所有实现了RACSubscriber协议的类都可以作为信号源的订阅者。

RACSubscriber

RACSubscriber协议中,声明了四个必须实现的方法:

///RepresentsanyobjectwhichcandirectlyreceivevaluesfromaRACSignal.
///
///Yougenerallyshouldn'tneedtoimplementthisprotocol.+[RACSignal
///createSignal:],RACSignal'ssubscriptionmethods,orRACSubjectshouldwork
///formostuses.
///
///Implementorsofthisprotocolmayreceivemessagesandvaluesfrommultiple
///threadssimultaneously,andsoshouldbethread-safe.Subscriberswillalso
///beweaklyreferencedsoimplementationsmustallowthat.
@protocolRACSubscriber@required

///Sendsthenextvaluetosubscribers.
///
///value-Thevaluetosend.Thiscanbe`nil`.
-(void)sendNext:(id)value;

///Sendstheerrortosubscribers.
///
///error-Theerrortosend.Thiscanbe`nil`.
///
///Thisterminatesthesubscription,andinvalidatesthesubscriber(suchthat
///itcannotsubscribetoanythingelseinthefuture).
-(void)sendError:(NSError*)error;

///Sendscompletedtosubscribers.
///
///Thisterminatesthesubscription,andinvalidatesthesubscriber(suchthat
///itcannotsubscribetoanythingelseinthefuture).
-(void)sendCompleted;

///Sendsthesubscriberadisposablethatrepresentsoneofitssubscriptions.
///
///Asubscribermayreceivemultipledisposablesifitgetssubscribedto
///multiplesignals;however,anyerrororcompletedeventsmustterminate_all_
///subscriptions.
-(void)didSubscribeWithDisposable:(RACCompoundDisposable*)disposable;

@end

其中-sendNext:-sendError:-sendCompleted分别用来从RACSignal接收nexterrorcompleted事件,而-didSubscribeWithDisposable:则用来接收代表某次订阅的disposable对象。

订阅者对信号源的一次订阅过程可以抽象为:通过RACSignal-subscribe:方法传入一个订阅者,并最终返回一个RACDisposable对象的过程:

注意:在ReactiveCocoa中并没有专门的类RACSubscription来代表一次订阅,而间接地使用RACDisposable来充当这一角色。因此,一个RACDisposable对象就代表着一次订阅,并且我们可以用它来取消这次订阅,详细内容将会在下面的章节中进行介绍。

除了RACSignal的子类外,还有两个实现了RACSubscriber协议的类,如下图所示:

其中,RACSubscriber类的名字与RACSubscriber协议的名字相同,这跟Objective-C中的NSObject类的名字与NSObject协议的名字相同是一样一样的,除了名字相同外,然并卵。通常来说,RACSubscriber类充当的角色就是信号源的真正订阅者,它老老实实地实现了RACSubscriber协议。

既然RACSubscriber类就是真正的订阅者,那么RACPassthroughSubscriber类又是干嘛用的呢?原来,在ReactiveCocoa中,一个订阅者是可以订阅多个信号源的,也就是说它会拥有多个RACDisposable对象,并且它可以随时取消其中任何一个订阅。为了实现这个功能,ReactiveCocoa就引入了RACPassthroughSubscriber类,它是RACSubscriber类的一个装饰器,封装了一个真正的订阅者RACSubscriber对象,它负责转发所有事件给这个真正的订阅者,而当此次订阅被取消时,它就会停止转发:

-(void)sendNext:(id)value{
if(self.disposable.disposed)return;
[self.innerSubscribersendNext:value];
}

-(void)sendError:(NSError*)error{
if(self.disposable.disposed)return;
[self.innerSubscribersendError:error];
}

-(void)sendCompleted{
if(self.disposable.disposed)return;
[self.innerSubscribersendCompleted];
}

事实上,在ReactiveCocoa中,我们倾向于隐藏订阅者,因为外界根本不需要知道订阅者的存在,这是内部的实现细节。这样做的主要目的是进一步简化信号源的订阅逻辑,客户端代码只需要关心它所需要的值就可以了,根本不需要关心内部的订阅过程。

RACMulticastConnection

通常来说,我们在订阅一个信号源的过程中可能会产生副作用或者消耗比较大的资源,比如修改全局变量、发送网络请求等。这个时候,我们往往需要让多个订阅者之间共享一次订阅,就好比我们读高中时,多个好朋友一起订阅一份英语周报,然后只要出一份钱,是一个道理。这就是ReactiveCocoa中引入RACMulticastConnection类的原因。

RACMulticastConnection通过一个标志_hasConnected来保证只对sourceSignal订阅一次,然后对外暴露一个RACSubject类型的signal供外部订阅者订阅。这样一来,不管外部订阅者对signal订阅多少次,我们对sourceSignal的订阅至多只会有一次:

:了解RACMulticastConnection的实现原理,对于我们后面理解-replayreplayLastreplayLazily等方法非常有帮助。

调度器

有了信号源和订阅者,我们还需要由调度器来统一调度订阅者订阅信号源的过程中所涉及到的任务,这样才能保证所有的任务都能够合理有序地执行。

RACScheduler

RACSchedulerReactiveCocoa中就是扮演着调度器的角色,本质上,它就是用GCD的串行队列来实现的,并且支持取消操作。是的,在ReactiveCocoa中,并没有使用到NSOperationQueueNSRunloop等技术,RACScheduler也只是对GCD的简单封装而已。

同样的,RACScheduler的一系列功能也是通过类簇来实现的,除了用来测试的子类外,总共还有四个私有子类:

咋看之下,RACScheduler的儿子貌似还不少,但是真正出力干活的却真心不多,主要就是RACTargetQueueScheduler子类:

  • RACImmediateScheduler:立即执行调度的任务,这是唯一一个支持同步执行的调度器;

  • RACQueueScheduler:一个抽象的队列调度器,在一个GCD串行列队中异步调度所有任务;

  • RACTargetQueueScheduler:继承自RACQueueScheduler,在一个以一个任意的GCD队列为target的串行队列中异步调度所有任务;

  • RACSubscriptionScheduler:一个只用来调度订阅的调度器。

值得一提的是,在RACScheduler中有一个非常特殊的方法:

-(RACDisposable*)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock;

这个方法的作用非常有意思,它可以将递归调用转换成迭代调用,这样做的目的是为了解决深层次的递归调用可能会带来的堆栈溢出问题。

清洁工

正如我们前面所说的,在订阅者订阅信号源的过程中,可能会产生副作用或者消耗一定的资源,所以当我们在取消订阅或者完成订阅时,我们就需要做一些资源回收和垃圾清理的工作。

RACDisposable

RACDisposableReactiveCocoa中就充当着清洁工的角色,它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法-dispose,调用这个方法就会执行相应的清理工作,这有点类似于NSObject-dealloc方法。RACDisposable总共有四个子类,它的继承结构图如下:

  • RACSerialDisposable:作为disposable的容器使用,可以包含一个disposable对象,并且允许将这个disposable对象通过原子操作交换出来;

  • RACKVOTrampoline:代表一次KVO观察,并且可以用来停止观察;

  • RACCompoundDisposable:跟RACSerialDisposable一样,RACCompoundDisposable也是作为disposable的容器使用。不同的是,它可以包含多个disposable对象,并且支持手动添加和移除disposable对象,有点类似于可变数组NSMutableArray。而当一个RACCompoundDisposable对象被disposed时,它会调用其所包含的所有disposable对象的-dispose方法,有点类似于autoreleasepool的作用;

  • RACScopedDisposable:当它被dealloc的时候调用本身的-dispose方法。

咋看之下,RACDisposable的逻辑似乎有些复杂,不过换汤不换药,不管它们怎么换着花样玩,最终都只是为了能够在合适的时机调用disposable对象的-dispose方法,执行清理工作而已。

总结

至此,我们介绍完了ReactiveCocoa的四大核心组件,对它的架构有了宏观上的认识。它建立于Monad的概念之上,然后围绕其搭建了一系列完整的配套组件,它们共同支撑了ReactiveCocoa的强大功能。尽管,ReactiveCocoa是一个重型的函数式响应式框架,但是它并不会对我们现有的代码构成侵略性,我们完全可以在一个单独的类中使用它,哪怕只是简单的一行代码,也是没有问题的。所以,如果你对ReactiveCocoa感兴趣的话,不妨就从现在开始尝试吧,Let’s go !

PSMVVMReactiveCocoa是我用MVVM+RAC编写的一个开源应用,如果你有兴趣的话不妨clone下来看看ReactiveCocoa的具体实践吧。

参考链接

https://github.com/ReactiveCocoa/ReactiveCocoa/tree/v2.5
https://github.com/ReactiveCocoa/ReactiveCocoa/blob/v2.5/Documentation/FrameworkOverview.md
https://github.com/ReactiveCocoa/ReactiveCocoa/blob/v2.5/Documentation/DesignGuidelines.md#avoid-using-subjects-when-possible
http://nshipster.com/reactivecocoa/
http://nathanli.cn/2015/08/27/reactivecocoa2-%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/
http://blog.devtang.com/blog/2014/02/11/reactivecocoa-introduction/
http://m.oschina.net/blog/294178

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom