React Native深入研究(第一篇)

一. 什么是RN?


1. Build native mobile apps using JavaScript and React

2. A React Native app is a real mobile app

一千個(gè)人用就有一千種解釋贯涎,本人概而言之為:React-Native利用web應(yīng)用和Native的優(yōu)勢米奸,用JS來實(shí)現(xiàn)移動(dòng)端的應(yīng)用慎框。利用React的原生UI組件代替DOM的渲染苦丁,實(shí)現(xiàn)了一種只用一種開發(fā)語言便能高效的開發(fā)出一款與平臺無關(guān)的app窥浪。

詳細(xì)請參考官方文檔:https://facebook.github.io/react-native/

二. 相關(guān)技術(shù)名詞解釋。


想更好的學(xué)習(xí)接下來的內(nèi)容愿险,一些必要的技術(shù)名詞是需要事先弄明白的颇蜡。免得一頭霧水。


三. RN的原理


對于菜鳥而言辆亏,對于這么深?yuàn)W的原理风秤,我一般會(huì)細(xì)化。

整體架構(gòu):


個(gè)人喜歡對比性分析問題扮叨,喜歡細(xì)化性分析問題缤弦,所以RN的原理我主要分了以下幾個(gè)步驟來學(xué)習(xí)理解:

第一步:ReactNative的渲染機(jī)制。

在這里引用一個(gè)Virtual Dom的概念彻磁。顧名思義碍沐,這是對DOM的一個(gè)虛擬。是一個(gè)純js寫的一個(gè)偽DOM的結(jié)構(gòu)衷蜓,主要是運(yùn)用一種diff的算法抢韭,高效完成局部數(shù)據(jù)刷新。網(wǎng)頁實(shí)際上都是解析成dom的格式被加載渲染恍箭,但是每次渲染都是dom數(shù)據(jù)的重載,而virtual dom則是實(shí)現(xiàn)了部分重新加載這樣就大大提高了高效性瞧省。

說到這扯夭,就會(huì)提到React框架,所謂的React框架其實(shí)就是一套簡潔高效繪制DOM的框架鞍匾,而這個(gè)框架的高效實(shí)現(xiàn)就是基于Virtual DOM交洗。之所以快還有一個(gè)原因Virtual DOM是運(yùn)行在內(nèi)存里的。

綜合以上橡淑,宏觀上我們似乎對React有了那么一丁點(diǎn)的了解构拳,不妨趁熱打鐵,來瞅瞅源碼對DOM的實(shí)現(xiàn):

對比性學(xué)習(xí)效率會(huì)更高,DOM的結(jié)構(gòu)大家都不陌生置森,第一想到的便是Element斗埂,沒錯(cuò)在ReactNative的源碼里有這兩個(gè)很相似的玩意:ReactElement和ReactClass

首先回顧下DOM的結(jié)構(gòu)實(shí)例:

{type:'button',

props: {? ? className:'button button-blue',??

children: {? ? ?

type:'b',? ? ?

props: {? ? ?

? children:'OK!'}??

}


}}

而針對于React而言,我們稱之為Component Element:

