React Native
1.React Native之了解
1.1 Native開發優勢:
Native的原生控件有更好的體驗;
Native有更好的手勢識別;
Native有更合適的線程模型,盡管Web Worker可以解決一部分問題,但如圖像解碼、文本渲染仍無法多線程渲染,這影響了Web的流暢性。
1.2 React Native優勢:
1.既擁有Native的用戶體驗、又保留React的開發效率(RN通過JavaScript Core解析JavaScript模塊,轉換成原生Native組件渲染)
2.React Native基本完成了對多端的支持,可以靈活的使用HTML和CSS布局,使用React語法構建組件,實現:H5, Android, iOS多端代碼的復用
3.追求極致的用戶體驗:實時熱部署(CodePush在修復一些小問題和添加新特性的時候,不需要經過二進制打包,可以直接推送代碼進行實時更新。)
4.UI排版的問題:
類似HTML + CSS的排版使用原生控件渲染的框架:
BeeFramework,BeeFramework雖然開源多年,而且有2000多的star數,但是受限于它自身的影響力以及框架的復雜性,一直沒有很大的成功。
React Native采用了類似HTML + CSS的排版,可以內嵌到模塊,也可以全局使用,定義樣式變得非常簡單通用。引入了Flexbox布局,使用很方便,學習起來也更簡單。
5.動態綁定,這個React的基本功能,被帶到了客戶端開發中來,數據和視圖是動態綁定的,數據發生變化,視圖會跟著變化,很多操作視圖的代碼都可以省略了。
6.引入了方便的npm管理,有大量現成的nodejs包可以用(例如moment,underscore等常用模塊),還可以把自己項目模塊搞到內部npm上做通用組件,另外,npm上還有不少別人寫的react native的插件。
7.第三方組件里有一個可以把icon font引入項目的組件,可以在任何顯示圖標的地方直接用icon font顯示
8.調試很方便,一次編譯后,每次改了js代碼,只需要在模擬器里command+R即可重新加載代碼。有問題會直接報錯,里面有代碼行數等詳細信息。
9.完整封裝了各種js內置的方法,例如:setTimeout,setInterval,XMLHttpRequest,localstorage,console.log等,都是用oc原生方法封裝的。
10.引入ES6的支持,可以使用各種新特性,例如最常用的箭頭函數,解決this作用域亂套的問題。
1.3 React Native是什么?

Facebook于2015年9月15日發布React Native
廣大開發者可以使用JavaScript和React開發跨平臺移動應用.
React Native提倡組件化開發:即提供一個個封裝好的組件,組件相互嵌套形成新的組件
1.4 React Native開發注意事項
目前react native在iOS上僅支持iOS8以上,Android僅支持Android4.1以上版本;
由于React Native的版本更新速度很快,如果沒有深厚的JavaScript基礎,建議選擇:
功能適中,交互一般,不需要特別多的系統原生支持;
對于部分復雜的應用,可以考慮原生+React Native混合開發
學習網站:
github地址: https://github.com/facebook/react-native
官網文檔: http://facebook.github.io/react-native/docs/getting-started.html
1.5 React Native開發環境:
參考中文React Native網站:
2.React Native之學習
2.1 FlexBox布局:
彈性盒模型(The Flexible Box Module),又叫Flexbox,意為“彈性布局”,旨在通過彈性的方式來對齊和分布容器中內容的空間,使其能適應不同屏幕,為盒裝模型提供最大的靈活性。
Flex布局主要思想是:讓容器有能力讓其子項目能夠改變其寬度、高度(甚至是順序),以最佳方式填充可用空間;
Flex學習入門網站:Flex布局教程
flex基本概念:
采用Flex布局的元素,稱為Flex容器(flex container),簡稱"容器"。它的所有子元素自動成為容器成員,稱為Flex項目(flex item),簡稱"項目"。
容器默認存在兩根軸:水平的主軸(main axis)和垂直的交叉軸(cross axis)。主軸的開始位置(與邊框的交叉點)叫做main start,結束位置叫做main end;交叉軸的開始位置叫做cross start,結束位置叫做cross end。
項目默認沿主軸排列。單個項目占據的主軸空間叫做main size,占據的交叉軸空間叫做cross size。
flex常用屬性總結:
容器屬性:
flex-direction(主軸方向)
flex-wrap(是否換行)
justify-content(item在主軸對齊方式) ,
align-items(item在交叉軸上如何對齊) ,
元素屬性:
Flex:彈性寬度:寬度=item該flex值/該容器所有item的flex和*(容器寬度-該容器item沒有設置flex的直接寬度)
align-self:性允許單個項目有與其他項目不一樣的對齊方式,可覆蓋align-items屬性
2.2 Touchable系列組件:
高亮觸摸TouchableHighlight:
當手指點擊按下的時候,該視圖的不透明度會進行降低同時會看到相應的顏色,其實現原理則是在底層新添加了一個View。TouchableHighlight只能進行一層嵌套,不能多層嵌套。
常用屬性:
activeOpacity number設置組件在進行觸摸的時候,顯示的不透明度(取值在0-1之間)
onHideUnderlay function方法當底層被隱藏的時候調用
onShowUnderlay function方法當底層顯示的時候調用
style可以設置控件的風格演示,該風格演示可以參考View組件的style
underlayColor當觸摸或者點擊控件的時候顯示出的顏色
不透明觸摸TouchableOpacity
該組件封裝了響應觸摸事件;當點擊按下的時候,該組件的透明度會降低。等等
代碼示例
style={styles.button}
source={require('./button.png')}
/>
style={styles.button}
source={require('image!myButton')}
/>
2.3組件生命周期:

一:實例化階段函數分析:
1,getDefaultProps
初始化一些默認的屬性,通常會將固定的內容放在這個函數中進行初始化和賦值;
可以利用this.props獲取組件在這里初始化它的屬性,組件自己不可以自己修改props(即:props可認為是只讀的)
2,getInitialState
用于對組件的一些狀態進行初始化;在以后的過程中,會再次調用,所以可以將控制控件的狀態的一些變量放在這里初始化,如控件上顯示的文字,可以通過this.state來獲取值,通過this.setState來修改state值,一旦調用了this.setState方法,組件一定會調用render方法,React框架會自動根據DOM的狀態來判斷是否需要真正的渲染。
3,componentWillMount
相當于OC中的ViewWillAppear方法.
4,render
render是一個組件中必須有的方法,本質上是一個函數,并返回JSX或其他組件來構成DOM,和Android的XML布局類似,注意:只能返回一個頂級元素;可通過this.state和this.props數據。
5,componentDidMount
在調用了render方法后一般會在這個函數中處理網絡請求等加載數據的操作;因為UI已經成功被渲染出來,所以放在這個函數里進行請求操作,不會出現UI上的錯誤。
二,存在期階段函數功能分析:
componentWillReceiveProps
指父元素對組件的props或state進行了修改
shouldComponentUpdate
一般用于優化,可以返回false或true來控制是否進行渲染
componentWillUpdate
組件刷新前調用,類似componentWillMount
componentDidUpdate
更新后的hook
三、銷毀期階段函數功能分析:
用于清理一些無用的內容,如:點擊事件Listener,只有一個過程:componentWillUnmount
2.4請求網絡數據:
React Native中通常是通過Ajax (異步的JavaScript和XML)請求從服務器獲取數據,然后在componentDidMount方法中創建Ajax請求,等到請求成功,再用this.setState方法重新渲染UI。
2.5 OC, Recat Native混合開發:
直接在iOS項目中寫代碼就能實現OC,reactNative混合開發,在需要引入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定義的一個TGMeituan組件
AppDelegate.m中在合適的位置引用rootView,即可實現混合開發
export default class TGMeituan extends Component {
render() {
return (
);
}
}
2.6 ES5和ES6的React Native差異化:
區別1:創建組件


組件是一個自定義的js對象,在es5中使用React.createClass();在es6中必須繼承React.component,
區別2:組件的屬性props


在ES6中,其為屬性:defaultProps(可以標識static定義在class內,也可以定義在class外),而在ES5中,其為方法:getDefaultProps: function(){return {name:value}};
區別3:組件的狀態state


上圖左為ES5 ,右為ES6
2.7 React Native不足:
組件不全,第三方組件也不全,遇到某些特殊功能,需要搗鼓很久,例如攝像相關的,文件讀寫,文件上傳之類的組件。
性能并非媲美原生,還是有一些損耗的,特別是交換大數據的時候,例如讀取相冊。
ios和android代碼并非通用,有可能會需要維護兩套,或者在代碼內做一些判斷。
并非網上大家說的,寫一次代碼,多端通用,網頁版和客戶端版完全不是一個概念,只有部分代碼可重用。
把代碼都打包到bundle里面,不知道蘋果對這種開發方式是否會不太喜歡,甚至拒絕上線。
打包出來的JSBundle過大;
首次進入RN頁面加載緩慢;
穩定性不夠,有大量因為RN導致的Crash:
iOS的Crash,基本都來自RCTFatalException,都是RCTFatal拋出錯誤信息所知,處理也相對簡單,設置自己的Error Handler即可。
void RCTSetFatalHandler(RCTFatalHandler fatalHandler);
大數據量時ListView加載卡頓。
3.ListView重用優化
3.1 ListView不能重用的原因
首先RN的ListView其實是基于RN的RCTScrollView來實現的。它也實現了類似UIKit中通過DataSource來控制數據,以及是否要做一些界面的刷新

