如何評價 React Native?
write native apps with React.js?
本文首發於http://Div.io: 我對 React Native 的理解和看法,最近我們也會持續分析他,並且也在Android上做類似的實現,觀點會迭代,文章會更新。
- - - - - -
React native充分利用了Facebook的現有輪子,是一個很優秀的集成作品,並且我相信這個團隊對前端的了解很深刻,否則不可能讓Native code「退居二線」。
對應到前端開發,整個系統結構是這樣:
- JSX vs HTML
- CSS-layout vs css
- ECMAScript 6 vs ECMAScript 5
- React native View vs DOM
- 無需編譯,我在第一次編譯了ipa裝好以後,就再也沒更新過app,只要更新雲端的js代碼,reload一下,整個界面就全變了。
- 多數布局代碼都是JSX,所有Native組件都是標籤化的,這對於前端程序員來說,降低了不少學習成本,也大大減少了代碼量。不信你可以看看JSX編譯後的代碼。
- 復用React系統,也減少了一定學習和開發成本,更重要的是利用了React裡面的分層和diff機制。js層傳給Native層的是一個diff後的json,然後由Native將這個數據映射成真正的布局視圖。
- css-layout也是點睛之筆,前端可以繼續用熟悉的類css方式來編寫布局,通過這個工具轉換成constrain布局。
- 系統只有js-objc的單向調用,就是把原生UI組件的方法通過javascritcore或者webview(低版本iOS)映射到js中來,整個調用過程是非同步的,這樣的設計令React native可以讓js運行在桌面chrome中,通過websocket連接Native code和桌面chrome,極大地方便了調試。對其中的機制Bang的一篇文章寫得很詳細,我就不拾人牙慧了:React Native通信機制詳解 ? bang』s blog 。但這樣設計也會帶來一些問題,後面說。
- 點按操作也被抽象成了一組組件(TouchableXXX),這種抽象方式是我在之前做類似工作中沒有想到的。facebook還列出Native為什麼和web「手感」不同的原因:實時的點按反饋和取消能力。React Native 這套相應機制設計得很完善,能像Native code那樣控制整個點按操作的所有過程。
- Debug相當方便!修改了js以後,通過內建的nodejs watcher編譯成bundle,在模擬器裡面按cmd+r就可以看到效果。而且按cmd+d,可以打開一個chrome窗口,所有的js都移到了chrome裡面運行,所以什麼斷點單步打調用棧,都不在話下。
上面的既是特點也是優點,下面說說缺點,或者應該說:「仍然遺留的問題」,在我看來,這個方案已經超越了Hybird方案。
- 系統仍然(不得不)依賴原生組件暴露出來的組件和方法。舉兩個例子,ScrollView這個組件,在Native層是有大量事件的,scrollViewWillBeginDragging, scrollViewWillEndDragging,scrollViewDidEndDragging等等,這些事件在現有的版本都沒有暴露,基本上做不了組件聯動效果。另外,這個版本中有大量組件是iOS only的:ActivityIndicatorIOS、DatePickerIOS、NavigatorIOS、PickerIOS、SliderIOS、SwitchIOS、TabBarIOS、AlertIOS、AppStateIOS、LinkingIOS、PushNotificationIOS、StatusBarIOS、VibrationIOS,反過來看,剩餘的都是一些抽象程度極強的基本組件。這樣,用戶必須在不同的平台下寫兩套代碼,而且所有能力仍然強烈依賴 React native 開發人員暴露的介面。
- 由於最外層是React,初次學習成本高,不像往常的Hybird方案,只要多學幾個JS API就可以開始幹活了。當然,React的確讓後續開發變得簡單了一些,這麼一套外來的(基於iOS)、殘缺不全的(css-layout)在React的包裝下,的確顯得不那麼面目可憎了。
另外,React Native仍然很不完善。文檔還不全,我基本上是看著他的示例代碼完成的demo,集成到已有app的文檔也是今天才出來。按照官方的說法,Android版本要到半年後才發布:Blog | React ,屆時整個系統設計可能還會有很大的變化。
PS,在使用Tabbar的時候,我驚喜的發現他們居然用了iconfont方案,我現在手頭的項目中也有同樣的實現,不過API怎麼設計一直很頭疼。結果,我發現他是這麼寫的:
&
在 _ix_DEPRECATED 的定義處,有一句注釋: // TODO(nicklockwood): How can this fit our require system?
以上。
下面是一周前,在React native還沒開源的時候,通過反解ipa的一些分析過程,有興趣的可以看看。
------------------------簡單粗暴的分割線--------------------
背景和調研手段
React Native還沒開源,最近和組裡兄弟「反編譯」了Facebook Group(這個應用是用React Native實現的)的ipa代碼,出來幾百個JS文件,格式化一下,花了幾天時間讀了一下源碼,對React Native的內部核心機制算是有了一個基本了解。
React Native的核心實現:
先簡單說幾點,詳細的等回頭更新。
1. React Native裡面沒有webview,這貨不是Hybrid app,裡面執行JS是用的
JavascriptCore。
2. 再說React Native的核心,iOS Native code提供了十來個最基本核心的類(RCTDeviceEventEmitter、RCTRenderingPerf等)、或組件(RCTView、RCTTextField、RCTTextView、RCTModalFullscreenView等),然後由React Native的JS部分,組成二十來個基本組件(Popover、Listview等),交由上層的業務方來使用(THGroupView)。3. 就如他們在宣傳時所說,他們實現了一套類似css的子集,用來解決樣式問題,相當複雜和強大,靠這個才能將Native的核心組件組成JS層的基本組件再組成業務端的業務組件,應該是採用facebook/css-layout · GitHub的C語言版本實現的(在ppt中我們看到了類似flex-direction: column一類的代碼,這個正是css-layout支持的語法)。
4. 在React Native中,寫JS的工程師解決的是「將基本組件拼裝成可用的React組件」的問題,寫Native Code的工程師解決的是「提供核心組件,提供足夠的擴展性、靈活性和性能」的問題。
React Native的設計考慮:
ReactJS對React Native有著直接的影響(我沒在生產環境中用過React,只看過代碼用過Angular,如果有誤請指出)
ReactJS裡面有這樣的設計:
1. ReactJS 的大工廠入口createElement返回的不是某個實體DOM對象,而只是一個數組
2. 通過源碼中 ui/browser/ 目錄中的代碼,將這個數組轉換成DOM
3. 底層的渲染核心是可以更換的
另外,Facebook自己有JSX,css-layout等開源項目,基於這些,如果要做一個用 JS來開發Native app的東西,很自然就想到了一套最有效率的搞法:
1. 將 ui/browser 裡面的代碼替換成一套 Native 的橋接JS(實際上,iOS版是通過
injectGenericComponentClass方法,將核心組件的方法注入到JS裡面 ),就直接復用React的MVVM,自動將數據映射到Native了
2. Native code裡面實現三組核心API,一組提供核心組件的API(create、update、delete),一組事件方法(ReactJS裡面的EventEmitter ),一組對css進行解析(css-layout)以及返回Style的ComputedStyle(React Native裡面叫meatureStyle)。
這樣,用上了ReactJS本身的所有核心功能和設計思路,Native的開發也足夠簡單。
那,React Native是什麼?
其實這東西從Native開發來說,相當於重新發明了一個瀏覽器渲染引擎並且套一個React的殼,從Web開發角度來說,就是把原來React的後端換成了Native code來實現,就跟Flipboard最近搞的React Canvas 一樣: Flipboard · GitHubreact-canvas
React Native的優勢和劣勢::
優勢相對Hybird app或者Webapp:
1. 不用Webview,徹底擺脫了Webview讓人不爽的交互和性能問題
2. 有較強的擴展性,這是因為Native端提供的是基本控制項,JS可以自由組合使用
3. 可以直接使用Native原生的「牛逼」動畫(在FB Group這個app裡面,面板滑出帶一點果凍彈動,面板基於某個點展開這種動畫隨處可見,這種動畫用Native code來做小菜一碟,但是用Web來做就難上加難)。
優勢相對於Native app:
1. 可以通過更新遠端JS,直接更新app,不過這快成為各家大型Native app的標配了…
劣勢:
1. 擴展性仍然遠遠不如web,也遠遠不如直接寫Native code(這個不用廢話解釋了吧)
2. 從Native到Web,要做很多概念轉換,勢必造成雙方都要妥協。比如web要用一套CSS的閹割版,Native通過css-layout拿到最終樣式再轉換成native原生的表達方式(比如iOS的ConstraintoriginCenter等屬性),再比如動畫。另外,若Android和iOS都要做相同的封裝,概念轉換就更複雜了。
更新1:添加了React對React Native的影響。
更新2:基本確定其使用了 css-layout,添加了對React Native的總結
更新3: React native已經開源了: React Native,只有iOS版。我寫了幾個demo,簡單看了看objc代碼並和開源前的我們的一些結論(見後文)交叉驗證。簡單地從前端工程師和系統整體角度說一下React native的特點和優劣吧。
更新4: 補充了幾條優勢和與前端開發的對照
在寫這個回答之前,我猶豫了很久,到底要不要唱反調呢,畢竟我也是一個也正在用RN做開發的人。但是看到前面這麼多吹的,我怕有的老闆在看了前面的回答之後,覺得只要找幾個前端工程師,就能在做前端頁面之外也能做原生開發了,我決定寫幾個在實際開發中遇到的問題及解決方法,如果大家覺得真的能hold住,再決定項目是不是完全轉向RN。
由於我最熟悉的還是iOS的那點東西,下面說的可能iOS多一些,但你要想查看更多問題,歡迎你到這裡查看:https://github.com/facebook/react-native/issues。
1. Cache:
我們現在項目中的圖片緩存完全是自己藉助[Redux-Persist](rt2zz/redux-persist)實現的(主要是為了能夠離線查看),但是這種Cache除了一些性能問題外,本身與iOS的URL Loading System是沒有關係的。比如說,正常情況下,如果圖片是在WebView打開查看的,你想再取出來,你只要不做任何特殊設置,你就可以通過NSURLCache取出來,像這樣:
NSURLCache *cache = [NSURLCache sharedURLCache];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSData *imgData = [cache cachedResponseForRequest:request].data;
UIImage *image = [UIImage imageWithData:imgData];
但是你會發現,在RN下URL Cache是取不出來的(至少我在40之前是這樣的,現在由於使用我們自己造的緩存,在新版本中這個情況沒有驗證),那你需要創建一個NSURLProtocol的子類,自己實現利用NSURLCache緩存:
#import "HttpProtocol.h"
@interface HttpProtocol ()
&<
NSURLSessionDelegate,
NSURLSessionDataDelegate
&>
@property(copy, nonatomic) NSURLSession* session;
@property(strong, nonatomic)NSURLSessionDataTask* task;
@end
@implementation HttpProtocol
+ (void)start {
[NSURLProtocol registerClass:self];
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if (request
([request.URL.scheme isEqualToString:@"http"] || [request.URL.scheme isEqualToString:@"https"])
([request.URL.pathExtension isEqualToString:@"jpg"] || [request.URL.pathExtension isEqualToString:@"png"] || [request.URL.pathExtension isEqualToString:@"bmp"] || [request.URL.pathExtension isEqualToString:@"gif"] ||
[request.URL.pathExtension isEqualToString:@"tiff"]|| [request.URL.pathExtension isEqualToString:@"jpeg"]||
[request.URL.pathExtension isEqualToString:@"JPEG"])) {
return YES;
}
return NO;
}
-(NSURLSession *)session {
if (!_session) {
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue new]];
}
return _session;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id &
return [super initWithRequest:request cachedResponse:cachedResponse client:client];
}
- (void)startLoading {
NSURLCache* cache = [NSURLCache sharedURLCache];
NSCachedURLResponse* cachedResponse = [cache cachedResponseForRequest:self.request];
if (cachedResponse) {//有緩存,從緩存中載入...
NSData* data= cachedResponse.data;
NSString* mimeType = cachedResponse.response.MIMEType;
NSString* encoding = cachedResponse.response.textEncodingName;
NSURLResponse* response = [[NSURLResponse alloc]initWithURL:self.request.URL MIMEType:mimeType expectedContentLength:data.length textEncodingName:encoding];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
} else {
NSMutableURLRequest* newRequest = [self.request mutableCopy];
newRequest.cachePolicy = NSURLRequestUseProtocolCachePolicy;
self.task = [self.session dataTaskWithRequest:newRequest];
[self.task resume];
}
}
-(void)stopLoading {
[self.task cancel];
self.task = nil;
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
[self.client URLProtocol:self didLoadData:data];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler{
completionHandler(proposedResponse);
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
if (error) {
[self.client URLProtocol:self didFailWithError:error];
} else {
[self.client URLProtocolDidFinishLoading:self];
}
}
@end
實際上,我認為這樣做緩存更加好,但是沒有時間改……
2. WebView:
講真,現在不用Webview的客戶端真的似乎好像是不存在,但是RN自身的UIWebview由於添加了一些員原來UIWebview不具備的能力,比如postMessage(WKWebview裡面的messagehandler),但是RN源碼本身hack實現是有一些問題的:
if (_messagingEnabled) {
#if RCT_DEV
// See isNative in lodash
NSString *testPostMessageNative = @"String(window.postMessage) === String(Object.hasOwnProperty).replace("hasOwnProperty", "postMessage")";
BOOL postMessageIsNative = [
[webView stringByEvaluatingJavaScriptFromString:testPostMessageNative]
isEqualToString:@"true"
];
if (!postMessageIsNative) {
RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
}
#endif
NSString *source = [NSString stringWithFormat:
@"window.originalPostMessage = window.postMessage;"
"window.postMessage = function(data) {"
"window.location = "%@://%@?" + encodeURIComponent(String(data));"
"};", RCTJSNavigationScheme, RCTJSPostMessageHost
];
[webView stringByEvaluatingJavaScriptFromString:source];
}
可以看見,window對象的postMessage對象本身被hack掉了,如果你的頁面邏輯又重寫了postMessage方法,就會有問題。
同樣的,向頁面發消息是通過webviewRef提供的postMessage方法(儘管在文檔中沒有提及),源碼實現是這樣的:
- (void)postMessage:(NSString *)message
{
NSDictionary *eventInitDict = @{
@"data": message,
};
NSString *source = [NSString
stringWithFormat:@"document.dispatchEvent(new MessageEvent("message", %@));",
RCTJSONStringify(eventInitDict, NULL)
];
[_webView stringByEvaluatingJavaScriptFromString:source];
}
如果客戶端想向發消息,你可能發現無法通信,這時你可以試試直接調用window.dispatchEvent
(今天我就遇到了,在Safari連接到真機網頁,並通過終端列印,document.dispatchEvent === window.dispatchEvent 的結果為true,但是前者無法通信,後者可以)
3. DEBUG:
詳細有很多人跟我一樣使用Webstorm進行調試,在最新版本中,你可以選擇通過Node還是Chrome進行debug:
我選擇使用Chrome調試主要是因為官方[Devtool](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=zh-CN)的原因。雖然工具很強大,但是你需要慎用,尤其是你想試試計時器是否起作用的時候:
https://github.com/facebook/react-native/issues/4470
4.動畫:
RN的動畫真的很難用,至少我是這麼認為的。在看完騰訊Alloy Team相關技術文章後,做動畫還是很彆扭,這種彆扭感超過了我在搞Mac開發時做動畫的感覺。使用LayoutAnimation可能還能好一些,但是能做的實現是在有限,更不用說原生那種轉場動畫,做跨組件之間的動畫更加蛋疼。
然而這不是關鍵,如果你如果沒有正確的使用動畫,會對你業務代碼的執行造成影響。比如說我們都知道使用InteractionManager.runAfterInteractions來跑耗時操作,然而裡面代碼的執行是依賴動畫執行情況的,已經有很多人提出類似的issue了,比如這個:
InteractionManager.runAfterInteractions doesnamp;amp;amp;amp;amp;#x27;t finished properly · Issue #7714 · facebook/react-native
一些針對動畫性能的優化上,比如你想對listview的cell的動畫做一下深度定製,一些國外的實踐經驗也是要針對平台優化(也就是寫原生的package),而不是琢磨RN的Animation:
https://medium.com/@talkol/performance-limitations-of-react-native-and-how-to-overcome-them-947630d7f440
5.手勢:
這大概是另一個RN做的比較屎的地方,由於安卓和iOS在手勢響應上有很大的差異,RN乾脆自己搞了一套,但是並不很好。比如說你想在一個View上加一個雙指觸控的手勢,大概需要一個這樣的實現:
this._panResponder = PanResponder.create({
// 要求成為響應者:
onStartShouldSetPanResponder: (evt, gestureState) =&> {
return gestureState.numberActiveTouches === 2
},
onStartShouldSetPanResponderCapture: (evt, gestureState) =&> {
return gestureState.numberActiveTouches === 2
},
onMoveShouldSetPanResponder: (evt, gestureState) =&> false,
onMoveShouldSetPanResponderCapture: (evt, gestureState) =&> false,
onPanResponderMove: (evt, gestureState) =&> {
// 最近一次的移動距離為gestureState.move{X,Y}
if (gestureState.numberActiveTouches === 2) {
this.method()
}
// 從成為響應者開始時的累計手勢移動距離為gestureState.d{x,y}
},
onPanResponderTerminationRequest: (evt, gestureState) =&> true,
onPanResponderRelease: (evt, gestureState) =&> {
// 用戶放開了所有的觸摸點,且此時視圖已經成為了響應者。
// 一般來說這意味著一個手勢操作已經成功完成。
},
onPanResponderTerminate: (evt, gestureState) =&> {
// 另一個組件已經成為了新的響應者,所以當前手勢將被取消。
},
onShouldBlockNativeResponder: (evt, gestureState) =&> {
// 返回一個布爾值,決定當前組件是否應該阻止原生組件成為JS響應者
// 默認返回true。目前暫時只支持android。
return true;
},
})
但是如果你添加的視圖如果是WebView,由於WebView本事也有一套手勢系統,導致你添加的這個不起作用,我的解決方法是乾脆自己添加一個原生手勢,然後同過DeviceEmitter通知RN,像這樣:
#import "RootViewController.h"
#import "RCTBundleURLProvider.h"
#import "RCTRootView.h"
@interface RootViewController ()&
@end
@implementation RootViewController
- (instancetype)initWithApplication: (UIApplication*)application andLaunchOptions: (NSDictionary*)launchOptions {
if (self = [super init]) {
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"mockingbot"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleTap:)];
tap.numberOfTouchesRequired = 2;
tap.delegate = self;
tap.delaysTouchesBegan = true;
[rootView addGestureRecognizer:tap];
self.view = rootView;
}
return self;
}
//需要設置於WebView自帶手勢共存
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (void)handleTap:(UITapGestureRecognizer*) tap {
[[NSNotificationCenter defaultCenter] postNotificationName:TapGesture object: nil];
}
@end
手勢Package:
#import "RootResponManager.h"
#import "RootViewController.h"
@implementation RootResponManager
{
bool hasListeners;
}
RCT_EXPORT_MODULE();
- (NSArray&
return @[TapGesture];
}
// 在添加第一個監聽函數時觸發
-(void)startObserving {
hasListeners = YES;
// Set up any upstream listeners or background tasks as necessary
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(sendTapGestureNotification:)
name:TapGesture
object:nil];
}
// Will be called when this module"s last listener is removed, or on dealloc.
-(void)stopObserving {
hasListeners = NO;
// Remove upstream listeners, stop unnecessary background tasks
[NSNotificationCenter.defaultCenter removeObserver:self];
}
-(void)sendTapGestureNotification:(NSNotification*)notification {
if (hasListeners) {
[self sendEventWithName:TapGesture body:nil];
}
}
@end
期初發現安卓和iOS在響應鏈上存在差異,最早的panResponder在安卓上是可用的,後來發現也需要原生手勢比較好:
public class MainActivity extends ReactActivity {
private GestureDetector detector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
detector = new GestureDetector(this, new GestureHandler());
}
//這裡的事件似乎是被子控制項消費掉了,看看以後能不能想辦法覆蓋掉,目前以下代碼不起任何作用
@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);
return super.onTouchEvent(event);
}
//控制觸控事件分發的時機
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getPointerCount() == 2) {
sendBroadcast();
}
return super.dispatchTouchEvent(ev);
}
private final String NORMAL_ACTION = "TapGesture";
public void sendBroadcast() {
Intent intent = new Intent(NORMAL_ACTION);
getApplicationContext().sendBroadcast(intent);
}
}
class GestureHandler extends GestureDetector.SimpleOnGestureListener {
private String getActionName(int action) {
String name = "";
switch (action) {
case MotionEvent.ACTION_DOWN: {
name = "ACTION_DOWN";
break;
}
case MotionEvent.ACTION_MOVE: {
name = "ACTION_MOVE";
break;
}
case MotionEvent.ACTION_UP: {
name = "ACTION_UP";
break;
}
default:
break;
}
return name;
}
}
總而言之,RN的手勢蛋疼無比,如果有特殊需求,請先考慮使用原生手勢。
6. Text:
要承認的是,es6的字元串模板的確很方便,Swift要等到4才有,OC用點語法糖才能做個差不多,像這樣:
NSString* str = @"AAAAA"
@"BBBBBB"
@"CCCCC";
但是一談到富文本,RN的&
TextInput組件也有問題,主要是在中文輸入法情況下,你如果輸入一些字元串沒有點擊回車,而是單純的讓Input失去焦點,候選的輸入內容不會被輸入,類似的反應有很多,像這個:
Possible bug with TextInput and Chinese input method on iOS · Issue #12599 · facebook/react-native
不過也有好消息,官方有望在0.47版本裡面修復這個Bug:
7. NPM:
1)NPM與Cocoapods、Gradle、Maven相比,似乎Bug多了那麼一些,在升級到5.X版本時,終於增加了package-lock.json,但是會導致你修改package.json來install失效,這時候請試著刪掉package-lock.json再試試。
2)RN升級也是一種痛苦,經歷過0.39 -&> 0.40升級的諸位相信一定也有類似的體會。
3)只要你依賴的項目涉及跨平台的一些特性,或者用到了node-gyp,那麼有很高几率在不同平台編譯不通過,多數情況是在Mac可以通過,在Windows上卻不行。在使用Realm、LeanCloud等SDK時都遇到過這種情況。
4)由於RN迭代速度很快,一些不經常更新的三方庫可能幹脆就跑不了了(雖然Swift的一些三方庫也有這個問題,但是Swift迭代速度沒有RN那麼喪病),我現在仍然可以看到有些公司的iOS客戶端仍然在用已經很久沒有維護的ASI,但是沒有見過有人用RN 0.2x版本時的package。
8. CI:
把一個平台的CI從寫腳本到跑通對我來說大概需要一到兩天的時間,然而你需要跑三個平台。額,應該不是乘以三倍的時間……
如果你做的是個開源的RN項目,用Travis做CI,可以看看這篇文章,然後自己試著搞一下,大概就能體會RN CI的痛苦:
React Native App CI
目前就想到這些,歡迎大家補充。
如果你們看了以後,仍然覺得解決RN的Bug很快樂(你們真的很有開源精神),可以再試試把項目完全切換到RN,否則,還是考慮一下原生+RN的方式吧。
更新於2月3日:關於我們的最新動態,我們把React跑在了其他Native的實現,讓React在無線不僅是ReactNative,就像我之前說的React更是一種模式:
------------------
------------------
對React的理解,認為React是一種架構模式,無論是內建的DOM、Native還是React Canvas都是的一種基於React模式的具體實現,當我們評價React Native還是評價React Canvas,都是React生態想像空間的一種表現。
React提出重新思考UI開發過程,其實不是面向瀏覽器,而是所有的前端,因為對前端開發而言我們需要涉及的領域已經開始包括了Web與Native。
這裡也分享淘寶基於React正在進行中的一些實踐,是我認為能戳中極客們的G點讓大家為了亢奮的事情。
React Web端
團隊里最早使用React的線上產品: 知了,前端是React+KISSY, 後端是淘寶基於Node.js解決方案Midway,這是完全由前端主導的項目,在前端,通過React極大的方便了富交互頁面的構建,同時也輕鬆的解決了頁面內業務組件狀態同步等問題。
淘寶懂我,我們在面向用戶的創新業務里毫無懸念的引入了React,不是為了技術而技術,而是基於React的開發過程,促使大家一切都是以組件的思考模式,這的確讓業務也變的更加清晰,開發效率提升,維護成本降低,發現React的確改變之前曾經非常困擾我們的事情。淘寶懂我的入口在「我的淘寶」里,大家可以去圍觀: 淘寶網 - 淘!我喜歡。
React Native端
F8大會當天,React Native終於正式開源了,這著實讓人興奮了一把,因為我們知道React Native即將成為在手機端上必不可少的開發模式之一。因為已經有React的開發經驗,稍微過目下文檔,很自然就能過渡到React Native的開發。筆者稍微努力了下,復刻了下手機淘寶的首頁,不到個把小時我這個菜鳥就差不多完成了大體的樣子,讓人驚訝於React Native這套技術方案的生產力。
而且React Native開發與Web幾乎一致的開發與調試體驗,也更讓我驚艷,這效率上差距可見一斑。
但是,Android版本還未開源,React Native只支持iOS7+平台,而在淘寶移動業務里依舊需要支持iOS6平台,所以在iOS6與Android平台上只能暫時繼續跑H5頁面,在技術上我們很快就確定將React Native代碼轉為H5版本,做到大家夢寐以求 Write once, run everywhere,就如大家在微博http://weibo.com/1797897057/CcFmN3nwp上看到的,我們就做了一個簡單的DEMO,基本確定這個方向的可行性。
興奮的同時,一個無法迴避的事實,對Web前端來說這是一個全新的領域,就是一個大坑,需要我們去填平,填平這些坑的就是我們配套的基礎設施。如圖這是淘寶基於React的已經完成或正在進行中相關領域,當這些基礎設施相對完善時,就是React Native爆發的時候,而我們現在做的事將是未來的肩膀。
結束語:Web是未來,Native是當下,而我們在未來與當下之間。
UPDATE list:
2015.9.21 React Native Android 9.15 提前發布,補充AppStore審核政策變化。
2015.5.23 Slide:上海前端技術峰會,http://yunpan.taobao.com/s/GeUkc7lAfI
2015.4.24 Slide:QCon北京2015,「http://www.stuq.org/ppt/show/95/5c28db3ecf661208387127be8878aed3#/1」
2015.4.17 天貓前端@橫天同學發表的react-native 之布局篇對css-layout的布局能力做了一些實驗,給出了一些有價值的結論,如:react 寬度基於pt為單位;flex能實現網格系統需求,且網格能夠各種嵌套無bug;padding 設置在Text元素上所有padding變成了marginBottom...
2015.4.11 風險,iOS6 javascriptCore.framework 為私有,可以通過JavaScriptCore-iOS · GitHub這個庫代替。
------
本文首發於 React Native概述:背景、規劃和風險 · GitHub
Facebook在2015.3.26 F8大會上開源了React Native(Introducing React Native),本文是對React Native的技術背景、規劃和風險的概述。看得比較倉促,問題處請直接回復。
組裡的同學於2015.4.2完成了天貓iPad客戶端「猜你喜歡」業務的React Native改造(4月中發版)。本周開始陸續放出性能/體驗、穩定性、擴展性、開發效率等評估結果。圖1 - 4.2已完成React Native改造的業務
一、背景
為什麼需要 React Native?What we really want is the user experience of the native mobile platforms, combined with the developer experience we have when building with React on the web.
摘自2015.3.26 React Native的發布稿(Introducing React Native),加粗的關鍵字傳達了React Native的設計理念:既擁有Native的用戶體驗、又保留React的開發效率。這個理念似乎迎合了業界普遍存在的痛點,開源不到1周github star破萬,目前是11000+。
圖2 - facebook/react-native · GitHub
React Native項目成員Tom Occhino發表的React Native: Bringing modern web techniques to mobile(牆外地址)詳細描述了React Native的設計理念。Occhino認為儘管Native開發成本更高,但現階段Native仍然是必須的,因為Web的用戶體驗仍無法超越Native:
1. Native的原生控制項有更好的體驗;
2. Native有更好的手勢識別;
3. Native有更合適的線程模型,儘管Web Worker可以解決一部分問題,但如圖像解碼、文本渲染仍無法多線程渲染,這影響了Web的流暢性。
圖3 - Occhino在ReactJS Conf分享了React Native(Keynote)
Learn once, write anywhere
「Learn once, write anywhere」同樣出自Occhino的文章。因為不同Native平台上的用戶體驗是不同的,React Native不強求一份原生代碼支持多個平台,所以不提「Write once, run anywhere」(Java),提出了「Learn once, write anywhere」。
圖4 - 「Learn once, write anywhere」
這張圖是筆者根據理解畫的一張示意圖,自下而上依次是:
1. React:不同平台上編寫基於React的代碼,「Learn once, write anywhere」。
2. Virtual DOM:相對Browser環境下的DOM(文檔對象模型)而言,Virtual DOM是DOM在內存中的一種輕量級表達方式(原話是lightweight representation of the document),可以通過不同的渲染引擎生成不同平台下的UI,JS和Native之間通過Bridge通信(React Native通信機制詳解 ? bang』s blog)。
3. Web/iOS/Android:已實現了Web和iOS平台,Android平台預計將於2015年10月實現(Blog | React)。
圖5 - 摘自React發布稿(2013)
1. 加亮文字顯示2013年已經在開發React Native的原型,現在也算是厚積薄發了。
2. 最近另一個比較火的項目是Flipboard/react-canvas · GitHub(詳見 @rank),渲染層使用了Web Canvas來提升交互流暢性,這和上圖第一個嘗試類似。
React本身也是個龐大的話題不再展開,詳見facebook/react Wiki · GitHub。
筆者認為「Write once, run anywhere」對提升效率仍然是必要的,並且和「Learn once, write anywhere」也沒有衝突,我們內部正在改造已有的組件庫和HybridAPI,讓其適配(補齊)React Native的組件,從而寫一份代碼可以運行在iOS和Web上,待成熟後開源出來。
二、規劃
下圖展示了業務和技術為React Native所做的改造:
圖6 - 業務和技術改造
圖6 - 業務和技術改造
自下而上:
1. React Node:React支持服務端渲染,通常用於首屏服務端渲染;典型場景是多頁列表,首屏服務端渲染翻頁客戶端渲染,避免首次請求頁面時發起2次http請求。
2. React Native基礎環境:
2.1. Framework集成:儘管React Native放出了Integration with Existing App文檔,集成到現有複雜App中仍然會遇到很多細節問題,比如集成到天貓iPad客戶端就花了組裡iOS同學2天的時間。
2.2. Networking改造:主要是重新建立session,而session通常存放於http header cookie中,React Native提供的網路IO fetch和XMLHttpRequest不支持改寫cookie。所以要不在保證安全的條件下實現fetch的擴展,要麼由native負責網路IO(已有session機制)再通過HybridAPI由JS調用,暫時選擇了後者。
2.3. 緩存/打包方案:只要有資源從伺服器端載入就避免不了這個話題,React Native也是如此,緩存用於解決資源二次訪問時的載入性能,打包解決的是資源首次訪問時的載入性能。
3. MUI是一套組件庫,目前會採用向React Native組件補齊的思路進行改造。
4. HybridAPI是阿里一組Hybrid API,此前也在多個公開場合(如感測器 @杭JS)分享過不再累述,React Native建立了自己的通信機制,看起來更高效(未驗證),改造成本不大。
5. 最快的一個業務將於4月中上線,通過最初幾個業務改造推動整體系統的改造,如果效果如預期則會啟動更大規模的業務改造。
更多詳細規劃和進展,以及性能、穩定性、擴展性的數據隨後放出。
三、風險
1. 儘管Facebook有3款App(Groups、Ads Manager、F8)使用了React Native,隨著React Native大規模應用,Appstore的政策是否有變不得而知,我們只能往前走一步。* 更新:
2015.7.28 AppStore審核政策調整:允許運行於JavascriptCore的動態載入代碼,下圖是此前的審核政策,對比加亮部分的改變。
2. React Native Android 已於2015.9.15發布,第一個使用RN開發的Android App(Facebook Adverts Manager)2015.6.29已上架Play了。
3. iOS6 javascriptCore.framework 為私有,如在iOS6上使用有拒審風險。(http://stackoverflow.com/questions/23514579/javascriptcore-framework-availability-on-ios),可以通過JavaScriptCore-iOS · GitHub這個庫代替,涉及改動較多,只在實驗階段支持了iOS6。
4. ListView 性能問題需要持續關注(ListView renders all rows? · Issue #499 · facebook/react-native · GitHub)
React Native相對於Webview和Native的優勢和劣勢 @berg 也給出了較詳細的描述,可以相互參照。
鬼道2015.4.6
先說結論:必有作為,但絕不會是一家獨大,甚至很難成為主流。
用過 React 會知道,React 的核心概念是「DOM Representation」,在開發者和 DOM 中間構建一個中間件,然後通過高效的演算法來 diff 兩次 Virtual DOM 渲染的差異,然後在最小範圍內更新 DOM,在大部分情況下——注意是大部分不是所有——這種做法都是足夠高效的,但是對於精細的需求、動畫控制等——比如在移動設備上做一個跟隨 touchmove 的元素,還要各種 transition 等等——場景 React 會顯得力不從心,或者很笨拙。
但是拋開這些太過複雜的需求,React 是有能力滿足大部分的業務場景的。
再說 React Native,這幾天不停看見媒體用「Web 開發要 XXX」一類的題目來發稿,真是吐槽無力。React Native 根本都不算 Web 開發好不好——Webview 都沒了還 Web 個 bird 啊...
React Native 繼承了 React 在 JavaScript 的擴展語法 JSX 中直接以聲明式的方式來描述 UI 結構的機制,並實現了一個 CSS 的子集,這把「DOM Representation」的概念外擴成了「UI Representation」,由於不是操作真實 UI,就可以放到非 UI 線程來進行 render——所有做客戶端 UI 開發的都應該知道 UI 線程是永遠的痛,無論你怎麼 render,render 的多低效,這些 render 都不會直接影響 UI,而要藉由 React Native 來將改變更新回 UI 線程。
由於目前沒有任何示例代碼,也看不到更細節的實現機制介紹,所以以下部分為猜測。如果 React Native 沿襲 React 的機制,就會同樣是把兩次 render 的 diff 結果算出來,然後把 diff 結果傳遞迴主線程,在最小範圍內更新 UI。
所以,核心是以下三點:
1. 在非 UI 線程渲染 UI Representation
2. 高效的 diff 演算法保證 UI update 的高效
3. 沒錯,由於中間件的機制,React 很有可能成為一個跨平台的統一 UI 解決方案,可以理解為 UI 開發的虛擬機?
聲明式 UI 開發,簡單快捷,必然大有作為。精細控制無力,複雜應用場景無法很好滿足,必然受限。
最後再說一句...不是能寫 JavaScript 就叫 Web 開發...
============================================
看了@Rix Tox 的答案後來補充。
這個答案作為補充或擴展來回答「React + Flux 模型」是非常好的,但問題是「React Native」。React Native 的亮點是解決了在 Native 中使用聲明式來開發 UI 的渲染效率問題,而不是軟體架構和工程模型的問題,無論是 iOS 還是 Android 固有的模型也是非常好的。為啥 FE 會在乎這個?因為 FE 最習慣用聲明式來開發 UI,而這麼多年想參與到 Native 開發中的目標都沒能達成,就是受制於最終的運行效率。
React 作為一個 View Component 封裝解決方案來講,同 Polymer 以及 AngularJS 中 Directive 並沒有本質區別,只是用不同的思路來封裝 View 而已,用 React 也不一定非得用 Flux 模型,React 替換 Backbone.View 組件,用純樸的 MVC 模型來描述也是 okay 的。但是當 component 很多且互相嵌套時,就需要有一個合理的模型來描述通信機制,優雅且高效,那就是 Flux 模型了。前年 React 剛發布,還沒有提出 Flux 時,我們在終端產品中開始小範圍嘗試 React 就遇到了 component 之間通信麻煩或者不合理的問題,當時的解決方案是全局實例化了一個繼承 Backbone Event 的對象作為 event hub,所有的 component 都在其上來 reg 和 trigger 事件。現在看來,就是簡化版的 Flux 模型。
不過我確實有一點遺漏的內容,React 的 DOM Representation 或者 React Native 的 UI Representation 的每個 component 都有一個 state 用來描述狀態,而 component 某種意義上來說就是一個狀態機,因此渲染結果是冪等的。
近些年 Web 前端的開發越來越多的受到工程複雜度上升導致整體性能下降的困擾,所以最近幾年的新型框架大多有一些獨特的機制來提升性能,而這些機制大多是從 Native UI 或者遊戲開發中借鑒來的,比如 AngularJS 中的數據 dirty check 機制,比如 React.js 中 Virtual DOM 的 diff 機制,這些特點同以前的前端框架或庫相比,真的是非常特殊且先進的改進,往往也會成為這個框架或庫的亮點之一,對 FE 來講當然就是新鮮玩意啦。
最後,Facebook 在 PHP 中直接寫 HTML Tag 那東西叫 XHP,對應是 JSX 擴展語法,和 React 關係也不大...一直在跟進 React.js , 中文論壇(http://react-china.org/)也主要是我在維護, 吐個槽
本人前端, Macbook 用戶, Android 用戶, 但是不會移動端的開發
首先如果 Facebook 發布 React Native, 那麼移動端開發的門檻瞬間降低, 非常好的消息
當然這個也將促使更多人選擇 React 這樣一套方案, 社區當中的資源也會更豐富
結果就是會有很多人來參與研究用 React 的範式怎樣來編程, 這是我們非常希望看到的
React 有一個觀點很明白, 就是以往的編程太多過程式的寫法, 他們想要變成聲明式的,
因為聲明式的寫法能講底層邏輯交給可靠的代碼, 那麼開發者的門檻極大地降低
發布會演講提到的就是隨著應用變大, 邏輯變複雜, 用 React 的話後來的開發者很好上手
相信這也是很多人選擇 Angular 的原因.. 雖然兩者實現聲明式編程的語法和方式差別還是不小
那麼 React 現在靠著它擺脫了 DOM 的高度的抽象, 想要把同樣的開發方式複製到本地應用!
順帶地, React 把前端的事件系統, Flexbox 布局, 打算複製到移動開發上
這個對於前端開發者來說會是很好的消息, 特別是已經用 React 寫了很多前端代碼的
對於移動端我希望是在 React Native 穩定版發布以後, 能被移動端開發者認可...
比如 Elm, 比如 React, 對界面還有數據及邏輯都做了比較理論化的考慮..
那我很期待各種圖形開發當中能引入這個我認為非常高效的方案
另外, Web 開發當中靈活的布局系統我也希望在我學習開發其他平台時能繼續使用.
用RN一兩個月了,現在準備全面切回原生。
在這裡給各位一個忠告,免得後人掉坑。RN iOS 有一個臭名昭著的 issue 499(一年前的issue),官方至今沒有解決。[ListView] renders all rows? · Issue #499 · facebook/react-native · GitHub
RN 自帶的 ListView 是沒有使用 Recycling 的,這樣就使得 RN 無法使用大數據的長列表。如果你的 App 中使用無限載入或者多個較多數據的列表。那麼你的 App 就會非常吃內存,卡死、甚至使得 App 閃退。已有的第三方解決方案都不能完美解決這個問題。
RN iOS 適合無長列表的 App,但是如果是類似新聞客戶端,數據展示類 App,千萬千萬千萬不要使用。ReactJS和ReactNative(0.46)幾個項目做下來,我和團隊的感覺是:
React.native是目前唯一靠譜有前途的移動跨平台解決方案。
搞移動跨平台,解決方案已經有過很多了。Xamarin, Cordova, 基於webView的PhoneGap, 還有一大票各種創業公司的方案。它們都很垃圾。原因很簡單:為達成「一次編寫到處運行」的目的,這些方案不得不對兩個主要平台(iOS和Android)的SDK做進一步的抽象,這意味著它們只能兼容兩個平台共有的組件,結果就是寫出來的app只能做到最平庸的用戶體驗。特別是微軟的Xamarin,連自家的Windows Phone都搞不好,還給Apple和Google的SDK做包裝,那能好么?基於Web的方案就更不用說了,本質就是拿HTML套個殼外加一些原生寫的插件。
React.native的高明之處在於:它並不追求一次編寫到處運行,它放棄了全部代碼跨平台這一不切實際的目標。RN的目標很實際:用同一門語言(Javascript),同樣的高層架構(Virtual DOM)和設計模式(component-based),針對不同平台分別作出最佳的用戶體驗。這也就是RN中「native」一詞的含義。
在實際開發中,要做到最佳用戶體驗,針對iOS和Android應該要分別編寫UI代碼的。實際上RN也鼓勵這麼做。Android是Android,iOS是iOS,web是web,三者有不同的界面語言和用戶習慣,憑什麼要一樣呢?但除卻UI,業務邏輯、data object、web call等等卻是可以一樣的。再加上採用了同一門語言和設計模式,RN在生產力上非常有競爭力。從另一方面看,Flux設計模式反過來也被原生開發社區接受,Redux庫在Java和Swift上都有翻版原生實現,所以你不一定要用RN寫app,但你還是可以借鑒採用React的設計模式。React項目對於整個開發社區的影響很正面,比PhoneGap這種催生了一大票廉價app碼農的垃圾技術正面多了。
另外,純Javascript的開源庫也可以直接應用到ReactJS/ReactNative中,這也進一步提升了生產力。
這是我做完 tutorial, 觀察源代碼後的評價:
很適合用來布局, 用 flex-box 拯救約束布局! -- 不過實現還有一些小問題, 有些元素不是那麼容易改大小和位置的, 內部的 Cassowary 約束不滿足怎麼辦? 或者它凸出來把 flex-box 破壞掉, 或者把它內部的布局破壞掉來滿足 flex-box, 兩個布局系統打架很心累...
它和 HTML/CSS 完全沒關係, 元素比 HTML 簡單很多, 能用的 style 都定義在 view class 中, 現在 commit 還很少, 支持的 style 種類非常少, 能力非常弱, 而且和 CSS style 不是完全對應的 (主要的 style 聲明見 RCTShadowView.h) 現在問題來了: 是不是可以把有複雜 style 需求的組件放到 WebView 中去? 可惜的是, 可以新建 WebView component 但出來的完全是個黑箱, 現在還沒法無縫的把 react-native 的 virtual dom 接到 WebView 內.
它的 JS 引擎是用 WebView 自帶的, 不能 JIT, 比瀏覽器里的 JS 性能差很多. 光用來布局或者做簡單應用很舒心, 做複雜應用將會是耗電吃內存大戶... 瓶頸部分改用 native 組件可以改善一下, 畢竟 Swift 寫組件, JS 調用 so easy. (3月30日更正: 其實用的是 JavascriptCore.framework, 而且現在 Nitro engine 可以 JIT 了, 不過每次通信都 eval 的那小段代碼是沒有 JIT 的, 有些微影響但不嚴重)
以後 Android 版發布後, 很可能會顛覆 Android 開發首選語言.
對社會的危害: 一大波全端程序員又給自己的技能標籤加上了 iOS, Android用了快一年, 覺得坑非常多,我就問這麼幾個問題:
1. 動畫怎麼做. 別說多複雜的東西, iOS原生UITableview的編輯狀態那點動畫, 你給我實現一下;
2. 安卓適配. 我是iOS開發出身, 恕在下能力有限, 還沒找到合適的適配安卓的方案;
3. 畢竟要先創建原生view,才有RCTRootView, 畢竟有個非同步渲染過程, 我就問問你如何做到真實原生體驗;
4. Listview的內存問題. (SGListView有折中方案, 我知道)
RN是在另一個角度解決了一些問題, 用來做做活動頁是可以, 拿來做整個APP?
求大神回答一下上述四個問題
從前端過來, 門檻低一些, 畢竟RN是用JS寫;
然而從原生過來, 要學習JS(ES5,ES6), React 和 React Native, 個人認為學習曲線還是比較陡峭.
當然, 即使是從前端過來, 要做一些複雜的東西, 還是需要深入理解原生.
RN不管原生和JS代碼, 都有一些不適合自己的(bug或自己想要的修改).
要做複雜點的APP, 用RN, 在相當長的一段初期, 都無法節約開發時間.
而且RN在安卓上的性能的確不怎樣.
且RN因為CSS樣式的布局, 雖然寫的方便了, 但是view的層級明顯多了, RN本身以及增加的view層級也帶來了內存的消耗
湊個熱鬧,正好最近在玩這方面的技術,來個技術貼。
註:在沒有同意的情況下,禁止轉載。
WebView 里無法獲得的能力雖然是「體驗增強」與「端基本能力」,但現都基本上有成熟解決方法。但後期的 UI 和 Layout 的性能反而是目前 Web 技術欠缺的。所以,無論是 Titanium 與 React Native 都是解決性能作為探索的出發點。
一、性能慢與快的分水領.
慢與快的標準,是按照每秒大於等於 60 FPS(60 幀每秒) 的理論,而為什麼是 60 FPS,這不多描述。
按此理論,那麼「每幀」里所有的操作都必須在 16ms 完成。
二、WebView 里 UI 性能慢的原因.
- WebView 單線程模型;
- DOM/CSS 排版複雜,在渲染上需要大量計算;
- 動畫是也很重要的考量因素。
多說兩句動畫。
最早做動畫都是用 setTimeout/setInterval。
而 setTimeout/setInterval 的處理回調的時間 tick time 精度都在 16ms 左右。
所以,可以想像正常用這兩個函數就已經 16 ms了,再加 reflow/repaint/compositing 卡頓或跳幀就是家常便飯了。
還好的是 w3c 標準和各瀏覽器廠商較早就支持了動畫介面 RAF(RequestAnimationFrame 函數)來處理動畫幀回調。解決了上述 setTimeout/setInterval Animation 不足的問題。
三、DOM 性能低下的原因.
瀏覽器執行的幾個步驟:
restyle/reflow/repaint 觸發條件:
了解完以上信息,考慮以下條件:
- 把 JavaScript 邏輯、複雜的 DOM 與樣式合成,並完成渲染;
- HTTP 請求下載多媒體;
- 在一個線程里;
- 移動上的 ARM 架構;
以上操作能在每幀 16ms 里完成,想想都覺得是一件 TMD「不可思議」的事情。
於是各種各樣的奇葩優化出現了。
四、WebView 里高性能組件分類.
已知的高性能組件的幾類方法:
1)常規方法.
這類的原理主要是利用人為或規範的方式,減少 restyle/reflow/repaint 觸發次數:
- 通用組件優化 DOM 結構,甚至用 Virtual DOM(虛擬 DOM)減少 reflow 和 DOM 的複雜度;
- 優化 CSS,少用或跳過 repaint 階段。用編譯的手段識別部分 CSS,將 left/top 變換變成 transform;
跳過 layout 與 paint 階段,就是多使用 Layer composite 技術,即 css 的「opacity」和「transform」屬性作動畫。
只能在 css 和 DOM 結構上去摳出些性能優化的空間,缺陷優化空間有限;這種優化技巧通常是放在最後調優時沖剌使用,不能做為常規手段。
2)進階方法.
原理是儘可能利用 native 能力,甚至將 JavaScript 轉換成 native App 代碼。
- 用 JavaScript 調起 native 組件,將增強與高性能組件交給 native 來處理,以前在 FEX 提的「輕組件」就是這麼做的。這個原理類似 PC 時代的 ActiveX;
- 將 WebView 里無法實現的功能用 native 實現。
- 利用 native Activity 的渲染線程,分擔瀏覽器渲染壓力(WebViewCoreThread 是 WebView 線程)。
- 最 dirty 的事在於處理 native 上 UI 的層次管理。
- 需要後台有線程一直在檢測 scroll/resize/ui change 時 UI 邊界是否有相互覆蓋與疊加的問題。
- JavaScript 翻譯成 Java/OC 代碼。類似 React Native/Titanium,將 JavaScript 翻譯成 native 代碼,特別是 UI 組件上。(有興趣的同學可以反編譯 React Native 寫的 Facebook Group)
- 例:用 React,通過虛擬 Web UI 映射至 Native View,並且將代碼邏輯翻譯成 native。
3)新方法 — Canvas UI.
這也是要說的重點,用「開發遊戲」的思路來做 UI 組件探索,我把它稱為 Canvas UI framework。
五、Canvas UI framework.
用遊戲的思路做 UI,最早我有這個想法是 2014 年。
1)為什麼要用 Canvas?
Canvas 是 H5 的畫布元素,即一個 DOM 元素。通過腳本控制邏輯給畫布上增加文字與圖像,而瀏覽器只需要繪製一次形成一幅圖。
- 只用一個 Canvas DOM 元素,降低 DOM 數量與渲染的複雜度,可以將原來 CPU 密集型變成 GPU 操作。
- 絕大多數針對 Canvas 是用硬體 GPU 加速渲染。
- GPU 的 ALU(計算單元) 比 CPU 要多很多;
- 而控制運算(邏輯)則可以用 JavaScript 在 CPU 里做,甚至還可以用 WebWorker 多線程處理 CPU 密集型的操作;
- 從而達到充分利用硬體資源的能力。
- Canvas 畫布無論是 JavaScript H5,還是 native 都有類似的 API。所以:
- 本地調試可在瀏覽器里完成。
- 最差方案可以用 Canvas UI 跑在瀏覽器里。
- 更進一步,可以把瀏覽器 Canvas 介面的反射到用 native 畫布上,以此提高性能。
值得一提的是,騰迅的 X5 內核已經將 egret(白鷺遊戲引擎)與cocos2dx內置,
所以時間線拉長來看,WebView 的畫布功能將會更加強大。
在 2014 年中時,很多人見識過默認置入 cocos2dx 引擎的瀏覽器,用 WebView 玩「捕魚達人」很流暢。
由此說明 Canvas 做 UI 組件可行性還是蠻高的。
2)解決方案.
用遊戲的思路來解決 DOM paint 的問題,業界早就有實驗。
最早實驗的是 zynga 做 angry birds 遊戲的廠家,2010 年寫的 demo scroller:
- zynga/scroller · GitHub
- Scroller - Canvas
http://zynga.github.io/scroller/demo/canvas.html (二維碼自動識別)
設計、開發一個基於 Canvas 的 UI 框架系統,由於系統相對比較複雜,需接管瀏覽器構建的整個過程:
驗證在實踐環境中的效果,要把原來頁面的 DOM 寫成 canvas,再加上一些調優與比較,工作量相對大,(包括 zynga 也只是實現了一個簡單的 demo 而已)
就暫時擱置了。
最近這陣子在翻 github 與新聞時,我驚喜的發現也有人在做同樣的事了,最後發現 Flipboard 同學們寫的一個 demo:Flipboard
https://flipboard.com/@flipboard/flipboard-picks-8a1uu7ngz (二維碼自動識別)
這個 demo 足夠複雜,動畫也足夠多、炫。是用 canvas 來構建整個 UI。測試過後:
- 這麼複雜的 demo 在 MI4 以及配置以上性能很好,流暢度無限接近於 native,比較理想。
- 對比過 G+ 的 Android 應用,G+ 的 App 從動畫上比 Flipboard 提供的的 demo 還「卡」些。
- 在小米 Note 上的動畫流暢度已秒掉 iPhone 6,非常贊。
按照摩爾定律,可以預計明年 Note 的標配的 CPU 和 GPU 配置會成為主流。
而現在用 canvas UI 框架用在 MI4 以下機器仍然比較慢。而 2015 年 H5 開發 App,對很多公司來說只是 plan B 計劃,大公司甚至 plan B 都不是。所以,如果一定要在純 H5 上搞牛逼動畫,還是再等等吧。
3)布局系統 css layout.
說回到 Canvas Component framework,回到我上面畫的這張圖:
UI 組件基於「文本」與「圖像」。但 framework,除了 UI 組件本身以外,還需要有 Layout,而 css 只適用於瀏覽器本身的 layout 而無法適用於 Canvas 畫布。
要給開發者好且排版可控的方案,那就要開發一個用 JavaScript 實現類似 CSS 的布局子集的框架。
否則 UI 的組件在畫布上沒有 layout 就無意義。
這個布局框架實現成本(簡單實現)理論上並不大,大的是在於未來增加新 Feature 並相互組合時與瀏覽器本身有差異,需要有完整的 unit test。
正好最近 facebook 也開源了一個用 JavaScript 寫 css layout 子集的解決方案,實現了:
- padding
- width
- margin
- border
- flex
- position( relative/absolute )
等等。
github 地址:facebook/css-layout · GitHub
這些 css 布局子集基本能滿足我們前期開發預期。
4)開發框架.
用 css-layout 再加上 UI 管理層,就可以比較清晰的實現出 canvas 的 UI 組件框架了。
那麼,剩下的事就是:
- 應用開發框架的選擇,如:選擇 React/MVC 框架。
- 模擬 DOM 層次,即圖層管理。
並且,讓我非常欣喜的是,Flipboard 在 2 月已經完成了構建,基於 React 框架。
https://github.com/Flipboard/react-canvas
基於 css-layout + React 基礎上整合而成。
六、Canvas UI 框架不足與風險.
看上去 Canvas 框架這麼牛逼,但有很多缺陷。
- 對開發人員的要求較高。需要用 JavaScript 實現一些瀏覽器基本的布局、圖層管理。
- 第三方使用者學習成本高。不象是用傳統「標籤」就可以實現 UI 在瀏覽器的輸出。
- 開發者是否買賬,對於框架的開發易用性有「很大」的挑戰。
- 對開發質量提出新要求。
- 由於所有的 UI 組件與交互都在畫布 Canvas 里,所以調試成本比較高,需要有較為完整的 Logging 與 Debugging 方案。
- 用戶可用性會受影響。例如:
- 語音無法識別 Canvas 里的文字。
- 無法象 WebView 里一樣將畫布里的文字選中並複製出來。
總結.
Canvas UI 作為柳暗花明又一村的技術產品,https://github.com/Flipboard/react-canvas 短短一周多,已經近 4K 的 star。確實很贊。
現在看 FB 開源 react native,不如好好研究 react-canvas 。
原文來自:http://mp.weixin.qq.com/s?__biz=MzA5NDY0ODkxNA==mid=202786512idx=1sn=c3b470b68a6d73953d38c309ea2f5f4b#rd
React Native跨平台移動應用開發實戰
React Native介紹
React Native (簡稱RN)是Facebook於2015年4月開源的跨平台移動應用開發框架,是Facebook早先開源的UI框架 React 在原生移動應用平台的衍生產物,目前支持iOS和安卓兩大平台。RN使用Javascript語言,類似於HTML的JSX,以及CSS來開發移動應用,因此熟悉Web前端開發的技術人員只需很少的學習就可以進入移動應用開發領域。
RN運行時包含一個原生主線程和一個JS線程,JS線程執行JS代碼,負責界面布局和業務邏輯處理,原生線程負責界面渲染和原生功能執行。RN盡量使用原生組件(現有原生組件經過封裝後可在JS里調用),以避免重複造輪子。這樣有很多好處,一是可以利用現有的大量的原生組件;二是可以達到跟原生組件一樣的性能;三是通過JS封裝過後的組件可以支持跨平台。
JS在RN裡面的作用類似於Python這樣的支持調用原生C庫的腳本語言,都是起著「膠水」的作用。複雜計算和系統功能通過調用原生介面來完成,流程式控制制和業務邏輯則在「膠水」語言里完成。這樣既提高了開發效率,又兼顧了性能。
起源本文章系列是 Go + Docker API服務開發和部署 的姊妹篇,都是筆者在開發 在球場 這款體育社交類應用過程中的技術總結。該應用已經發布到AppStore、GooglePlay和騰訊應用寶,覆蓋iOS和安卓兩大平台。平台之間的代碼復用率超過90%,流暢度接近原生應用。相比而言iOS的體驗更為流暢一些,畢竟RN最先支持的就是iOS平台,優化做得更好。
經過這次的實際開發體驗,筆者相信RN以後將成為絕大多數移動應用開發的首選技術。除開那些對性能要求非常苛刻的應用,比如遊戲類。RN目前的短板在於代碼的穩定性不夠,經JS封裝的原生組件還比較少。不過這些隨著時間的發展將會很快解決,畢竟RN從開源到現在也才一年多時間就達到了目前的水平,發展速度非常之快。
內容目錄Lite版的代碼已在GitHub開源,在球場Lite版 。
- 開篇
- 搭建開發和調試環境
- 技術棧
- 布局和導航
- 啟動
- 數據同步
- 登錄註冊
- 首頁
- 打包發布
其它資料
- 視頻課程 - React Native跨平台移動應用開發
- 源代碼 - 在球場Lite版
- 技術文章 - Go + Docker API服務開發和部署
- 在球場官網
關注RN很久了,前面回答裡面有很多很好的分享,目前的項目完全採用RN做的,已經基本成型了,所以來分享一下相關的經驗。
首先技術選型,我們並不是因為RN很NB,很xx之類所以才嘗鮮試的去選擇它,而是我們覺得本身需要一種技術來做跨平台的開發,減少重複的工作量,這樣才能作出更好的APP。之前在View層和網路層我們通過同一份文件(JSON,XML,etc)可以映射成iOS和android的原生代碼,在尋求更簡單的邏輯層表達的過程中,RN進入了我們的考慮範圍。
在我看到f8App的例子前,我是懷疑Facebook開源的動機的,但是官方提供的f8App這個例子打消了我的疑慮,在編寫了原生模塊和原生組件的測試demo後,我們就決定了用RN來進行開發新的項目。
項目的最開始,投入了我一個人進行了兩周的開發,出了第一個iOS版本,然後用了兩天時間適配出了第一個android版本.在此之前我沒有js的開發經驗,在兩周的過程中我還需要不斷查閱js的語法。第一個版本裡面沒有新增任何的原生組件和原生模塊,會遇到一些坑但是麻煩並不大。在這個過程中,我們採用了redux,這讓程序狀態的管理變得非常的輕鬆。
接下來的二個月的開發時間中的一些任務是開發了三個原生模塊以及一個原生組件。開發完這些後,我已經可以很熟練的把原生的代碼封裝後交給js層去處理了。
最後,項目又投入了一位側重於android的工程師,這個時候前提的投入開始獲得收益了。這時我們有一項緊急的開發需求,是完成一項智能設備的接入,在包括藍牙處理在內以及相關的功能總共有七個模塊,通過分工,我和另一位工程師只需要一人完成50%,而不是普通原生開發的100%。js的強大的表達能力已經可以彌補RN本身兩端適配帶來的時間損失了。並且,如果是一個比較長時間的迭代周期,通過合適的策略進行iOS以及android版本的測試工作,測試的工作量也可以至少減少百分之40。這些節省下來的時間,可以投入在更好的方案設計以及組件優化上,從而讓我們app在技術上領先於競爭對手。
我們團隊採用RN可能是一個比較成功的例子,因為我們在引入之前就清楚我們需要利用它的哪些優勢。在RN中,js強大的表達能力會提高開發效率,布局方案領先於原生的方案,最重要的優勢是iOS和android的公共代碼超過了百分之90,而RN的組件化開發會讓本身的代碼復用率也非常的高。
前面的回答也有轉向RN失敗然後退回原生的例子,在推進技術的時候,肯定會遇到讓人退縮的阻力的,因團隊而異吧,整體來說RN的社區還是非常活躍的,絕大部分有難度的問題都能找到答案。
RN不太適合不熟悉移動端開發的工程師入手,比較適合熟練的iOS/android工程師進行前期的推進。因為js只是構建新的表達方式,對原生SDK和移動端功能的掌握才是硬性的。官方的一句描述"原生能實現的,RN都能實現",讓很多前端工程師會認為這個是很容易的,其實這個才是RN的技術門檻,精通RN的原理本身並不能讓你做到在RN中實現原生能實現的所有功能。
最後,我們僅僅只是使用RN來更好的滿足工程上的一些需求而已。如果無法對團隊自身定製的跨平台方案進行持續投入的話,我覺得小團隊在RN的基礎上做手腳架或者新的表達方式是非常好的選擇,可以非常完美的利用了社區的成果和自身的定製化開發。
現在RN的版本已經比較穩定了,我覺得很多團隊都在默默的用著RN在偷著樂了,拉鉤上RN的薪資和崗位相比之前已經有了提高了,之前挺多3-6K的, 這些錯誤的薪資看法會讓我覺得團隊當前採用RN的正確性,因為競爭對手裡面對RN的錯誤看法的人越多,我們的產品越有一點點的機會。如果從公司的角度出發,我還是覺得用RN的團隊越少越好吧!!!
Why React Native Matters · joshaber 這篇文章的觀點比較認同,使用 Javascript 只是一個實現細節:
React lets us write our UIs as a pure function of their state.
This is a big, important idea.
Right now we write UIs by poking at them, manually mutating their properties when something changes, adding and removing views, etc. This is fragile and error-prone. Some tools exist to lessen the pain, but they can only go so far. UIs are big, messy, mutable, stateful bags of sadness.
React let us describe our entire UI for a given state, and then it does the hard work of figuring out what needs to change. It abstracts all the fragile, error-prone code out away from us. We describe what we want, React figures out how to accomplish it.
UIs become composable, immutable, stateless value types. React Native is fantastic news.
實際上 Facebook 在之前也用 Objective-C++ 開發了一套類似現在 React Native 的框架 Components 用於 Facebook for iOS,在 The Functional Programming Concepts in Facebook"s Mobile Apps 這個演講很好地解釋了這種模型的優勢。
哈哈,大家都不看好它,我倒是覺得它非常不錯。
先說一下傳統的 hybrid app 的一些優劣勢:- 優勢:js 可以直接被 native 端執行,也可以和 native code 進行通訊,進而可以調用一些 native 提供的介面。 能直接被執行的好處在於可以直接從伺服器上載入並執行 js 代碼,這點在 iOS 上是 native code 和其他 to objc 的語言都難以做到的。這使我們有了一些更靈活的方法,來完成諸如應用內更新或者開發應用內插件之類的工作。同時,iOS 和 Android 可以共享一些前端部分的代碼,使得代碼能夠更好地重用。
- 劣勢:界面渲染效率低,多線程支持差,GC 問題。在 WebView 中繪製界面、實現動畫的效率都比較低,開銷也比較大。WebWorker 提供的多線程在 native 端有很大的局限,js 在 GC 時也有可能卡 UI。
React Native 的做法非常激進,完全拋棄了 HTML(拋棄了 HTML 不代表拋棄了聲明式),拋棄了 WebView,在 background thread 里運行 js 並直接使用原生控制項進行渲染。
這從根本上解決了渲染問題,使得 js 不再只能做 hybrid app,而能做出具有 native behavior 的流暢靠譜的 native app。從這一點上來說 React Native 已經做得相當不錯了,儘管它只實現了 CSS 的子集,但是考慮到 CSS 如此複雜而它又拋棄了使用 webview 渲染,這是可以接受的。
===========
但是,React 的意義絕不在於解決了一些 hybrid app 的痛點。
React 是一個很有野心的項目,它的目標不僅僅是簡單地使前端能用 js 寫 native app,而是希望推廣一個通用的前端構建方案,不論是 Web 前端,還是客戶端前端。
FB 在演講里說,React 的目標不是 「Write once, run anywhere",因為不同平台的差異是客觀存在的,設計風格也各有不同。它要做的,是 「Learn once, write anywhere」。React 里的 view 不僅可以是 DOM,也可以是 iOS 控制項或者 Android 控制項,不論是什麼平台,都能 「build in React」。
就我看來,前端不只是需要寫網頁,更重要的是要解決屬於前端領域的各種問題。Web 前端和客戶端前端在本質上是一致的,探索前端領域的最佳實踐是一件很有意義的事。
近些年前端比較火,相比於自成體系較為封閉的客戶端前端, Web 前端在前端實踐上的探索或許更多一些。不管是阿里的 Midway 那樣侵入到後端的 UI layer,還是像 React Native 這樣侵入到客戶端前端,我認為都是非常值得稱讚的探索。
至於到底怎麼評價 React Native,我覺得它和 React 一樣贊。
===========最後附個 FB 關於 React Native 的演講:https://www.youtube.com/watch?v=KVZ-P-ZI6W4
今年年初公司開始正式在業務上推廣React Native,前幾天的技術分享會上也給公司的研發團隊做了關於React Native實現原理的分享,總體感覺坑還是有的,尤其是在與業務團隊的對接上,但是好處也是明顯的,跨端開發,快速發布。
這裡貼一下React Native技術分享的文章與PPT,希望對大家理解原理有幫助O(∩_∩)O~
React Native實現原理分析文章
guoxiaoxing/react-native
註:好吧,好像知乎手機端識別不了上面這個github鏈接,ppt末尾給的有文章地址,也可以直接去github上搜索guoxiaoxing/react-native。
1ReactNative源碼篇:源碼初識
2ReactNative源碼篇:代碼調用
3ReactNative源碼篇:啟動流程
4ReactNative源碼篇:渲染原理
5ReactNative源碼篇:線程模型
6ReactNative源碼篇:通信機制
React Native實現原理分析PPT
======= React Native 正式發布 =======
官網:React Native
沒有參會的我只能等這一天了。雖然慢了點,但還是講講第一體驗吧。
首先一點,React Native真的是完完整整地將React.js的特性搬到了iOS平台上。所以我最開始說的關於數據流的控制以及Diff Update這些作用在React Native也能得到很好的體現,所以我前面說的竟然沒有跑題。
不過,儘管都是用JavaScript,但是用React做iOS開發跟做網頁開發是完全兩種體驗。在React Native里你見不到&,但是會經常用到&
React Native將Flexbox排版移植到了iOS上,也就是說開發者可以不再忍受Constraint-based的Layout Engine,而轉而使用更直觀、更像CSS的Boxed Layout。
JS VM跟iOS的渲染是處於兩個線程的,也就是說JS代碼控制邏輯和Viewer,iOS Framework對Viewer進行渲染。從效率上來看由於JS跟O-C處在兩個線程,不會出現因使用VM而帶來的卡頓,相反App的使用體驗十分流暢。對此不太確信的可以嘗試一下Facebook Groups,你會感覺跟用Native App的體驗沒有什麼區別。在開發的過程中JS可以脫離VM來跑,並通過JSON等的序列化來進行通訊,在React Con上的演示就是這樣,他們可以直接用Chrome Developer Tools對App的JS代碼進行調試。
可以看出我先前對React Native的預測基本都說中了,它實現了去WebView化,用iOS原生組件作為Viewer的Render Engine,JS代碼雖然沒有編譯過但是執行效率還不錯。從開發體驗上來看,React Native提供了一套非常完整的開發Workflow,基本上你只要一直跑著iOS Simulator,更新代碼後在Simulator中Cmd-R一下就能刷新App,基本不需要進行編譯。我在想,這樣一來其實一定程度上還能做到App內部的自主更新不是嗎?只要把最新的JS代碼拉下來就能放到VM裡面跑了。就是不知道App Store會不會允許這樣的更新機制。
App的Test也可以完全用JS來寫,而且理論上來說Test的Context與Render環境無關,可以脫離iOS直接在本地的Node.js上跑,這對於程序的調試來說也是一種便利。開發者可以在非Mac系統下進行Test-driven的開發而不需要去考慮iOS的具體環境。
當然,從文檔的各種留白我們可以看出React Native現在還處於非常初期的摸索階段,還有很多iOS Framework下的東西沒有很好地整合進去。但是FB已經向我們Proved the Concept,並且消除了人們對效率和兼容性的擔憂。我覺得在現今的基礎上做更深一步的融合是相當迅速的,我相信在接下來的幾個月中我們將看到新一波的Paradigm Shift出現在移動App開發領域。
========== 更新 =========
看到 @rank 把react-canvas都扔出來了我不得不來嘮兩句了。我也是這個星期才在HN上看到的這個項目,實際上這個項目一周前才發布出來。不得不說這的確是一個很贊的實現,我也見過一些用canvas寫的網頁,對移動端的適應做得非常好,比如這個Legend就設計得很酷炫。
但是,從我目前了解到的信息來看,react-canvas在製作規模化移動端應用方面並不能趕超React Native的勢頭,原因如下:
- React Native的目的在於讓原本用WebView做的App去掉DOM渲染層,用更加原生的界面元素和渲染引擎提升性能。但是如果用react-native的話則仍舊沒有脫離WebView,兩者的運行時體積可見一斑。而且這加多一層東西必然對性能有所影響,比如你用Native的方式做Transition Animation,在動畫的實現方面不需要由你的代碼控制,可以直接使用渲染引擎的自帶動畫,就算有些自定義動畫也不必再JavaScript上面運行,因為React Native已經將JS編譯成了Native的二進位來執行了,所以又減去一層JS虛擬機的效率損耗。再看react-native,你要跑canvas就不得不用WebView,不得不使用原本的JS引擎去驅動整個界面,你不得不將所有的控制項、Layout、Animation全部在JS層面實現一遍,並全權交由JS來控制,這個計算開銷不是一般的大。你如果僅僅為了保留WebView、JavaScript的情況下使用canvas硬體加速而去增加這麼多中間層,實在不是一種理想的趨勢。
- 由於內容需要在canvas上渲染,那麼有可能會遇到一些寫遊戲的人喜聞樂見的問題,比如CJK字體渲染。還有很多Accessibility方面的硬傷,比如canvas裡面的文字沒法選中、複製或者使用系統的Define功能查詞,也無法兼容讀屏程序。在Flipboard的例子中他們的解決這些問題的方法實在是太Dirty了,他們用回了DOM元素,用DOM渲染文字,然後懸浮在canvas上面,這樣用戶就能對文字進行Native的操作。但是這樣的解決方法根本就是在倒退回DOM的開發模式啊好嗎,為了達到上面的效果,他們還得在後台代碼構造一個Virtual DOM Tree,實時跟canvas的內容同步,然後更新DOM的內容和style。這樣的做法簡直無法理喻,如果說canvas能靠硬體加速提升性能,你把DOM跟canvas混用那這個效率還能提升多少?再說如果用DOM畫的部分要與canvas上的內容作更複雜的互動,用DOM是根本沒法渲染的。引用react-canvas作者的話來說就是:「pushing the browser beyond its limits that we can make progress in this area.」所以這個項目到底有多Dirty作者本人也是知道的。
相比之下,React Native致力於去掉更多的中間層,只保留一個Flux+React的中心概念,這才是一個合理的發展趨勢。我見到有人說React Native跟react-canvas兩者不衝突,可以用canvas做React Native的渲染引擎。說這話的人還是沒有認識到React Native的目的究竟在哪。我說了React Native在於去掉中間層,你又要用回canvas再加上一個WebView和一堆JavaScript,那React Native的意義何在?就好比喬布斯千方百計把iPhone的厚度減少了一毫米,你帶個套又給人加回去了一樣。
======== 原答案 ========
看了所有的答案,都沒有講清楚React的實質作用在哪。
我上周用純React完成了一個CMS發布系統的界面框架,數據層僅僅用了Parse的JS庫,但是功能還算完備。中間讀了不少這方面的資料,對Flux + React有了一個較為系統的了解。這裡大致總結一下給還沒開始動手寫React的人一點啟發。
要講React的實質作用那一定不能脫離Flux模型。一說到Flux,很多人的第一反應就是那個經常見到的、看起來很複雜的Loop流程圖。我不想一開始就貼那張圖,因為我默認你們都沒真正用過React,那張圖只能讓你們更加不明覺厲。其實了解Flux + React最好的方法就是看看官網給的這個視頻,我就把重點摘出來講講吧。
首先,Flux是為了解決MVC模型中數據流向不一致的問題的。視頻中給出了一個非常典型的案例,可以說我以前也被類似的問題折騰了好久。比方說你從服務端拉下來的數據要改變兩個View的內容,比如給未讀消息的數字提醒+1,然後把消息放到Chat View中,如果用戶正在看當前的Chat View就把未讀消息數字-1。這一連串的操作在沒有用React的時候要怎麼寫?她給出的代碼也是我以前處理類似狀況的方法:我以前也不是沒想過寫一個MessageManager之類的,類似Flux的Dispatcher一樣的東西去控制這些邏輯然後分別對不同的View進行改動,但是好好想想這不過是一個簡單的信息接收而已啊,為啥要複雜到專門給一個數字寫一個Dispatcher去做這種事情?所以最後想想還是算了,代碼丑點就丑點,能用就行,最後我也寫成上面那樣了。但是你要知道,這樣的代碼寫在非同步程序里是非常危險的!如果用戶同時在操作,那你的代碼很可能就會被中斷報錯的。還有,如果後期需要加功能,比如在屏幕側邊再搞個信息框,那你怎麼知道在哪去加代碼?你程序的數據交換會像這樣混亂:
怎麼樣?寫非同步MVC的童鞋有沒有感同身受?臉書以前的Chat就是經常因為非同步數據的問題被人黑。比如經常是右上角一個紅色的1,點進去一看啥都沒有,強迫症的怒火你可曉得?
那麼問題來了:如何讓非同步數據像同步數據一樣正確地顯示出來?臉書的攻城獅想到一個絕妙的招式:咱別一個DOM一個DOM的去更新內容了,來一次數據俺們就「刷新」頁面!是的,就是回到了90年代的那種模式:你想看新郵件?請刷新頁面!然後服務端把你當前的郵件全部讀一遍,排好序,生成好HTML,然後給你寄過來,你就能看到新郵件啦。Flux就是這麼乾的,只不過現在已經是21世紀了,渲染HTML這種工作不需要服務端也能做,我們也不用真的去刷新整個網頁,雖說Chat的那個問題的確可以靠刷新頁面解決掉lol。Flux的(大致)做法是:來新的數據了,好,我把它Merge到客戶端的舊數據中,然後把客戶端存著的所有數據交給一個Render,然後把整個HTML從頭到尾渲染一邊,最後把新的HTML替換掉現有的HTML就好啦!那麼之前那個Chat的代碼咱就可以這樣寫(我YY的,視頻沒有,也不是React代碼)function newMassageHandler(newMessage) {
this.messages.push(newMessage);
this.renderTheWholePage(); // In practice it should be an Action Creator
}
看到沒有?管你有多少個View,你們自個兒去算怎麼渲染吧,handler只管存數據跟提醒渲染。如果哪個View計算完了說,不對,那個Unread Message Number要-1,好,你跟Action Creator說一聲,他會在下一輪渲染前把這個項目加到數據中的。如此一來就不會出現把改動View的代碼寫到某個Controller中,然後一個Controller又要同時關聯好幾個View的情況,這樣一來整個程序的結構就變得非常清晰了。這時候再看這張圖就容易理解多了:
數據的流向變得非常統一,如果View要對數據做些改動也得等整個View渲染完了在下一輪渲染的時候再改,從而形成一個非常完整的Loop,這對非同步數據的處理是非常有幫助的。
Flux是一個取代MVC的設計模式,而React就是臉書寫的給Flux用的專門做Viewer的框架。由於Flux要將原有的HTML從頭到尾渲染一邊,那麼就不得不考慮一個平滑過渡的問題,因為畢竟我們要講究用戶體驗,不能每次收到數據就真的把原網頁給刪了然後重新給用戶畫一個,那用戶還不崩潰啊?React解決這個問題的辦法就是用Virtual DOM,這才是React真正的核心價值。其原理是寫看起來像是DOM的JS代碼,然後生成ReactDOMElement的對象,而不是真正的DOM,然後把新的Virtual DOM Tree跟用戶正在看的DOM Tree做個Diff,然後用最小的改動量Update整個網頁。對比一下jQuery大概就是這樣:// jQuery
$("&
// React without JSX
React.creatElement("button", {onClick: submitHandler}, "Submit");
// &
// React with JSX
&
可以看到,其實JSX只是React的一個語法糖,這只不過是一門DSL。有些人一看到JSX就說把HTML寫JS里了,我真是不知道該怎麼評價這種人。ReactDOMButton並不是真的DOM,在運行時只是一個普通的JS Object,我寫成XML形式只是易讀。不用真的DOM當然是因為效率問題,這也是一種優化手段,但是Virtual DOM的好處當然不止這樣,後面會講。
你要是不喜歡JavaScript也沒事,我就不喜歡。知道GitHub的Atom嗎?就是用CoffeeScript + React寫的。它並不是用改過語法的那個看起來就很噁心的coffee-react項目,而是自己寫了一個很簡單的helper來做簡單的Transform。大致的原理是這樣的:{div, button} = require("react").DOM
div
className: "message"
onClick: @messageHandler
"some messages"
button()
我覺得這樣的寫法真的很簡潔很優雅,而且一想到這寫的根本就不是HTML也沒碰一行JavaScript就覺得很開心啊。不過我是LiveScript黨,有更加簡潔的語法,把Atom的helper改了改現在用在別的項目中用得很方便。
看到這樣的寫法觀眾就應該很清楚地明白了React的原理了吧,它實際上就是把DOM元素全部變成了JS的類,可以直接用JS等價的代碼調用new出來。(實際上是要用一個factory包著才能用的,0.12後把createFactory整合到createElement裡面了所以現在用法上更加簡潔)
但是千萬不要以為React就只是JS世界中的東西,其實它也是受到別的領域的啟發呢。上面那個視頻中後半段詳細介紹了React,其實React這種idea受啟發於遊戲的渲染。有很多遊戲都是數據來了,跟現在的數據Merge一下,然後update數據結構,diff一下老的結構,然後再部分渲染。所以說React其實最重要的還是一種使用Virtual Element描述界面的方式,而因為這種方式容易進行二次計算,所以能夠保證最小幅度的影響用戶體驗,最大幅度地保證數據跟界面的一致。因此React才能夠很輕易地使用在後端,做給Spider渲染HTML的工作。要知道Angular在之前是做不到這一點的,對搜索引擎的優化竟然要用到PhantomJS!而且這種方式竟然還演變成了收費的服務!簡直讓人咋舌。但是React卻能做到,因為它並不受限於JavaScript,臉書最先用React做後端的時候是PHP的,後來整合Instagram的時候才分離出來寫了開源的JS庫。
所以我覺得React Native的最終形式並不僅僅是把Web做到移動端上,因為那樣根本沒法跟Native的比。而是要把React這種Viewer的架構在Native上實現,把原有的模板式UI重構成Virtual Elements,然後以更加動態的方式渲染。不過就目前的開發進度來看,這個目標還是遙遙無期。
以上。henter/ReactNativeRubyChina · GitHub
henter/ReactNativeRubyChina · GitHub
Facebook 在 React.js Conf 2015 大會上推出了基於 JavaScript 的開源框架 React Native,結合了 Web 應用和 Native 應用的優勢,可以使用 JavaScript 來開發 iOS 和 Android 原生應用。
下面是對 React Native 官方文檔的完整中文翻譯教程:Facebook React Native 中文教程現在趨勢都是聲明式UI,React卻「反其道而行之」,把UI深綁定到JS中去。
然後又嫌編程創建控制項的方式,對於UI的樹狀結構太麻煩了,於是搞出了個JSX。
我就納了悶了,他們團隊究竟是對JS有多執念?
誠然,以現在的前端生態來看,JS的群眾基礎的確是好。
但是用React以後,要盡量保證與傳統DOM的絕緣(下文例1)。從ReactJS的Virtual DOM,到React Native的「No DOM」(引號是因為這詞不是官方的)。對JS群眾而言是個新的挑戰。(而且JSX真的是不怎麼不好用啊)
因為還沒有看到第二天的視頻,具體的一些實例代碼和技術細節都看不到,但從現在的理解看,它僅僅是用了JS而已,跟前端技術已經沒什麼關係了,東西都要重新學。這方面我會繼續關注後面的視頻。
然後另一方面,因為我不會做Native開發,不知道各種控制項樣式一般而言是用什麼來定義的,但是如果React Native要搞一套「Native CSS」,這又是個學習成本,同時也是個無底天坑了。
定義樣式這方面,昨天的視頻里沒有看到,今天的視頻發出來了之後我會再關注一下。
React Native除了JSX以外,已經和JS的發源地——WEB前端技術沒太大關係了,東西都要重新學。
它並不能提供一個用Web的技術和經驗來開發NativeApp的方式,這一點與Ionic有本質區別。
前端開發者想因此快速做出Native開發,也許做做小樣還行,做出嚴肅的產品,我認為還有距離。
結論:我不認為這是一條給前端轉Native的出路。
----------------------
上文例1
一些UI控制項,比如模態框,很常見的實現方式是把模態框從定義的節點裡摘出來,塞進BODY裡面,這樣定位什麼的非常好搞。
氮素,這樣做會破壞真實DOM與Virtual DOM之間的關聯關係,ReactJS會罷工。
例如SemanticUI中的modal組件,需要指定`detachable`才能讓它不把元素剝離進BODY里去,SemanticUI的文檔中說這樣做會造成渲染獲得更少硬體加速的優化。
這還是人UI組件庫設計的比較周到,如果是要撿著自己的組件庫用,還不知道要踩多少坑。
相比之下,Angular要友好一些,我們寫的UI組件自己接管狀態維護之後,只需要修改$scope,然後調用$apply,把狀態維護交還給它的digest循環就行了。具體就不展開討論了。
推薦閱讀:
TAG:JavaScript | iOS 開發 | Android 開發 | 原生應用 | React Native |