React Native
1.React Native之了解
1.1 Native開發(fā)優(yōu)勢(shì):
Native的原生控件有更好的體驗(yàn);
Native有更好的手勢(shì)識(shí)別;
Native有更合適的線程模型,盡管Web Worker可以解決一部分問題,但如圖像解碼、文本渲染仍無法多線程渲染,這影響了Web的流暢性。
1.2 React Native優(yōu)勢(shì):
1.既擁有Native的用戶體驗(yàn)、又保留React的開發(fā)效率(RN 通過 JavaScript Core 解析 JavaScript 模塊,轉(zhuǎn)換成原生 Native 組件渲染)
2.React Native基本完成了對(duì)多端的支持,可以靈活的使用HTML和CSS布局,使用React語法構(gòu)建組件,實(shí)現(xiàn):H5, Android, iOS 多端代碼的復(fù)用
3.追求極致的用戶體驗(yàn):實(shí)時(shí)熱部署(CodePush 在修復(fù)一些小問題和添加新特性的時(shí)候,不需要經(jīng)過二進(jìn)制打包,可以直接推送代碼進(jìn)行實(shí)時(shí)更新。)
4.UI排版的問題:
類似HTML + CSS的排版使用原生控件渲染的框架:
BeeFramework ,BeeFramework雖然開源多年,而且有2000多的star數(shù),但是受限于它自身的影響力以及框架的復(fù)雜性,一直沒有很大的成功。
React Native采用了類似HTML + CSS的排版,可以內(nèi)嵌到模塊,也可以全局使用,定義樣式變得非常簡(jiǎn)單通用。 引入了Flexbox布局,使用很方便,學(xué)習(xí)起來也更簡(jiǎn)單。
5.動(dòng)態(tài)綁定,這個(gè)React的基本功能,被帶到了客戶端開發(fā)中來,數(shù)據(jù)和視圖是動(dòng)態(tài)綁定的,數(shù)據(jù)發(fā)生變化,視圖會(huì)跟著變化,很多操作視圖的代碼都可以省略了。
6.引入了方便的npm管理,有大量現(xiàn)成的nodejs包可以用(例如moment,underscore等常用模塊),還可以把自己項(xiàng)目模塊搞到內(nèi)部npm上做通用組件,另外,npm上還有不少別人寫的react native的插件。
7.第三方組件里有一個(gè)可以把icon font引入項(xiàng)目的組件,可以在任何顯示圖標(biāo)的地方直接用icon font顯示
8.調(diào)試很方便,一次編譯后,每次改了js代碼,只需要在模擬器里command+R即可重新加載代碼。有問題會(huì)直接報(bào)錯(cuò),里面有代碼行數(shù)等詳細(xì)信息。
9.完整封裝了各種js內(nèi)置的方法,例如:setTimeout,setInterval,XMLHttpRequest,localstorage,console.log等,都是用oc原生方法封裝的。
10.引入ES6的支持,可以使用各種新特性,例如最常用的箭頭函數(shù),解決this作用域亂套的問題。
1.3 React Native是什么?
Facebook于2015年9月15日發(fā)布React Native
廣大開發(fā)者可以使用JavaScript和React開發(fā)跨平臺(tái)移動(dòng)應(yīng)用.
React Native提倡組件化開發(fā): 即提供一個(gè)個(gè)封裝好的組件,組件相互嵌套形成新的組件
1.4 React Native開發(fā)注意事項(xiàng)
目前react native在iOS上僅支持iOS8以上,Android僅支持Android4.1以上版本;
由于React Native的版本更新速度很快,如果沒有深厚的JavaScript基礎(chǔ),建議選擇:
功能適中,交互一般,不需要特別多的系統(tǒng)原生支持;
對(duì)于部分復(fù)雜的應(yīng)用,可以考慮原生+React Native混合開發(fā)
學(xué)習(xí)網(wǎng)站:
github地址:https://github.com/facebook/react-native
官網(wǎng)文檔:http://facebook.github.io/react-native/docs/getting-started.html
1.5 React Native開發(fā)環(huán)境:
參考中文React Native網(wǎng)站:
2.React Native之學(xué)習(xí)
2.1 FlexBox布局:
彈性盒模型(The Flexible Box Module),又叫Flexbox,意為“彈性布局”,旨在通過彈性的方式來對(duì)齊和分布容器中內(nèi)容的空間,使其能適應(yīng)不同屏幕,為盒裝模型提供最大的靈活性。
Flex布局主要思想是:讓容器有能力讓其子項(xiàng)目能夠改變其寬度、高度(甚至是順序),以最佳方式填充可用空間;
Flex學(xué)習(xí)入門網(wǎng)站:Flex 布局教程
flex基本概念:
采用Flex布局的元素,稱為Flex容器(flex container),簡(jiǎn)稱"容器"。它的所有子元素自動(dòng)成為容器成員,稱為Flex項(xiàng)目(flex item),簡(jiǎn)稱"項(xiàng)目"。
容器默認(rèn)存在兩根軸:水平的主軸(main axis)和垂直的交叉軸(cross axis)。主軸的開始位置(與邊框的交叉點(diǎn))叫做main start,結(jié)束位置叫做main end;交叉軸的開始位置叫做cross start,結(jié)束位置叫做cross end。
項(xiàng)目默認(rèn)沿主軸排列。單個(gè)項(xiàng)目占據(jù)的主軸空間叫做main size,占據(jù)的交叉軸空間叫做cross size。
flex常用屬性總結(jié):
容器屬性:
flex-direction(主軸方向)
flex-wrap(是否換行)
justify-content(item在主軸對(duì)齊方式) ,
align-items(item在交叉軸上如何對(duì)齊) ,
元素屬性:
Flex:彈性寬度:寬度=item該flex值/該容器所有item的flex和*(容器寬度-該容器item沒有設(shè)置flex的直接寬度)
align-self:性允許單個(gè)項(xiàng)目有與其他項(xiàng)目不一樣的對(duì)齊方式,可覆蓋align-items屬性
2.2 Touchable系列組件:
高亮觸摸 ?TouchableHighlight:
當(dāng)手指點(diǎn)擊按下的時(shí)候,該視圖的不透明度會(huì)進(jìn)行降低同時(shí)會(huì)看到相應(yīng)的顏色,其實(shí)現(xiàn)原理則是在底層新添加了一個(gè)View。TouchableHighlight只能進(jìn)行一層嵌套,不能多層嵌套。
常用屬性:
activeOpacity? number? 設(shè)置組件在進(jìn)行觸摸的時(shí)候,顯示的不透明度(取值在0-1之間)
onHideUnderlay? function? 方法 當(dāng)?shù)讓颖浑[藏的時(shí)候調(diào)用
onShowUnderlay? function 方法 當(dāng)?shù)讓语@示的時(shí)候調(diào)用
style?? 可以設(shè)置控件的風(fēng)格演示,該風(fēng)格演示可以參考View組件的style
underlayColor? 當(dāng)觸摸或者點(diǎn)擊控件的時(shí)候顯示出的顏色
不透明觸摸??TouchableOpacity
該組件封裝了響應(yīng)觸摸事件;當(dāng)點(diǎn)擊按下的時(shí)候,該組件的透明度會(huì)降低。等等
代碼示例
style={styles.button}
source={require('./button.png')}
/>
style={styles.button}
source={require('image!myButton')}
/>
2.3 組件生命周期:
一:實(shí)例化階段函數(shù)分析:
1,getDefaultProps
初始化一些默認(rèn)的屬性,通常會(huì)將固定的內(nèi)容放在這個(gè)函數(shù) 中進(jìn)行初始化和賦值; ? ? ? ? 可以利用this.props獲取組件在這里初始化它的屬性,組件自己不可以自己修改props(即:props可認(rèn)為是只讀的)
2,getInitialState
用于對(duì)組件的一些狀態(tài)進(jìn)行初始化;在以后的過程中,會(huì)再次調(diào)用,所以可以將控制控件的狀態(tài)的一些變量放在這里初始化,如控件上顯示的文字,可以通過this.state來獲取值,通過this.setState來修改state值,一旦調(diào)用了this.setState方法,組件一定會(huì)調(diào)用render方法,React框架會(huì)自動(dòng)根據(jù)DOM的狀態(tài)來判斷是否需要真正的渲染。
3,componentWillMount
相當(dāng)于OC中的ViewWillAppear方法.
4,render
render是一個(gè)組件中必須有的方法,本質(zhì)上是一個(gè)函數(shù),并返回JSX或其他組件來構(gòu)成DOM,和Android的XML布局類似,注意:只能返回一個(gè)頂級(jí)元素?;可通過this.state和this.props數(shù)據(jù)?。
5,componentDidMount
在調(diào)用了render方法后一般會(huì)在這個(gè)函數(shù)中處理網(wǎng)絡(luò)請(qǐng)求等加載數(shù)據(jù)的操作;因?yàn)閁I已經(jīng)成功被渲染出來, 所以放在這個(gè)函數(shù)里進(jìn)行請(qǐng)求操作,不會(huì)出現(xiàn)UI上的錯(cuò)誤。
二,存在期階段函數(shù)功能分析:
componentWillReceiveProps
指父元素對(duì)組件的props或state進(jìn)行了修改
shouldComponentUpdate
一般用于優(yōu)化,可以返回false或true來控制是否進(jìn)行渲染
componentWillUpdate
組件刷新前調(diào)用,類似componentWillMount
componentDidUpdate
更新后的hook
三、銷毀期階段函數(shù)功能分析:
用于清理一些無用的內(nèi)容,如:點(diǎn)擊事件Listener,只有一個(gè)過程:componentWillUnmount
2.4 請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù):
React Native中通常是通過 Ajax (異步的 JavaScript 和 XML)請(qǐng)求從服務(wù)器獲取數(shù)據(jù),然后在componentDidMount?方法中創(chuàng)建 Ajax 請(qǐng)求,等到請(qǐng)求成功,再用?this.setState?方法重新渲染 UI。
2.5 OC, Recat Native混合開發(fā):
直接在iOS項(xiàng)目中寫代碼就能實(shí)現(xiàn)OC,reactNative混合開發(fā),在需要引入React Native 的位置引用該模塊即可
AppDelegate.m部分代碼
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"TGMeituan"
initialProperties:nil
launchOptions:launchOptions];
index.ios.js 部分代碼
AppRegistry.registerComponent('TGMeituan', () => TGMeituan);
xcode的代碼引用了index.ios.js文件中的'TGMeituan', index.ios.js其輸出了index.ios.js定義的一個(gè)TGMeituan組件 AppDelegate.m 中在合適的位置引用rootView,即可實(shí)現(xiàn)混合開發(fā)
export default class TGMeituan extends Component {
render() {
return (
);
}
}
2.6 ES5和ES6的React Native差異化:
區(qū)別1:創(chuàng)建組件
組件是一個(gè)自定義的js對(duì)象,在es5中使用React.createClass();在es6中必須繼承React.component,
區(qū)別2:組件的屬性props
在ES6中,其為屬性:defaultProps(可以標(biāo)識(shí)static定義在class內(nèi),也可以定義在class外),而在ES5中,其為方法:getDefaultProps: function(){return {name:value}};
區(qū)別3:組件的狀態(tài)state
上圖左為ES5 ,右為ES6
2.7 React Native不足:
組件不全,第三方組件也不全,遇到某些特殊功能,需要搗鼓很久,例如攝像相關(guān)的,文件讀寫,文件上傳之類的組件。
性能并非媲美原生,還是有一些損耗的,特別是交換大數(shù)據(jù)的時(shí)候,例如讀取相冊(cè)。
ios和android代碼并非通用,有可能會(huì)需要維護(hù)兩套,或者在代碼內(nèi)做一些判斷。
并非網(wǎng)上大家說的,寫一次代碼,多端通用,網(wǎng)頁版和客戶端版完全不是一個(gè)概念,只有部分代碼可重用。
把代碼都打包到bundle里面,不知道蘋果對(duì)這種開發(fā)方式是否會(huì)不太喜歡,甚至拒絕上線。
打包出來的 JSBundle 過大;
首次進(jìn)入 RN 頁面加載緩慢;
穩(wěn)定性不夠,有大量因?yàn)?RN 導(dǎo)致的 Crash:
iOS 的 Crash,基本都來自RCTFatalException,都是RCTFatal拋出錯(cuò)誤信息所知,處理也相對(duì)簡(jiǎn)單,設(shè)置自己的Error Handler即可。 void RCTSetFatalHandler(RCTFatalHandler fatalHandler);
大數(shù)據(jù)量時(shí) ListView 加載卡頓。
3.ListView重用優(yōu)化
3.1 ListView不能重用的原因
首先RN的 ListView 其實(shí)是基于 RN 的 RCTScrollView 來實(shí)現(xiàn)的。它也實(shí)現(xiàn)了類似 UIKit 中通過 DataSource 來控制數(shù)據(jù),以及是否要做一些界面的刷新
這個(gè)View會(huì)有一個(gè) RCTView 會(huì)引用它。當(dāng)這個(gè) View 被移出屏幕之外,再觀察他的內(nèi)存引用時(shí),它就只被 RCTUIManager 引用了:
RN 為了能夠保持一定的 UI 上的性能,他用 UImanager 來管理所有的 UI 元素,只要?jiǎng)?chuàng)建過的,還有可能被顯示在界面上的東西,他都用這個(gè) UImanager 來去管理,從而在進(jìn)行 Dom Diff 時(shí)能夠減少 View 的創(chuàng)建和銷毀。
3.2 ListView 多做了什么?
然后,我們?cè)賮砜纯?ListView 本身比 RCTScrollView 多做的哪些東西,首先 ListView 包含兩個(gè)屬性 —- initialListSize 和 pageSize ,initialListSize決定了第一屏加載item的數(shù)量,pageSize則是當(dāng)你需要加載更多的時(shí)候,每次需要載入多少的item,這樣做的主要目的在盡量減少你手機(jī)加載第一屏?xí)r所需要的時(shí)間。
還有就是它還實(shí)現(xiàn)了從JS端實(shí)現(xiàn)了 Section Header,Header,F(xiàn)ooter 的封裝,以及實(shí)現(xiàn)了監(jiān)聽 onScroll 事件,隨著 View 的滾動(dòng)動(dòng)態(tài)的添加 row view。
3.3 那么ListView相當(dāng)于UITableView少了一點(diǎn)什么呢?
怎么沒有提到復(fù)用?
我們先看一下 iOS 的 JS,JS里面只有一行代碼
module.exports = require('ScrollView');
3.4 ListView 性能優(yōu)化解決方案
Bridge 一個(gè) UITableView
在RN中我們要 bridge 一個(gè) RN 的 View 組件,我們需要實(shí)現(xiàn) RCTComponent 這個(gè) protocol,這里有兩個(gè)很重要的方法
- (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex;
- (void)removeReactSubview:(id)subview;
這兩個(gè)方法是 RN 做 Dom Diff 的關(guān)鍵
什么是Dom Diff呢
在界面發(fā)生變化前,界面存在一個(gè) Dom Tree,發(fā)生業(yè)務(wù)變化之后是另外一個(gè) Dom tree,Tree中的每個(gè)元素都有自己的引用值,Diff 其實(shí)就是找出兩個(gè) Tree 的差異點(diǎn)來確定需要進(jìn)行更新的節(jié)點(diǎn)。最終確定一個(gè)需要插入和刪除的 View 的列表,并通知相應(yīng)的 Dom 節(jié)點(diǎn)來處理。
但是RN的UI處理方式和原生對(duì)UI處理完全不一樣,我們?nèi)绾?Bridge 一個(gè) TableView 呢,我們想到了一個(gè)方法。
我們創(chuàng)建一些 VirtualView,他只是遵從了 RCTComponent 協(xié)議,他其實(shí)并不是一個(gè)真正的 View,我把它形成一個(gè)組件,把它 Bridge 到 JS,這就使得,你在寫 JSX 的時(shí)候,就可以直接用 VirtualView 來去做布局了。在RN里面做布局的時(shí)候我們用VirtualView來做布局。但是最終在 insertReactSubview 時(shí),我們把這些 VirtualView 當(dāng)做數(shù)據(jù)去處理,通過 VirtualView 和RealView 的對(duì)應(yīng)關(guān)系,把它轉(zhuǎn)化成一個(gè)真實(shí)的 View 對(duì)象添加到 TableView 中去。
用這個(gè)圖來說,更清晰一些。
首先我們寫的是一個(gè) JSX,React 把它轉(zhuǎn)化成 Dom Tree,在進(jìn)行 Dom Diff 后,React 會(huì)調(diào)用 insertReactSubview 傳入 VirtualView,我們通過 VirtualView 生成 Tree Data, 通過 VirtualView 和 RealView 的對(duì)應(yīng)關(guān)系,我們創(chuàng)建 RealView 去真正的添加到原生的 View 上。
但是這里又產(chǎn)生另外一個(gè)問題,大家會(huì)自定義一個(gè) cell 的一個(gè)對(duì)象來去做的。這個(gè)對(duì)象,能夠接收你特定的數(shù)據(jù),對(duì)這個(gè) cell 重新去 set 一些控件的值,然后把界面更新。 但是在JS里面我們并沒有辦法這樣做,在 RN 中,我們不可能動(dòng)態(tài)的去往 Native 里面去加一個(gè)類。
那么我們是如何做到,在復(fù)用的時(shí)候?qū)τ?Cell 上面的子View能夠去設(shè)置更新他的數(shù)據(jù)?
我們?cè)谒凶?view 上面我們也加上了 tag 屬性,在更新數(shù)據(jù)的時(shí)候我們通過 tag 找到更新的子 view上面的 view 對(duì)他做數(shù)據(jù)的更新的。所以并不是只有Cell有這樣的tag,包括子 view 也會(huì)有這樣的 tag,這樣就做到了可以獲取到對(duì)應(yīng) tag 的子 view 并對(duì)子 view 的數(shù)據(jù)進(jìn)行更新。
最后,為了客戶端的同學(xué)在使用這個(gè) TableView 時(shí)更好上手一些,我們把幾乎整套的 TableViewDataSource 方法,全部照搬到了 RN 中,所以我們?cè)趧?chuàng)建這個(gè) ListView 的時(shí)候我們需要去設(shè)置很多的回調(diào)方法,這樣做也是為了能夠更快的做一些界面的遷移工作。
3.5 ListView 性能優(yōu)化解決方案的缺點(diǎn)
首先既然它需要做映射,我們肯定需要做一個(gè) Virtualview 到 NativeView,大多數(shù)的 cell 里面如果做展示來用的話,Label 和 Image 基本上能夠滿足大多數(shù)的需求了。所以我們現(xiàn)在只是做了 Label 和 Image 的對(duì)應(yīng)工作,但在RN的一些官方控件,在這個(gè) view 里面都是沒法直接使用的。
還有一個(gè)缺點(diǎn)就是說,因?yàn)槲覀兪前凑?TableView 的邏輯去做的,這個(gè)邏輯其實(shí)在 Android 上可能不適用,因?yàn)?Android 的 ListView 實(shí)現(xiàn)跟iOS完全不是一個(gè)邏輯,導(dǎo)致使用這個(gè) ListView 的 RN 代碼,可能沒法直接應(yīng)用到 Android 里面去。