一. 什么是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)注推捐!