classButtonextendsReact.Component {

render() {

const{ children,color } =this.props;

return{

type:'button',

props: {

className:'button button-'+ color,

children: {

type:'b',

props: {

children: children

}

// Component Elements

{

type: Button,

props: {

color:'blue',

children:'OK!'

}

看完他和DOM的結(jié)構(gòu)不同凫海,再來看看我們怎么用:


看起來好簡單呛凶,就是js隨便搞搞,但是問題來了行贪,怎么沒和dom結(jié)構(gòu)扯上任何關(guān)系的趕腳漾稀?看看人家如何運(yùn)行的就明白了:

進(jìn)入到ReactElement的源碼當(dāng)中其實(shí)就是一個(gè)js:

var ReactElement * function(type,key,ref,self,source,owner,props);

可以看的,父元素的props對象的一個(gè)屬性children指向了剛新建的eleChildren元素建瘫;我們用對象的形式表示出來就是:

{

$$typeof: Symbol(react.element),//ReactElement的唯一標(biāo)識

_owner: null,

key: undefined,//唯一標(biāo)識

props: {//屬性

children: {

$$typeof: Symbol(react.element),

_owner: null,

key: undefined,

props: {

children: "look~",

id: "reactChild"

},

ref: undefined,

type: "p"

},

id: "text",

onclick:"hello"http://指向hello方法的指針

},

ref: undefined,//對DOM的引用

type: "div"http://標(biāo)簽類型

}

這樣的話ReactElement的結(jié)構(gòu)就很清楚了崭捍,React正是通過這種對DOM的抽象,再根據(jù)不同的ReactElement生產(chǎn)不同的組件Component啰脚,然后遞歸渲染殷蛇;其中React.render()便是處理事件綁定的過程。

以上都是說的ReactElement拣播,下面我們聊聊ReactClass晾咪。

顧名思義,看見class就知道肯定和類脫不了關(guān)系贮配。你很聰明谍倦,它確實(shí)就是React Component整出來的類或者是屬性。說到這有些小伙伴可能不太懂Component這個(gè)東東了泪勒,玩過html嗎昼蛀?很好,你可以簡單的理解它就是html的任何一個(gè)tag屬性圆存,比如div叼旋,li等等。幾乎包含了html的所有標(biāo)簽沦辙。不過需要注意的是每一個(gè)component要包含一個(gè)render使用夫植。這個(gè)也不難理解,這就是為了創(chuàng)建一個(gè)element油讯。因?yàn)楫吘棺罱K的dom都是以element組成的详民。吻合了dom的渲染。


通過上圖可以看出這是一個(gè)遞歸調(diào)用的過程陌兑,最終是以一個(gè)element結(jié)束沈跨。

Component Elements實(shí)例

class Button extends React.Component{render() {??

???? const { children, color } =this.props;return{type:'button',? ? ?

??? props: {? ? ? ? className:'button button-'+ color,? ? ? ?

?? children: {

????? type:'b',? ? ? ? ?

props: {? ? ? ? ? ?

???? children: children? ? ? ? ?

??? }? ? ? ? }? ? ? }? ? };? }}

// Component Elements

{type:

?? Button,?

?? props: {? ? color:'blue',? ? children:'OK!'}

}

1. 調(diào)用 React.render 方法,將我們的 element 根虛擬節(jié)點(diǎn)渲染到 container 元素中兔综。element 可以是一個(gè)字符串文本元素饿凛,也可以是如上介紹的 ReactElement 狞玛。

2. 根據(jù) element 的類型不同,分別實(shí)例化 ReactDOMTextComponent , ReactDOMComponent ,

3. ReactCompositeComponent 類涧窒。這些類用來管理 ReactElement ,負(fù)責(zé)將不同的 ReactElement轉(zhuǎn)化成DOM心肪,并更新DOM。

4. ReactCompositeComponent 實(shí)例調(diào)用 mountComponent 方法后內(nèi)部調(diào)用 render 方法杀狡,返回了 DOM Elements 蒙畴。然后遞歸。

第二步:ReactNative通信機(jī)制

在說到底層的通信機(jī)制前呜象,先來了解下宏觀上js和nativemodule上是怎么玩的膳凝。官網(wǎng)上明確提出了三種方式,callback恭陡,promises和event以及onActivityResult蹬音。

callback:例子很簡單,比如native層對布局發(fā)生了改變休玩,callback就返回給js層著淆,返回去的數(shù)據(jù)包括布局所需要的屬性值。

promises:個(gè)人覺得和callback的用法神相似拴疤,只不過是pormises返回給js的是一個(gè)對象永部。

event和onActityResult就很熟悉了在這里就不多講了,大家一看便知呐矾。個(gè)人建議學(xué)習(xí)api還是照官網(wǎng)來苔埋。

well,接下來我們透過現(xiàn)象看本質(zhì)蜒犯,實(shí)際上js和native的通信到底是干了什么見不得人的勾搭组橄。

由于本人是做android的,所以直接拿android說事罚随。首先盜個(gè)圖:


通過上圖不難看出玉工,通信的核心部分就是Bridge和Webkit這兩塊東西了。根據(jù)通信方向是雙向的淘菩,和cs的模式非常相似遵班,一端發(fā)問一端回答,而這里的客戶端切記是native層發(fā)出的潮改。

一說到底層大家就慌了费奸,有種不知如何下手的趕腳,別怕进陡,先從入口來吧,總歸會(huì)發(fā)現(xiàn)些許的蛛絲馬跡微服。拿我的demo為例趾疚,先從MainActivity開始:


好簡潔神馬都沒缨历,那就直接進(jìn)入ReactActivity里面看看〔诼螅看到了一個(gè)關(guān)鍵:

private final ReactActivityDelegate mDelegate;

ReactActivity被封裝的也很好并看不出入口的嫌疑辛孵,所以我鎖定了這個(gè)代理,進(jìn)去瞅瞅赡磅。

我們熟悉的onCreate方法終于出現(xiàn)了魄缚,loadApp讓我們眼前一亮,繼續(xù)跟蹤:

終于等到你焚廊,startReactApplicaition這個(gè)方法說明了一切冶匹,別太高興,我們剛剛找到個(gè)入口而已咆瘟〗腊看看參數(shù)吧,細(xì)枝末葉的就不說了袒餐,這個(gè)ReactInstanceManager貌似很重要飞蛹,manager嘛肯定來頭不小,進(jìn)去一看果真如此:

一切的一切越來越明朗了灸眼,這個(gè)builder有那么多熟悉的玩意卧檐,瞬間想起了我建立的nativemodule啊,package啊以及傳說中的js啊焰宣,好像都在這里同時(shí)出現(xiàn)了霉囚,趕緊來看看demo之間他們的聯(lián)系。

TestMoudle:兩個(gè)地被用到宛徊,第一處是在MyReactNativePackage里面:

@Override

publicListcreateNativeModules(ReactApplicationContext context) {

List modules =newArrayList<>();

modules.add(newTestModule(context));

returnmodules;

}

第二在index.android.js里面:

export default class FirstReactApp extends Component{

render() {

return(

toast for short

this.onClick()}>

);}onClick() {

NativeModules.TestModule.callNativeMethod("zsfsdgfhfgh");}}

MyReactNativePackage: 好家伙在MainApplicaiton里面呢佛嬉。

一層層往后剝,不管你們懂沒懂闸天,看到這我大概就懂了暖呕。先小小總結(jié)一下,消化下再繼續(xù)苞氮。

module像是一個(gè)javabean湾揽,reactpackage相當(dāng)于一個(gè)容器囊括的各色各樣的module,最終又全部塞進(jìn)Applicaiton里面送到底層笼吟。同時(shí)也把入口的js給傳過去了库物,我不睜眼說瞎話,看證據(jù):

從createReactInstanceManager
這個(gè)方法看到:

builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));

進(jìn)入到getBundleAssetName()這個(gè)方法內(nèi):

protected@Nullable

StringgetBundleAssetName() {return"index.android.bundle";

}

備注:明白人都知道index.android.js編譯之后都是和index.android.bundle對應(yīng)的贷帮。

后面就是JSBundleLoader處理js的操作戚揭。它的主要作用是去加載JSBundle。

回到startApplication方法里找到真正執(zhí)行的方法:

越陷越深,突然發(fā)現(xiàn)了執(zhí)行js的這個(gè)玩意:

有種恍然大悟的感覺撵枢,configmap民晒,原來人家是有協(xié)議的精居。換句話說是要進(jìn)行一一對應(yīng),網(wǎng)上說的真對潜必,js和native之間是要相互注冊進(jìn)行翻譯識別的靴姿。在Java層與Js層的bridge分別存有相同一份模塊配置表,Java與Js互相通信時(shí)磁滚,通過bridge里的配置表將所調(diào)用模塊方法轉(zhuǎn)為{moduleID,methodID,args}的形式傳遞給處理層佛吓,處理層通過bridge的模塊配置表找到對應(yīng)的方法執(zhí)行,如果有callback垂攘,則回傳給調(diào)用層维雇。

講到這大概的工作原理是通了,下面就真槍實(shí)彈來一發(fā)吧搜贤。


四. RN的環(huán)境搭建谆沃。


?環(huán)境準(zhǔn)備

step1:下載最新的node.js

官網(wǎng)下載地址:https://nodejs.org/en/

step2: 配置node.js的環(huán)境變量,例如:


驗(yàn)證node.js是否安裝成功:

step3:AS安裝配置就不說了仪芒,但是有一點(diǎn)sdk必須是23.0.1的唁影,react默認(rèn)支持這個(gè)版本。

step4:安裝React-Native. 用npm安裝掂名。npm install -g react-native-cli(前提是先下載reactnative)

step5:安裝成功后創(chuàng)建項(xiàng)目:react-native init XXX(your project's name)

備注:有關(guān)RN開發(fā)環(huán)境搭建的東西就不詳細(xì)介紹据沈,以上步驟足以開發(fā)一個(gè)rn項(xiàng)目,如有需要可以下載模擬器饺蔑。

五. RN的demo


1. 如何在AS里面導(dǎo)入一個(gè)不冗余的RN項(xiàng)目

通過react-native init xxx后的文件锌介,直接導(dǎo)入android這個(gè)文件夾即可。

2. 可能會(huì)遇到的問題

問題一:出現(xiàn)不能load index.android.js的問題

原因是:導(dǎo)入到as要想正常的加載運(yùn)行react項(xiàng)目必須有個(gè)build的工具進(jìn)行對js的橋接猾警,這個(gè)就是index.android.bundle孔祸。

解決方案:

要在main的目錄文件先創(chuàng)建一個(gè)assets文件夾然后在項(xiàng)目根目錄下執(zhí)行:

問題二,導(dǎo)入到as的android項(xiàng)目沒有把index.android.js導(dǎo)進(jìn)來发皿,怎么辦崔慧?

原因是:index.android.js是在整個(gè)react目錄下并不屬于具體的android單獨(dú)的項(xiàng)目,因此沒法包含進(jìn)來穴墅。

解決方案一惶室,把一下標(biāo)紅的模塊全部復(fù)制到android目錄下,然后修改所有js模塊下的目錄引用玄货,將../../改成../(方法不可取皇钞,工作量很大,一處修改不好都會(huì)造成項(xiàng)目運(yùn)行失敗)


解決方案二松捉,as只負(fù)責(zé)下react的純代碼邏輯夹界,js的東西還是在外面單獨(dú)寫吧。(雖然看起來和項(xiàng)目達(dá)不到是是一致性隘世,但是是可取的可柿,接受代碼分離開發(fā)的模式也拜,畢竟還是人家rn還是前端的,除非你不用as開發(fā))

問題三趾痘,修改了index.android.js代碼,可是并沒有起作用蔓钟。

原因是:上面就提到過永票,index.android.js雖然是關(guān)鍵,但是index.android.bundle卻是紐帶滥沫,他需要把js加工一層侣集。不經(jīng)過加工的js怎么改都是沒用的,as不支持自動(dòng)更新兰绣。

解決方案:在項(xiàng)目所在的根目錄下重新生成index.android.bundle文件:

F:\myTestProject\FirstReactApp>react-native bundle --platform android --dev fals

e --entry-file index.android.js --bundle-output android/app/src/main/assets/inde

x.android.bundle --assets-dest android/app/src/main/res/

備注:目前我只發(fā)現(xiàn)可以這樣世分,如有更好的解決方案,歡迎留言缀辩。

3. demo詳解

通過對index.android.js的修改玩轉(zhuǎn)各種基本屬性臭埋。

A. 有關(guān)神馬的html標(biāo)簽如何使用,就不啰嗦了臀玄,很簡單瓢阴。我總結(jié)的規(guī)則:想使用什么標(biāo)簽就得先import什么標(biāo)簽。render是起點(diǎn)健无,return是關(guān)鍵荣恐。事實(shí)上return回來的東西是要jsx解析的。所以return里面可以寫任意類似的html標(biāo)簽累贤,若想定義屬性叠穆,可以在render里,return外定義臼膏。

B. props屬性


再?zèng)]開始props這個(gè)屬性前硼被,我們先看下代碼結(jié)構(gòu)default class就類似于我們?nèi)肟赾lass,可以想象成java當(dāng)中的main方法讶请,其他的class祷嘶,可以任意定義,然后嵌套使用和引用夺溢。而引用的方式卻是組件式屬性引用论巍。‘a(chǎn)a’相當(dāng)于一個(gè)變量名稱风响,必須和引用一一對應(yīng)嘉汰,所以可以任意定義。效果圖:

C. state屬性状勤。

上面講到props屬性感覺挺好用的鞋怀,有種數(shù)據(jù)綁定的意思双泪。但是真正起到數(shù)據(jù)綁定且在項(xiàng)目中舉足輕重的還是state這個(gè)屬性。比如server端的數(shù)據(jù)有更新密似,前端需要更新咋整焙矛?這個(gè)時(shí)候就需要state了。以下是個(gè)簡單的例子:


這里的例子和官網(wǎng)的例子差不多残腌,因?yàn)闆]有server端的數(shù)據(jù)村斟,所以就整了計(jì)時(shí)器假裝數(shù)據(jù)有更新。這個(gè)demo需要注意的點(diǎn)抛猫。第一蟆盹,state必須在construct里面初始化,從代碼中可以看到其實(shí)state也是一個(gè)props闺金,你可以當(dāng)成是props的升級逾滥。第二,數(shù)據(jù)更新的關(guān)鍵是setState方法败匹。

D. Network

比較好的一點(diǎn)是寨昙,支持fetch,支持第三方網(wǎng)絡(luò)請求庫哎壳,也支持websocket毅待。

Fetch

testFetchMethod() {

fetch('source address',

{method:'POST',

headers:{'Accept':'application/json','Content-Type':'application/json'},

body:JSON.stringify({

firstParam:'aaaa',

secondParams:'safdsfds'})});

}

