title: RN實戰(zhàn)經(jīng)驗總結(jié)
前言
在草稿箱中發(fā)現(xiàn)了許久之前寫的這篇文章泳唠,雖然不搞RN已經(jīng)大半年了格粪,但是之前寫過的東西還是還出來作個紀念舍哄。如果能幫到別人那再好不過了溪椎。
Android中集成RN
這個需要說的都在這里說了普舆,見在原有Android項目中快速集成React Native
關(guān)于RN在項目中Android端的預加載
此前曾根據(jù)網(wǎng)上的做法并結(jié)合最新的RN源碼做了一個RN的預加載庫,不過在后來發(fā)現(xiàn)會出現(xiàn)內(nèi)存泄漏問題校读。在集成到項目Android端此著手解決了這一問題沼侣。
然而在實際的開發(fā)中,幾乎有大半的頁面是用RN開發(fā)歉秫,如果全部頁面都使用預加載蛾洛,那么對內(nèi)存會有很大的壓力,而且也沒有這個必要雁芙。
首先說一下目前項目的頁面組織結(jié)構(gòu)轧膘,其實就是目前主流的主Activity(帶四個Fragment)+其他Activity,主Activity在應(yīng)用運行期間是一直存在的兔甘,這就為預加載提供了一個絕佳的基礎(chǔ)谎碍。
最終使用預加載的是主Activity【我的】Fragment頁面。在RN中加載Fragment并不難洞焙,在Android中加載RN蟆淀,無論是在Activity還是Fragment拯啦,加載的都只是一個View而已。而給Fragment設(shè)置View熔任,只需要Fragment的onCreateView返回RN的View即可褒链。
具體見:在Android中預加載React Native jsBundle
優(yōu)化非預加載初始化屬性傳遞
在原本的ReactActivity中傳遞啟動屬性可以用以下方式
public class C3RNActivity extends ReactActivity {
public static final String MAIN_COMPONENT_NAME = C3RNActivity.class.getSimpleName();
protected @Nullable
String getMainComponentName() {
return MAIN_COMPONENT_NAME;
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Nullable
@Override
protected Bundle getLaunchOptions() {
Bundle bundle=new Bundle();
//往bundle中添加啟動屬性鍵值對
bundle.putString("key","value");
return bundle;
}
};
}
}
這種方式傳遞是完全沒問題的,但是有點局限性笋敞。查看ReactActivity的源碼碱蒙,createReactActivityDelegate
是在ReactActivity的構(gòu)造方法調(diào)用(在OnCreate之前)。但這樣一來就無法在OnCreate通過getItent獲取別的Activity傳遞過來的參數(shù)夯巷,因此我們需要對原本的ReactActivity進行改造赛惩。將createReactActivityDelegate方法調(diào)用從ReactActivity移到onCreate方法中,但是在ReactActivityDelegate的onCreate方法之前趁餐。這樣我們需要重寫ReactActivity而不是直接通過繼承創(chuàng)建滿足我們要求的ReactActivity喷兼。
public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private MyReactActivityDelegate mDelegate;
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
* e.g. "MoviesApp"
*/
protected @Nullable
String getMainComponentName() {
return null;
}
/**
* Called at construction time, override if you have a custom delegate implementation.
*/
protected MyReactActivityDelegate createReactActivityDelegate(final Intent intent) {
return new MyReactActivityDelegate(this, getMainComponentName()){
@Nullable
@Override
protected Bundle getLaunchOptions() {
Bundle bundle=new Bundle();
//在這里將intent參數(shù)放入bundle,作為RN的頁面啟動參數(shù)
//例如:
bundle.putString("key",intent.getStringExtra("xxx"));
return bundle;
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent=getIntent();
mDelegate = createReactActivityDelegate(intent);
mDelegate.onCreate(savedInstanceState);
}
@Override
protected void onPause() {
super.onPause();
mDelegate.onPause();
}
@Override
protected void onResume() {
super.onResume();
mDelegate.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
mDelegate.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mDelegate.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}
@Override
public void onBackPressed() {
if (!mDelegate.onBackPressed()) {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
if (!mDelegate.onNewIntent(intent)) {
super.onNewIntent(intent);
}
}
@Override
public void requestPermissions(
String[] permissions,
int requestCode,
PermissionListener listener) {
mDelegate.requestPermissions(permissions, requestCode, listener);
}
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected final ReactNativeHost getReactNativeHost() {
return mDelegate.getReactNativeHost();
}
protected final ReactInstanceManager getReactInstanceManager() {
return mDelegate.getReactInstanceManager();
}
}
其中后雷,MyReactActivityDelegate 是直接繼承ReactActivityDelegate季惯,因為在ReactActivityDelegate中,onCreate臀突,onPause勉抓,onDestroy等方法是protect修飾,無法在其他包中引用候学,所以需要對其復寫藕筋,實現(xiàn)中只需要調(diào)用父類方法即可。
多入口
在本次項目中使用的是多注冊方式實現(xiàn)RN的多入口梳码,實際上通過啟動屬性傳遞需要打開的RN頁面參數(shù)也是可以的隐圾。不過因為使用多注冊實現(xiàn)多入口還是踩了一些坑。在多注冊方式下掰茶,RN的全局變量在iOS客戶端是無效的暇藏。也就是說,在一個根組件中給一個全局變量賦值濒蒋,在另外一個根組件中讀取到的全局變量值是空的盐碱。而在Android端是沒有這個問題。
網(wǎng)絡(luò)圖片加載過渡
這里不得不提這是RN的一個坑沪伙,最新的RN都發(fā)布到0.5x了瓮顽,在Android中依然沒有支持默認占位圖,默認加載錯誤圖焰坪,以及加載進度方法。這三個都只有在iOS端有效聘惦,在Android端則需要自己手動實現(xiàn)某饰。
網(wǎng)絡(luò)狀況判斷
這里不得不提這又是RN的一個坑儒恋。在RN中官網(wǎng)推薦判斷網(wǎng)絡(luò)是否可用的方法如下:
NetInfo.isConnected.fetch().done(
(isConnected) => { this.setState({isConnected}); }
);
然而實際上在iOS端,isConnected返回的永遠是false黔漂。
官方文檔說上面這個方法是Android和iOS平臺通用的诫尽,然而你實際使用的時候在iOS端就會發(fā)現(xiàn)問題,即使到了0.51炬守,這個問題仍然存在.....
其實這個解決辦法很多牧嫉,
具體可見:Github issue:iOS: NetInfo.isConnected returns always false
其中一種解決辦法如下:
function handleFirstConnectivityChange(isConnected) {
if (!sConnected) {
// do action
}
NetInfo.isConnected.removeEventListener('change', handleFirstConnectivityChange);
}
if (Platform.OS === 'ios') {
NetInfo.isConnected.addEventListener('change', handleFirstConnectivityChange);
} else {
NetInfo.isConnected.fetch().then(isConnected => {
if (!sConnected) {
// do action
}
}
Linking模塊在Android release模式下getInitialURL返回為null
這也是一個大坑。在RN中减途,如果你的應(yīng)用被其注冊過的外部url調(diào)起酣藻,則可以在任何組件內(nèi)這樣獲取和處理它:
componentDidMount() {
Linking.getInitialURL().then((url) => {
if (url) {
console.log('Initial url is: ' + url);
}
}).catch(err => console.error('An error occurred', err));
}
然而在Android端打成Release包時,返回的url偶爾會為空鳍置,對辽剧,是偶爾,并且概率還比較大税产,原因暫時未知怕轿。所以要通過外部鏈接和Linking模塊來打開RN的話,這種做法是不靠譜的辟拷。解決辦法是用Android原生的老辦法撞羽,在Activity的onCreate方法中獲取外部鏈接以及相關(guān)參數(shù),并作為啟動參數(shù)傳遞給RN衫冻。為此诀紊,需要重寫ReactActivity和ReactActivityDelegate。具體參考:優(yōu)化非預加載初始化屬性傳遞一節(jié)羽杰。
總結(jié):
從17年4月份開始接觸RN渡紫,至今如有大半年時間,在這大半年時間里考赛,從入門學習到實際動手寫出一個完整的仿實際產(chǎn)品的App出來花了一個月時間惕澎,與當初學習Android相比這個時間短得太多了。到17年6月份在我們公司的天翼云iOS客戶端其中一個頁面試點使用RN颜骤,然后前后花了一個月時間唧喉,但實際動手接入項目中與從零開始一個RN項目有很大的不同,期間踩了好幾個坑忍抽,還好都能及時解決八孝。到17年10月份,在我們公司的產(chǎn)品兩個客戶端都接入RN并且是重度使用鸠项,大概有50%~60%頁面是使用RN開發(fā)干跛。在這一次接近2個月的開發(fā)過程中祟绊,對RN簡直又愛又恨哥捕,踩了大大小小好多個坑,看到了RN的許多不足遥赚,也看到了原生與RN無法比擬的一些優(yōu)勢。
先來說一下切身體會的優(yōu)勢:
- 上手快阐肤,即使不懂JS凫佛,入門也不用太長時間,半個月時間其實就足夠了愧薛。上手之后,開發(fā)效率其實可以很高厚满。
- 跨平臺,這是一個巨大的優(yōu)勢碧磅,雖然RN的代碼不能做到100%兩個端復用碘箍,但是90%還是沒問題的。而自然地鲸郊,可以節(jié)省一定的人力成本。
- 熱更新秆撮,這一功能在Android中實現(xiàn)比較簡單,但是因為蘋果爸爸禁用了JSPatch职辨,因此在iOS端能用的熱更新方法不多了,而熱更新則是其中一個舒裤。
- 更新快,這其實是一個優(yōu)點也可以說是缺點腾供,說它是優(yōu)點因為勤快地更新則說明RN加了某些新特性或者修復了一些歷史遺留的bug,說它是缺點則是因為更新太快伴鳖,說不定某些API哪天突然就不能用了,代碼的寫法又不一樣了搞疗,RN版本升級的時候也是一件比較痛苦的事情。
- 調(diào)試方便:可以在谷歌瀏覽器上面單步調(diào)試JS代碼匿乃,雙擊R(或搖一搖或CMD+R)就能快速reload代碼
不足:
- 開發(fā)過程會時不時就踩到坑,RN作為一個還沒正式發(fā)布1.0版本的框架扳埂,有一些bug是必然的瘤礁。
- 列表控件性能仍不能滿足要求,在快速滑動時會看到一些空白項柜思。
- 圖片緩存:官方?jīng)]有很好的支持,第三方庫也沒有找到比較滿意的方案赡盘。
- 動畫效果不佳:這個眾所周知,動畫效果需要自己做優(yōu)化陨享。