經歷這麼多版本,RxJava本質上不變的是什麼?
本文作者
作者:
Amter
連結:
https://juejin。cn/post/6898862722357100557
本文由作者授權釋出。
0前言
RxJava的版本從釋出到現在,已經經歷了好多個版本了,雖然原始碼在不斷的修改,但是不知你有沒有發現,RxJava的主體架構還是沒有變化,為什麼呢?可以說是RxJava架構決定了它的特性,比如程式碼邏輯的簡潔以及運算子帶來極強的擴充套件能力,這些在RxJava迭代了這麼多個版本之後,這些特性,沒有減少,反而大大的增強了,這個特性,就是響應式程式設計,那麼接下來,就來講講RxJava為什麼會有這種特性,以及帶來其特性不變的本質是啥!
本文主要講解RxJava的架構思想,不會涉及到大量的原始碼分析,請放心食用,文章篇幅較長,建議收藏,慢慢品嚐!
implementation ‘io。reactivex。rxjava2:rxjava:2。1。4’
implementation ‘io。reactivex。rxjava2:rxandroid:2。0。2’
1
RxJava不變的是什麼?
不知你是否會有這樣的焦慮,框架的更新有時候會讓人感嘆!
雖然RxJava的版本更新沒有那麼頻繁,但是每次的更新,總會讓人感覺,之前剛看的原始碼,是不是前功盡棄了,原始碼一更新,我又得繼續去學習;
但是不知你是否有這樣想過,框架一直在更新,底層有沒有不變的東西,今天很高興告訴你,RxJava框架有,那麼這個不變的東西是什麼呢?
架構
沒錯,RxJava雖然迭代了幾個版本,但是其底層的架構還是沒有怎麼變動,為什麼呢?因為其特性就決定了它的架構不會有多大的變化;
它的特性有哪些?簡單明瞭的程式碼邏輯,強大的運算子,而這些特性正是響應式程式設計的思想;
就好比一棟房子,其特性有抗震,防風,那麼其底層的架構就是必然是按著抗震和防風的特性去建造的,而一旦建造成功,其具有的特性,不會跟著房子的裝修而變化,那麼其底層的架構也是同樣的道理;
那麼你想知道是什麼架構來實現這種特性的嗎?
別急,下面我們先來講一講RxJava涉及到的設計模式,為什麼設計模式這麼重要呢?
因為設計模式是架構的基礎,我們怎麼設計才能讓這個架構具有某種特性,這個和設計模式分不開;
2
RxJava的觀察者模式
2.2、RxJava的觀察者模式是怎樣的呢?
RxJava的觀察者模式,是擴充套件的觀察者模式,為啥要叫擴充套件呢?因為它和普通的觀察者模式不太一樣,擴充套件的觀察者模式,不止一個通知觀察者的方法,它有好幾個,下面我們來看看它的實現原理吧!
首先先來看一下RxJava觀察者模式涉及到的幾個類:
Observable:被觀察者
Observer:觀察者
Event:被觀察者通知觀察者的事件
Subscribe:訂閱
看完下面這個圖,你的心目中是不是已經對RxJava的觀察者模式一目瞭然了;
下面我們來看一RxJava的事件型別,這個事件是被觀察者用來通知觀察者的,也就是Event,而Event可以分為下面幾種型別,這個我們瞭解一下即可;
Next:常規事件,可以傳遞各種各樣的資料;
Complete:結束事件,當觀察者接收到結束事件後,就不會再接收後續被觀察者傳送來的事件;
Error:異常事件,當被觀察者傳送異常事件後,那麼其他的事件就不會再繼續傳送了;
下面我用示例程式碼來講一次RxJava的觀察者模式;
首先定義一個觀察者Observer:
publicabstractclassObserver
// 和被觀察者訂閱後,會回撥這個方法;
publicabstractvoidonSubscribe(Emitter emitter);
// 傳遞常規事件,用於傳遞資料
publicabstractvoidonNext(T t);
// 傳遞異常事件
publicabstractvoidonError(Throwable e);
// 傳遞結束事件
publicabstractvoidonComplete();
}
Observer類的方法很簡單,都是回撥,這裡有個新的介面類Emitter,這個Emitter是用來幹嘛的呢?
我們暫且把這個Emitter稱為發射器,主要用於發射事件;
publicinterfaceEmitter
voidonNext(T value);
voidonError(Throwable error);
voidonComplete();
}
實現邏輯就是透過包裝Observer,裡面最終是透過Observer來進行呼叫的,來看看這個類有哪些方法;
publicclassCreateEmitter
final Observer
CreateEmitter(Observer
this。observer = observer;
}
@Override
publicvoidonNext(T t){
observer。onNext(t);
}
@Override
publicvoidonError(Throwable error){
observer。onError(error);
}
@Override
publicvoidonComplete(){
observer。onComplete();
}
}
裡面的具體實現就是透過Observer物件來進行呼叫;
下面我們來看一下被觀察者Observable是怎麼實現的;
publicabstractclassObservable
// 實現訂閱的邏輯
publicvoidsubscribe(Observer
// 透過將傳進來的observer包裝成CreateEmitter,用於回撥
CreateEmitter
// 回撥訂閱成功的方法
observer。onSubscribe(emitter);
// 回調發射器emitter
subscribe(emitter);
}
// 訂閱成功後,進行回撥
publicabstractvoidsubscribe(Emitter
}
這個類的邏輯很簡單,就兩步,第一步,進行訂閱,第二步,回撥Emitter物件,用於發射事件;
那麼我們來看看怎麼呼叫吧;
privatevoidobserver(){
// 第一步,建立被觀察者
Observable
@Override
publicvoidsubscribe(Emitter
emitter。onNext(“第一次”);
emitter。onNext(“第二次”);
emitter。onNext(“第三次”);
emitter。onComplete();
}
};
// 第二步,建立觀察者
Observer
@Override
publicvoidonSubscribe(Emitter emitter){
Log。i(“TAG”, “ onSubscribe ”);
}
@Override
publicvoidonNext(String s){
Log。i(“TAG”, “ onNext s:” + s);
}
@Override
publicvoidonError(Throwable e){
Log。i(“TAG”, “ onError e:” + e。toString());
}
@Override
publicvoidonComplete(){
Log。i(“TAG”, “ onComplete ”);
}
};
// 第三步,被觀察者訂閱觀察者
observable。subscribe(observer);
}
這裡是使用邏輯很簡單,分為三步:
第一步:建立被觀察者Observable;
第二步:建立觀察者Observer;
第三步:被觀察者Observable訂閱Observer;
當訂閱成功之後,被觀察者的subscribe方法裡面,就可以透過發射器發射各種事件,最終在觀察者的方法裡進行回撥;
RxJava也是觀察者和被觀察者訂閱的過程,只是被觀察者有變化的時候,是透過發射器來發射各種事件的,這樣就不侷限於一種事件了;
3
RxJava的裝飾者模式
3.1、裝飾者模式
什麼是裝飾者模式?
要理解這個模式其實不難,我們從“裝飾”這兩個字就可以看出,這個模式用於裝飾用的,至於怎麼裝飾,且聽我細細道來;
比如說我現在有一個手機,我怎麼在不改變這個手機原有的結構,而讓其具有防摔的功能,當然你也可以說我的手機是諾基亞,從幾樓往下丟,手機都不帶磕碰的,但是現實是,我們使用的手機,並不是那麼的抗摔;
那麼我要怎麼讓其具有更強的抗摔能力呢?
相信答案你已經很清楚了,就是套手機殼,貼膜,而這兩個動作,是在沒有改變手機原有的結構上,讓其具有了抗摔的功能,而這個過程可以稱為裝飾,而裝飾者模式的原理也是如此;
在不改變其原有結構的基礎上,為其新增額外的功能,是作為其原有結構的包裝,這個過程稱為裝飾;
那麼在程式碼裡是怎麼體現出來的呢?
同理,假如我們要在一個類上新增新功能,而不修改其原有的邏輯,那麼我們這時候就可以使用裝飾者模式進行封裝,具體怎麼做,我們下面來看看;
還是以上面為例子,定義一個外觀的介面Appearance,有一個抽象的方法,結構structure;
publicinterfaceAppearance {
voidstructure();
}
然後再定義一個手機類Phone實現這個介面,這個手機的結構有玻璃後蓋,金屬邊框等屬性,如下:
publicclassPhoneimplementsAppearance{
@Override
publicvoidstructure(){
// 手機屬性:玻璃後蓋,金屬邊框
Log。i(“TAG”, “手機的屬性:玻璃後蓋,金屬邊框”);
}
}
好了,接下來我們要讓這個手機變得更堅固,但是又不能改變手機原有的結構,那麼我們要怎麼做呢?
如果不能修改其原有的結構,那麼我可以透過裝飾來對手機進行包裝,先定義一個手機的包裝類,用來包裝手機,命名為PhoneDecorator,實現了Appearance介面,在這裡透過構造方法傳進來的外觀類Appearance,呼叫了外觀類的structure方法,保證其原有的功能實現;
簡單來說,這個類的作用,就是為了實現原有類的功能;
publicabstractclassPhoneDecoratorimplementsAppearance{
protected Appearance appearance;
publicPhoneDecorator(Appearance appearance){
this。appearance = appearance;
}
@Override
publicvoidstructure(){
appearance。structure();
}
}
那麼接下來就是包裝類的具體實現了,定義一個套手機殼功能的類PhoneShell,功能實現就是在原有功能的基礎上,給手機套上手機殼,來看看具體實現吧;
publicclassPhoneShellextendsPhoneDecorator{
publicPhoneShell(Appearance appearance){
super(appearance);
}
@Override
publicvoidstructure(){
super。structure();
Log。i(“TAG”, “給手機套上手機殼”);
}
}
這裡的實現很簡單,繼承手機的包裝類,在structure裡面去實現“套上手機殼”的操作;
那麼套手機殼的類有了,還差一個貼膜的類,和手機殼一樣,我們也來定義一個貼膜的包裝類PhoneCover,看看具體實現;
publicclassPhoneCoverextendsPhoneDecorator{
publicPhoneCover(Appearance appearance){
super(appearance);
}
@Override
publicvoidstructure(){
super。structure();
Log。i(“TAG”, “給手機貼上鋼化膜”);
}
}
這裡的實現和上面的套手機殼的操作一樣,那麼到這裡兩個包裝類都寫好了,我們來看看怎麼呼叫吧;
privatevoiddecorator(){
// 建立一個手機
Phone phone = new Phone();
// 給手機套上手機殼
PhoneShell phoneShell = new PhoneShell(phone);
// 給手機貼上鋼化膜
PhoneCover phoneCover = new PhoneCover(phoneShell);
// 最終的手機結構
phoneCover。structure();
}
使用起來很簡單,將需要包裝的類,作為構造引數,傳入到包裝類裡面,就可以讓這個類具有包裝的功能,比如這裡,將手機Phone傳入到手機殼PhoneShell的類裡面,那麼手機就有套上手機殼的功能了;
同理,再將套上手機殼的手機PhoneShell類,傳入到貼膜的類PhoneCover裡面,那麼這個手機就具有了貼膜的功能,最後再呼叫一下結構的方法structure,那麼就可以看到這個手機已經被套上手機殼,並且貼上膜了;
最終包裝後的結構如下:
到這裡你有沒有發現,裝飾者對於功能的擴充套件並不是使用的繼承的方法,為什麼?
因為繼承隨著功能的增加,會導致子類越來越膨脹,而裝飾者模式的雙方可以隨意擴充套件,不會相互耦合;
那麼RxJava的裝飾者模式是怎麼實現的呢?
且聽我細細道來;
3.2、RxJava的裝飾者模式是怎麼實現的呢?
RxJava的裝飾者主要是用於實現被觀察者Observable和觀察者Observer的包裝,為什麼要進行包裝呢?
從上面我們可以知道,裝飾者模式,是基礎功能上,不修改其原有的邏輯,進行擴充套件;
那麼為什麼RxJava的被觀察者需要這種特性呢?
假如我想實現這樣一種功能,在子執行緒獲取資料,然後切換到主執行緒進行資料的賦值,正常情況下我們會這樣做,先在子執行緒獲取資料,然後再透過Handler的post方法,切到主執行緒;
但是如果我想在子執行緒獲取到資料後,然後再對資料做一下轉化處理,最後再回調給主執行緒呢?
如果按照常規的實現邏輯,這樣的程式碼就會很混亂,作為一名有追求的工程師,我們是無法忍受這樣的寫法的;
那麼有沒有什麼方式可以變的優雅一些呢?
答案是:有的;
RxJava透過裝飾者模式+觀察者模式設計出了鏈式呼叫的效果,這樣程式碼邏輯清晰,也方便維護;
比如下面這樣的鏈式呼叫邏輯:
Observable。create(new ObservableOnSubscribe
@Override
publicvoidsubscribe(@NonNull ObservableEmitter
// 發射器發射資料
emitter。onNext(“1”);
emitter。onNext(“2”);
emitter。onNext(“3”);
emitter。onComplete();
}
})。subscribeOn(Schedulers。io())
。observeOn(AndroidSchedulers。mainThread())
。subscribe(new Observer
@Override
publicvoidonSubscribe(@NonNull Disposable d){
}
@Override
publicvoidonNext(@NonNull Integer s){
}
@Override
publicvoidonError(@NonNull Throwable e){
}
@Override
publicvoidonComplete(){
}
});
這樣的鏈式呼叫邏輯是不是很清晰!
下面我們來看看RxJava的裝飾者模式具體是怎麼實現的;
我們以上面講的手機模型為例子,來一步步拆解RxJava的裝飾器模式;
(1) 被觀察者Observable:
第一步:要有一個抽象介面對應上面的Appearance介面,而RxJava的介面是ObservableSource,裡面有一個方法,subscribe,用於和觀察者進行訂閱;
publicinterfaceObservableSource
/**
* Subscribes the given Observer to this ObservableSource instance。
* @param observer the Observer, not null
* @throws NullPointerException if {@code observer} is null
*/
voidsubscribe(@NonNull Observer<? super T> observer);
}
第二步:要有一個包裝類,實現了ObservableSource介面,對應上面的PhoneDecorator包裝類,RxJava的包裝類是Observable,和PhoneDecorator同理,實現了對應的介面,並且在subscribe方法裡面透過呼叫抽象方法subscribeActual,來對觀察者進行訂閱;
publicabstractclassObservable
@Override
publicfinalvoidsubscribe(Observer<? super T> observer){
。。。
subscribeActual(observer);
。。。
}
protectedabstractvoidsubscribeActual(Observer<? super T> observer);
}
第三步:接下來就是具體的包裝類了,和上面一樣具有包裝功能的PhoneShell,PhoneCover等等,而RxJava的包裝類,非常強大,先看個圖;
有一百多個包裝類,由此可以看出RxJava的強大,當然也是裝飾者模式給與其易擴充套件的特性;
上面看完被觀察者的邏輯,下面來看看觀察者的裝飾者邏輯;
(2)觀察者Observer:
第一步:要有一個抽象介面對應上面的Appearance介面,而RxJava的介面是Emitter和Observer,裡面有好幾個方法,基本一樣,onNext,onError,onComplete,用於被觀察者進行回撥;
第二步:要有一個包裝類,實現了Emitter或者Observer介面,觀察者比較特殊,沒有一個基礎的包裝類,而是直接封裝了很多的包裝類;
也是有100多個包裝類;
那麼到這裡你是否會有疑惑,被觀察者和觀察者這麼多的包裝類,到底要咋用?
從上面的例子,可以知道,包裝類是有一個過程的,有的是在建立的時候就進行包裝了,而有的是在呼叫的時候進行包裝的;
而RxJava的被觀察者是在建立的時候進行包裝的,比如上面的示例程式碼,第一步,透過Observable。create方法,裡面透過建立ObservableCreate物件,進行了第一層的包裝,此時的結構如下:
第二步的subscribeOn方法呼叫時,進行了第二層的包裝,此時的結構如下:
第三步的observeOn方法呼叫時,進行了第四層的包裝,那麼結構就是下面這樣樣子:
最終呼叫訂閱的方法的時候,已經進行了四次包裝,可以這麼理解,每調動一次運算子,那麼就會進行一層被觀察者的包裝;
這樣包裝的好處是什麼呢?
前面我們講過,裝飾者模式,是為了在不改變其原有的基礎上,新增額外的功能;
這進行了這麼幾次包裝的作用,就是為了新增額外的功能,那麼來大概看一下每一層新增的額外功能有啥?
3.3、被觀察者的subscribe方法
當我們最終呼叫了subscribe方法之後,我們會從最外層的包裝類,一步一步的往裡面呼叫;
上面我們知道,被觀察者的包裝,是在subscribeActual方法裡,進行實現的,那麼我們來看看這幾個包裝類的subscribeActual方法的邏輯;
先來看最外層的包裝,來看一下subscribeActual大概的邏輯:
這裡的source是上一層包裝類的例項,也就是ObservableSubscribeOn;
這裡會將觀察者進行一層包裝,也就是ObserveOnObserver,這個ObserveOnObserver的包裝,裡面實現了執行緒切換的邏輯,具體邏輯在onNext裡面;
為什麼要這麼做呢?因為這就是裝飾者模式帶來的好處,這個onNext的被觀察者通知觀察者會回撥的方法,然後這裡透過包裝類,在裡面實現了額外的執行緒切換的功能,這裡會切換到主執行緒去執行;
此時,觀察者的結構是這樣的:
下面我們來看倒數第二層的包裝類的subscribeActual方法的邏輯,倒數第二個包裝類是ObservableSubscribeOn;
這一層包裝類的subscribeActual方法又對觀察者做了一層包裝,也就是SubscribeOnObserver類,這個包裝類又實現了什麼功能呢?
這裡做了一些執行緒的釋放,這個我們下面再講;
包裝完之後觀察者的結構是這樣的:
讓我們回到被觀察者的實現邏輯,下面就呼叫了執行執行緒的方法,scheduleDirect,如果你傳進來的是子線成的執行緒排程器Scheduler,那麼SubscribeTask就會在子執行緒執行,而我們這裡傳的就是子執行緒;
在SubscribeTask,又呼叫了subscribe方法,這個source是上一層的包裝類,也就是ObservableCreate,那麼ObservableCreate的subscribeActual方法,就會在子執行緒執行了;
下面我們來看看ObservableCreate這個包裝類的subscribeActual方法;
這裡對觀察者做了一層包裝,也是CreateEmitter類,來看這個觀察者的包裝類又實現了什麼額外的功能呢?
這裡面主要實現了判斷執行緒是否釋放了,如果釋放了,那麼觀察者就不再進行回撥;
那麼這時候,觀察者的結構是這樣的:
接下來就呼叫了觀察者的onSubscribe方法,最終會回撥到觀察者Observer的onSubscribe方法;
然後下面就呼叫了source。subscribe(parent),這個source是我們建立的最原始的ObservableOnSubscribe,這裡會回撥到ObservableOnSubscribe的subscribe方法;
此時,我們上面的包裝類ObservableSubscribeOn,切換到子執行緒後,那麼我們的ObservableOnSubscribe的subscribe方法的執行也是在子執行緒;
3.4、被觀察者通知觀察者的事件是怎麼流向的呢?
然後我們在ObservableOnSubscribe的subscribe方法裡呼叫了發射器,發射字串,那麼這時候的呼叫邏輯是怎樣的呢?
從上面觀察者的結構來看,當發射器傳送事件時,會一層層的回撥對應的觀察者包裝類,從最外面一層開始;
上面我們知道ObservableEmitter類是CreateEmitter對觀察者的包裝,那麼這個onNext就會走CreateEmitter的onNext方法,上面我們知道這個方法只是做了判斷,最終還是回撥給上一層的包裝類的onNext方法;
再上一層的包裝類是SubscribeOnObserver,這個方法的onNext沒有對觀察者做任何處理;
那麼還是得繼續往上一層的包裝類進行檢視,上一層的包裝類是ObserveOnObserver,這個類的onNext方法,執行了執行緒的切換,最終切換到主執行緒執行;
那麼最終回撥到這裡之後,就是在主執行緒執行了;
4
總結
我們在建立被被觀察者的時候,會對被觀察者做一層包裝, 建立幾次就包裝幾次,然後再被觀察者呼叫subscribe方法時,一層層回撥被觀察者的subscribeActual方法,而在被觀察者的subscribeActual方法裡,會對觀察者做一層包裝;
也就是說被觀察者是在建立的時候進行包裝,然後在subscribeActual方法裡實現額外的功能;
而觀察者是在被觀察者呼叫subscribeActual方法裡進行包裝的,然後針對觀察者實現自己額外的功能;
下面我們來看一下流程圖:
那麼到這裡,RxJava底層架構是不是已經清晰明瞭了,總結起來就是觀察者模式+裝飾者模式;
透過裝飾者模式來包裝觀察者和被觀察者,然後在包裝類裡面實現額外的功能;
那麼最終的架構如下:
第一步:建立被觀察者時,或者使用運算子時,會對被觀察者進行包裝:
第二步:被觀察者訂閱觀察者,這時候會一層層的回撥被觀察者的包裝類的subscribeActual方法,然後對觀察者進行包裝;
此時,被觀察者的功能實現是在subscribeActual方法裡,而觀察者的實現是在包裝類裡;
第三步:被觀察者和觀察者不同的是,被觀察者是在訂閱成功之後,就執行了包裝類相應的功能,而觀察者是在事件回撥的時候,會在觀察者的包裝類裡實現對應的功能;
最終流程圖:
5
為什麼RxJava要這樣設計?
5.1、事務的概念
在開始之前,我們先來了解一個概念,“事務”;
什麼是事務?
事務,一般是指要做的或所做的事情。而在程式碼裡我們可以理解為一段程式碼邏輯;
而事務的關係,我們可以理解為業務邏輯之間的關係,有可能有關聯,也有可能沒有關聯;
比如我進入一個列表頁,這個列表頁的資料,需要從好幾個介面請求返回的,而請求返回後我還要根據資料和網路來展示對應的頁面,比如列表頁或者無資料,無網路的頁面,那麼我展示的邏輯就是根據列表頁返回的邏輯來展示的;
而這幾個請求,我姑且稱為事務A,事務B,事務C,事務D;
事務A,事務B,事務C分別對應請求網路介面的資料,而事務D則是根據返回的資料處理展示的邏輯;
那麼我正常的處理邏輯可能是這樣,在子執行緒去處理這三個事務A,事務B,事務C,最終等都處理完了之後,再處理事務D,而這樣寫的壞處就是我把這幾個介面的資料都放在一個子執行緒去執行了,那麼最終結果就是會導致載入緩慢;
那麼我們是否可以換成另外一種寫法,事務A,事務B,事務C分別在三個子執行緒去執行,然後最終在三個子執行緒的回撥裡面去判斷這幾個介面是否已經載入完畢了,這樣可以解決上面的問題,但是如果以後還有新增的事務,那麼最終會導致判斷的邏輯越來越臃腫;
而RxJava提供了響應式程式設計的思想,可以解決這類問題;
5.2、響應式程式設計
什麼是響應式程式設計?
響應式程式設計是一種透過非同步和資料流來構建事務關係的程式設計模型
我們可以理解為由事件來驅動事務,比如我請求網路資料成功了,傳送請求成功的事件通知下一層事務進行處理;
而RxJava提供了一系列的特性,比如我們可以對事務進行變換,串聯,組裝等來對事務進行操作,比如上面的事務A,事務B,事務C,我們可以透過組裝的方式來進行處理;
那麼最終RxJava的處理邏輯如下:
將事務A,事務B,事務C進行組裝,等處理完畢了之後,最終會發送一個事件通知事務D進行處理;
RxJava響應式程式設計帶來了什麼好處?
1、大幅度降低事務之間的耦合性,方便後期維護與擴充套件;
2、簡化複雜的執行緒操作,讓我們專注於業務開發,避免了很多執行緒併發的問題,比如執行緒死鎖;
3、提供了豐富的運算子,讓我們對於事務的處理更加方便;
4、對於複雜的業務,我們可以構建出清晰的程式碼邏輯,方便理解業務邏輯;
5.3、RxJava的架構對於響應式程式設計的思考
RxJava底層透過觀察者模式來處理的事件傳遞,透過裝飾者模式來處理事務的操作,由這兩個設計者模式來構建了響應式程式設計的思想,並且裝飾者模式還保證了其靈活的擴充套件性,比如我以後要新增一個運算子,只需要實現對應的觀察者和被觀察者的包裝類即可;
RxJava不僅僅是一個非同步框架,還提供了我們處理事務的能力,把複雜的邏輯透過響應式程式設計的思想,變得更清晰易懂,這讓我們對於複雜業務的處理更加的得心應手;
這是非常優秀的原始碼,也很感嘆作者奇妙的思路,很值得我們去學習;
當我們掌握了RxJava的核心原理後,那麼無論原始碼再怎麼更新,也脫離不了這個架構思想,當我們帶著這個架構思想去看原始碼細節的時候,架構思想就是你的燈塔,讓你不會迷失在茫茫的碼海里;
關於我
兄dei,如果我的文章對你有幫助的話,請幫我點個贊吧,也可以關注一下我的Github和部落格;
https://github。com/Amterson?tab=repositories
https://juejin。cn/user/1063982984346055/posts
最後推薦一下我做的小程式:
程式設計師陌然
,包含詳盡的小程式開發案例、好用的工具,還有本公眾號文章合集,歡迎體驗和收藏和提出建議!
如果你想要跟大家分享你的文章,歡迎投稿~
┏(^0^)┛明天見!