第三方庫(比如:XMLHttpRequest)

var request =new XMLHttpRequest();

request.onreadystatechange= (e) => {

if(request.readyState!==4) {

return;

}

if(request.status==400) {

console.log('success',request.responseText);

}else{

console.log("error");

}

request.open("GET","address");

request.send();

websocket

varws =newWebSocket('ws://host.com/path');

ws.onopen= () => {

// connection opened

ws.send('something');// send a message

};

ws.onmessage= (e) => {

// a message was received

console.log(e.data);

};

ws.onerror= (e) => {

// an error occurred

console.log(e.message);

};

ws.onclose= (e) => {

// connection closed

console.log(e.code,e.reason);

};

備注:以上只是個(gè)人覺得比較實(shí)用的屬性。其他有需要可以研究api归榕。

F:JS和Native通信的demo

其實(shí)在講通信機(jī)制的時(shí)候已經(jīng)說到了尸红,大概的流程:



源碼就不再貼了,說一下可能遇到的問題刹泄。

解決方案:在你的nativemodule里面一定要加入以下代碼:

@Override

public booleancanOverrideExistingModule() {

//這里需要返回true

return true;

}

六. RN的優(yōu)缺點(diǎn)


有關(guān)RN的優(yōu)缺點(diǎn)外里,本人暫時(shí)先保留不說。別問我為什么特石,還沒研究到一定的程度時(shí)盅蝗,我沒資格說!姆蘸!

七. 總結(jié)

從一竅不通到對這個(gè)RN的學(xué)習(xí)墩莫,研究收貨甚多。也感覺到了RN的精妙之處逞敷,這是第一篇狂秦,后續(xù)我會(huì)繼續(xù)深究完善后續(xù)升級文檔。喜歡的希望繼續(xù)關(guān)注推捐!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裂问,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌堪簿,老刑警劉巖痊乾,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異椭更,居然都是意外死亡哪审,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門虑瀑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來协饲,“玉大人,你說我怎么就攤上這事缴川。” “怎么了描馅?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵把夸,是天一觀的道長。 經(jīng)常有香客問我铭污,道長恋日,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任嘹狞,我火速辦了婚禮岂膳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘磅网。我一直安慰自己谈截,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布涧偷。 她就那樣靜靜地躺著簸喂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪燎潮。 梳的紋絲不亂的頭發(fā)上喻鳄,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音确封,去河邊找鬼戳稽。 笑死胳嘲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寺酪,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼比藻!你這毒婦竟也來了边琉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎候址,沒想到半個(gè)月后吕粹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岗仑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年匹耕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荠雕。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稳其,死狀恐怖炸卑,靈堂內(nèi)的尸體忽然破棺而出既鞠,到底是詐尸還是另有隱情,我是刑警寧澤盖文,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布嘱蛋,位于F島的核電站,受9級特大地震影響五续,放射性物質(zhì)發(fā)生泄漏洒敏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一疙驾、第九天 我趴在偏房一處隱蔽的房頂上張望凶伙。 院中可真熱鬧,春花似錦它碎、人聲如沸函荣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽偏竟。三九已至,卻和暖如春敞峭,著一層夾襖步出監(jiān)牢的瞬間踊谋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工旋讹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殖蚕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓沉迹,卻偏偏與公主長得像睦疫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子鞭呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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