初識React Native
我們都知道React是一套開源JavaScript 庫(也可以稱為前端 UI 框架),而React Native則是React向移動端的延伸。我們可以認(rèn)為React Native分為兩層,React 層是一套JavaScript 庫;Native層則扮演橋梁的功能,根據(jù)平臺不同映射為不同的原生控件;通過這種設(shè)計(jì)實(shí)現(xiàn)了—開發(fā)者構(gòu)建的RN代碼可以在不同的平臺上(Android和IOS)運(yùn)行,也就是Learn once, write anywhere。Java(Write once, run anywhere)
RN是如何解決H5性能瓶頸的?
首先我們需要了解H5性能瓶頸是怎么造成的?對比原生,H5包含信息更加自由豐富,這也導(dǎo)致:
1、從服務(wù)器獲取信息時候,H5頁面需要更多的數(shù)據(jù)量和校驗(yàn)步驟,相應(yīng)的需要消耗更多的時間;
2、本地解析渲染(顯示)時候,H5需要更復(fù)雜的解析渲染規(guī)則,相應(yīng)的需要耗費(fèi)更多的時間。
RN可以認(rèn)為是H5(JS)的特殊子集合。那么RN解決H5性能瓶頸,自然也是從這兩點(diǎn)進(jìn)行出發(fā)的。
首先我們看問題1,它關(guān)鍵在于正常使用時候減少網(wǎng)絡(luò)請求的數(shù)據(jù)量。這一點(diǎn)我們其實(shí)已經(jīng)從一個名詞( JS bundle)中猜測到,RN相當(dāng)于進(jìn)入一個模塊時候(可以自定義分包)把需要更新的的H5頁面打包成一捆(js bundle,可能是一頁,也可能是好幾頁)一次性更新,當(dāng)再次加載該頁面的時候,就直接從本地獲取就行了。在這種方式下,當(dāng)非首次加載JS bundles時候,從網(wǎng)絡(luò)請求的數(shù)據(jù)跟原生基本差不多,甚至一些特定情況比原生還要少。所以問題1的答案就是是運(yùn)用(批量)緩存的方式來減少平時使用過程中網(wǎng)絡(luò)請求數(shù)據(jù)量。
接下來我們再研究問題2,它關(guān)鍵在于縮短JS代碼解析渲染的時間,為解決這個問題,F(xiàn)acebook采用了兩個重要舉措:
1、引入虛擬DOM減少不必要的渲染;
虛擬DOM、虛擬硬盤、虛擬機(jī)等計(jì)算機(jī)術(shù)語中提到的“虛擬”基本都是為了解決“真實(shí)”操作起來復(fù)雜、繁瑣、代價(jià)高等問題而生,它們都能夠?qū)⑾鄬唵畏奖愕奶摂M操作映射成為真實(shí)的行動。虛擬DOM的出現(xiàn)之前,一次頁面更新可能有十幾個DOM更新操作,前前后后更新了十幾次,大大浪費(fèi)了渲染性能。出現(xiàn)虛擬DOM之后,一次更新操作中的10次更新DOM的動作,不會立即操作DOM,而是將這10次更新更新到虛擬DOM中,通過對比找出前后兩次虛擬DOM的不同(diff算法),然后把不同一次性映射為真實(shí)DOM的更新。
2、舍棄JS渲染,通過映射的方式把RN代碼中的控件一個個映射為原生控件進(jìn)行渲染。
由于JS的靈活性與復(fù)雜性,雖然很多團(tuán)隊(duì)在研究它的渲染問題,但它的解析+渲染始終比原生慢上許多。于是facebook工程師另辟蹊徑,它認(rèn)為既然原生渲染快,那就用原生渲染代替JS渲染,RN做好橋接就好了。比如我們用RN代碼寫一個Button,那么實(shí)際在運(yùn)行的時候RN會生成一個映射表,將這個Button分別映射為android和ios的button,相關(guān)設(shè)置的屬性也進(jìn)行了同步映射。
兩個很重要的概念
Props(屬性)
大多數(shù)組件在創(chuàng)建時就可以使用各種參數(shù)來進(jìn)行定制。用于定制的這些參數(shù)就稱為props(屬性)。
以常見的基礎(chǔ)組件Image為例,在創(chuàng)建一個圖片時,可以傳入一個名為source的prop來指定要顯示的圖片的地址,以及使用名為style的prop來控制其尺寸。
比如Android里的Activity的話,Props有點(diǎn)類似Intent ,props是在父組件中指定,而且一經(jīng)指定,在被指定的組件的生命周期中則不再改變。但是功能比Intent 強(qiáng)大,
State(狀態(tài))
我們使用兩種數(shù)據(jù)來控制一個組件:props和state。props是在父組件中指定,而且一經(jīng)指定,在被指定的組件的生命周期中則不再改變。 對于需要改變的數(shù)據(jù),我們需要使用state。
一般來說,你需要在constructor中初始化state(譯注:這是ES6的寫法,早期的很多ES5的例子使用的是getInitialState方法來初始化state,這一做法會逐漸被淘汰),然后在需要修改時調(diào)用setState方法。
相同點(diǎn)
- 不管是props還是state的改變,都會引發(fā)render的重新渲染。
- 都能由自身組件的相應(yīng)初始化函數(shù)設(shè)定初始值。
不同點(diǎn)
- 初始值來源:state的初始值來自于自身的getInitalState(constructor)函數(shù);props來自于父組件或者自身getDefaultProps(若key相同前者可覆蓋后者)。
- 修改方式:state只能在自身組件中setState,不能由父組件修改;props只能由父組件修改,不能在自身組件修改。
- 對子組件:props是一個父組件傳遞給子組件的數(shù)據(jù)流,這個數(shù)據(jù)流可以一直傳遞到子孫組件;state代表的是一個組件內(nèi)部自身的狀態(tài),只能在自身組件中存在。
生命周期
我們先來看初始化,在初始化的過程中,會按順序調(diào)用下面5個函數(shù)。
getDefaultProps:組件實(shí)例創(chuàng)建前調(diào)用,多個實(shí)例間共享引用。注意:如果父組件傳遞過來的Props和你在該函數(shù)中定義的Props的key一樣,將會被覆蓋。
getInitalState:組件示例創(chuàng)建的時候調(diào)用的第一個函數(shù)。主要用于初始化state。注意:為了在使用中不出現(xiàn)空值,建議初始化state的時候盡可能給每一個可能用到的值都賦一個初始值。
componentWillMount:在render前,getInitalState之后調(diào)用。僅調(diào)用一次,可以用于改變state操作。
render:組件渲染函數(shù),會返回一個Virtual DOM,只允許返回一個最外層容器組件。render函數(shù)盡量保持純凈,只渲染組件,不修改狀態(tài),不執(zhí)行副操作(比如計(jì)時器)。
componentDidMount:在render渲染之后,React會根據(jù)Virtual DOM來生成真實(shí)DOM,生成完畢后會調(diào)用該函數(shù)。在瀏覽器端(React),我們可以通過this.getDOMNode()來拿到相應(yīng)的DOM節(jié)點(diǎn)。然而我們在RN中并用不到,在RN中主要在該函數(shù)中執(zhí)行網(wǎng)絡(luò)請求,定時器開啟等相關(guān)操作
初始化完成之后,組件將會進(jìn)入到運(yùn)行中狀態(tài),運(yùn)行中狀態(tài)我們將會遇到如下幾個函數(shù):
componentWillReceiveProps(nextProps):props改變(父容器來更改或是redux),將會調(diào)用該函數(shù)。新的props將會作為參數(shù)傳遞進(jìn)來,老的props可以根據(jù)this.props來獲取。我們可以在該函數(shù)中對state作一些處理。注意:在該函數(shù)中更新state不會引起二次渲染。
boolean shouldComponentUpdate(object nextProps, object nextState):該函數(shù)傳遞過來兩個參數(shù),新的state和新的props。state和props的改變都會調(diào)到該函數(shù)。該函數(shù)主要對傳遞過來的nextProps和nextState作判斷。如果返回true則重新渲染,如果返回false則不重新渲染。在某些特定條件下,我們可以根據(jù)傳遞過來的props和state來選擇更新或者不更新,從而提高效率。
componentWillUpdate(object nextProps, object nextState):與componentWillMount方法類似,組件上會接收到新的props或者state渲染之前,調(diào)用該方法。但是不可以在該方法中更新state和props。
render:跟初始化的時候功能一樣。
componentDidUpdate(object prevProps,object prevState):和初始化時期的componentDidMount類似,在render之后,真實(shí)DOM生成之后調(diào)用該函數(shù)。傳遞過來的是當(dāng)前的props和state。在該函數(shù)中同樣可以使用this.getDOMNode()來拿到相應(yīng)的DOM節(jié)點(diǎn)。如果你需要在運(yùn)行中執(zhí)行某些副操作,請?jiān)谠摵瘮?shù)中完成。componentWillReceiveProps(nextProps):props改變(父容器來更改或是redux),將會調(diào)用該函數(shù)。新的props將會作為參數(shù)傳遞進(jìn)來,老的props可以根據(jù)this.props來獲取。我們可以在該函數(shù)中對state作一些處理。注意:在該函數(shù)中更新state不會引起二次渲染。
銷毀階段只有一個函數(shù),很簡單
componentWillUnmount:組件DOM中移除的時候調(diào)用。在這里進(jìn)行一些相關(guān)的銷毀操作,比如定時器,監(jiān)聽等等。
生命周期 | 跳用次數(shù) | 是否setState |
---|---|---|
getDefaultProps | 1(全局調(diào)用一次) | 否 |
getInitialState | 1 | 否 |
componentWillMount | 1 | 是 |
render | >=1 | 否 |
componentDidMount | 1 | 是 |
componentWillReceiveProps | >=0 | 是 |
shouldComponentUpdate | >=0 | 否 |
componentWillUpdate | >=0 | 否 |
componentDidUpdate | >=0 | 否 |
componentWillUnmount | 1 | 否 |
優(yōu)勢
1、 跨平臺
2、 組件
3、 簡化的生命周期
4、 迭代的速度(高峰期)
5、 性能(出去初始化的時間的話)
6、 Redux (狀態(tài)管理的庫等)
7、 支持原生
8、 動畫
9、 開源
遇到的問題
首先,必須承認(rèn),RN不是JS工程師就可以搞定的。 不懂IOS還好, 團(tuán)隊(duì)沒有懂a(chǎn)ndroid整個架構(gòu)的工程師, android就可能有各種問題。 優(yōu)化android是一件非常繁瑣的事情,得知道優(yōu)化android需要很多的黑科技。
其次, RN也不是Native工程師可以順利搞定的。 native工程師做的時候會束手束腳,其實(shí)這和npm生態(tài)有關(guān)。 在開發(fā)native產(chǎn)品時候, 大部分需要用到的架構(gòu)library庫,都是官方搞定的。 而RN如果說, 架構(gòu)很好, 那是社區(qū)搞定的。 所以對于剛剛接觸RN的native工程師, 會覺得RN非常難以架構(gòu), 沒有架構(gòu)的工具,
而且Android技術(shù)也在不斷優(yōu)化中,就一個Databinding特性就不是ReactNative所能支持的,更別提其他的RxAndroid響應(yīng)式編程了這些東西了,涉及底層的功能需要Android和Ios雙端單獨(dú)開發(fā),JS調(diào)用,整體性能仍不如原生;
我覺得ReactNative更像是原生開發(fā)的一個補(bǔ)充,一些App的新的嘗試的可能。畢竟用純ReactNative開發(fā)商業(yè)化的App,我怎么都覺得有點(diǎn)不靠譜,有過Aribnb 大公司的前車之鑒
趟坑總結(jié):
同樣的代碼在Android 和IOS 的響應(yīng)速度以及請求的成功率不一樣:
TypeError: Network request failed:
https://blog.csdn.net/qq_32312317/article/details/80868118
**react-native link XX **
react-native 并不成熟
React Native比Android或iOS更不成熟(或者說是穩(wěn)定)。 它更新,更有野心,并且更新速度非常快。 雖然React Native在大多數(shù)情況下都能很好地工作,但有些情況下,它的不穩(wěn)定可能會顯示出來,并且會使原生開發(fā)中的一些微不足道的事情變得非常困難。 不幸的是,這些情況很難預(yù)測,可能需要幾小時到幾天的時間才能解決。這個問題,Airbnb 團(tuán)隊(duì) 自行維護(hù)RN分支
由于React Native的不成熟,他們有時需要修正React Native源碼。 除了向React Native做出貢獻(xiàn)之外,他們還必須維護(hù)一個分支,以便他們能夠快速合并更改并使版本崩潰。 在過去兩年中,他們不得不在React Native之上添加大約50次提交。 這使得升級React Native的過程非常痛苦。
重構(gòu)之難
JavaScript是一種無類型的語言。 缺乏類型安全性難以擴(kuò)展,JavaScript被忽略的副作用是重構(gòu)非常困難且容易出錯。 重命名props,特別是帶有通常名稱的props,如通過多個組件傳遞的onClick或props,是準(zhǔn)確重構(gòu)的噩夢。 更糟糕的是,重構(gòu)在生產(chǎn)中而不是在編譯時崩潰,很難為其添加適當(dāng)?shù)撵o態(tài)分析。
react-native開放源代碼庫
在Android上,許多React Native庫也要求您使用node_modules的相對路徑,而不是發(fā)布與社區(qū)所期望的不一致的maven工件。 需要動態(tài)去鏈接一些庫,
RN的很多庫,需要單獨(dú)對IOS或者Aandroid 要進(jìn)行特殊的處理,但是一般給出的文檔都不是很詳細(xì)
比如 react-native-webview react-native-community/react-native-masked-view react-native link react-native-device-info 都需要去react-native link xx or pod install 原理就是將一個node_module里面的工程,作為Android的project
初始化時間
在React Native首次可以渲染之前,必須初始化其運(yùn)行時。 不幸的是,即使在高端設(shè)備上,也需要幾秒鐘的時間。 這使得使用React Native進(jìn)行啟動屏幕幾乎是不可能的。通過在應(yīng)用程序啟動時初始化React Native來縮短第一次渲染時間。與原生屏幕不同,渲染React Native需要至少一個完整的主線程 - > js - >Yoga布局線程 - >主線程往返行程,然后才有足夠的信息來第一次渲染屏幕。iOS平均初始p90呈現(xiàn)280ms,Android平均440ms。 在Android上,使用通常用于共享元素轉(zhuǎn)換的postponeEnterTransition API來延遲顯示屏幕直到它被渲染。 在iOS上,我遇到了問題,從React Native快速設(shè)置導(dǎo)航欄配置。 因此,我為所有React Native屏幕過渡添加了50ms的仿真延遲,以防止配置加載后導(dǎo)航欄閃爍。
手勢
在涉及復(fù)雜手勢的屏幕上使用React Native,因?yàn)锳ndroid和iOS的觸摸子系統(tǒng)足夠不同,以至于提出統(tǒng)一的API對整個React Native社區(qū)來說都具有挑戰(zhàn)性。需要按照不同的os 單獨(dú)設(shè)置
莫名其妙的崩潰
不得不面對一些難以解決的非常奇怪的崩潰。 例如,Hermes 引擎下,react-navigation-stack庫不支持,在官方這些文檔上并沒有體現(xiàn)出來,不支持這個的原因我也是在假設(shè),正常的代碼打成bundle 就運(yùn)行不了!
資料之難
似乎RN的已經(jīng)大勢所去,國內(nèi)外對目前版本的一些文章博客很少,github 很多優(yōu)秀的開源項(xiàng)目基本上都是三四年的,已經(jīng)很陳舊,沒有一個能順利的運(yùn)行起來