這個View會有一個RCTView會引用它。當這個View被移出屏幕之外,再觀察他的內存引用時,它就只被RCTUIManager引用了:
RN為了能夠保持一定的UI上的性能,他用UImanager來管理所有的UI元素,只要創建過的,還有可能被顯示在界面上的東西,他都用這個UImanager來去管理,從而在進行Dom Diff時能夠減少View的創建和銷毀。
3.2 ListView多做了什么?
然后,我們再來看看ListView本身比RCTScrollView多做的哪些東西,首先ListView包含兩個屬性—- initialListSize和pageSize,initialListSize決定了第一屏加載item的數量,pageSize則是當你需要加載更多的時候,每次需要載入多少的item,這樣做的主要目的在盡量減少你手機加載第一屏時所需要的時間。
還有就是它還實現了從JS端實現了Section Header,Header,Footer的封裝,以及實現了監聽onScroll事件,隨著View的滾動動態的添加row view。
3.3那么ListView相當于UITableView少了一點什么呢?
怎么沒有提到復用?

我們先看一下iOS的JS,JS里面只有一行代碼
module.exports = require('ScrollView');

3.4 ListView性能優化解決方案
Bridge一個UITableView

在RN中我們要bridge一個RN的View組件,我們需要實現RCTComponent這個protocol,這里有兩個很重要的方法
- (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex;
-
(void)removeReactSubview:(id)subview;
這兩個方法是RN做Dom Diff的關鍵
什么是Dom Diff呢
在界面發生變化前,界面存在一個Dom Tree,發生業務變化之后是另外一個Dom tree,Tree中的每個元素都有自己的引用值,Diff其實就是找出兩個Tree的差異點來確定需要進行更新的節點。最終確定一個需要插入和刪除的View的列表,并通知相應的Dom節點來處理。
但是RN的UI處理方式和原生對UI處理完全不一樣,我們如何Bridge一個TableView呢,我們想到了一個方法。
我們創建一些VirtualView,他只是遵從了RCTComponent協議,他其實并不是一個真正的View,我把它形成一個組件,把它Bridge到JS,這就使得,你在寫JSX的時候,就可以直接用VirtualView來去做布局了。在RN里面做布局的時候我們用VirtualView來做布局。但是最終在insertReactSubview時,我們把這些VirtualView當做數據去處理,通過VirtualView和RealView的對應關系,把它轉化成一個真實的View對象添加到TableView中去。
用這個圖來說,更清晰一些。

首先我們寫的是一個JSX,React把它轉化成Dom Tree,在進行Dom Diff后,React會調用insertReactSubview傳入VirtualView,我們通過VirtualView生成Tree Data,
通過VirtualView和RealView的對應關系,我們創建RealView去真正的添加到原生的View上。
但是這里又產生另外一個問題,大家會自定義一個cell的一個對象來去做的。這個對象,能夠接收你特定的數據,對這個cell重新去set一些控件的值,然后把界面更新。
但是在JS里面我們并沒有辦法這樣做,在RN中,我們不可能動態的去往Native里面去加一個類。
那么我們是如何做到,在復用的時候對于Cell上面的子View能夠去設置更新他的數據?

我們在所有子view上面我們也加上了tag屬性,在更新數據的時候我們通過tag找到更新的子view上面的view對他做數據的更新的。所以并不是只有Cell有這樣的tag,包括子view也會有這樣的tag,這樣就做到了可以獲取到對應tag的子view并對子view的數據進行更新。

最后,為了客戶端的同學在使用這個TableView時更好上手一些,我們把幾乎整套的TableViewDataSource方法,全部照搬到了RN中,所以我們在創建這個ListView的時候我們需要去設置很多的回調方法,這樣做也是為了能夠更快的做一些界面的遷移工作。
3.5 ListView性能優化解決方案的缺點
首先既然它需要做映射,我們肯定需要做一個Virtualview到NativeView,大多數的cell里面如果做展示來用的話,Label和Image基本上能夠滿足大多數的需求了。所以我們現在只是做了Label和Image的對應工作,但在RN的一些官方控件,在這個view里面都是沒法直接使用的。
還有一個缺點就是說,因為我們是按照TableView的邏輯去做的,這個邏輯其實在Android上可能不適用,因為Android的ListView實現跟iOS完全不是一個邏輯,導致使用這個ListView的RN代碼,可能沒法直接應用到Android里面去。