RN實戰(zhàn)經(jīng)驗總結(jié)


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)化陨享。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赞厕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌定硝,老刑警劉巖皿桑,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蔬啡,居然都是意外死亡诲侮,警方通過查閱死者的電腦和手機箱蟆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顽腾,“玉大人,你說我怎么就攤上這事久信±炷Γ” “怎么了裙士?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵腿椎,是天一觀的道長桌硫。 經(jīng)常有香客問我啃炸,道長,這世上最難降的妖魔是什么南用? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任裹虫,我火速辦了婚禮,結(jié)果婚禮上筑公,老公的妹妹穿的比我還像新娘。我一直安慰自己匣屡,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布兴泥。 她就那樣靜靜地躺著虾宇,像睡著了一般搓彻。 火紅的嫁衣襯著肌膚如雪嘱朽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天稀轨,我揣著相機與錄音岸军,去河邊找鬼。 笑死艰赞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的方妖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼雌澄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了镐牺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤卒废,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逆皮,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年秽梅,在試婚紗的時候發(fā)現(xiàn)自己被綠了剿牺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡钞诡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荧降,到底是詐尸還是另有隱情,我是刑警寧澤朵诫,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布薄扁,位于F島的核電站,受9級特大地震影響邓梅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜日缨,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诈铛。 院中可真熱鬧,春花似錦幢竹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽循签。三九已至,卻和暖如春县匠,著一層夾襖步出監(jiān)牢的瞬間撒轮,已是汗流浹背乞旦。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工题山, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玖姑。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓慨菱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抡柿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,732評論 25 707
  • 詩歌翻譯 波德萊爾 Correspondances Correspondances La Nature est u...
    月明西湖閱讀 969評論 0 4
  • 勵志小故事:《再試一次》 有個年輕人去微軟公司應(yīng)聘备蚓,而該公司并沒有刊登過招聘廣告囱稽。見總經(jīng)理疑惑不解,年輕人用不太嫻...
    藝趣書苑閱讀 754評論 0 1
  • “你為什么拒絕我 不喜歡我嗎” “不 喜歡 非常喜歡 就因為太喜歡 所以才不知所措”
    喵喵小仙女閱讀 181評論 0 0
  • 從上個學期頻繁去醫(yī)院流昏,看到人來人往的人們,焦慮的况凉,失望的。 激素六項檢查一直拖拖拖刁绒,不敢去醫(yī)院,不敢面對自己脆弱的...
    黃莯洱閱讀 330評論 0 0