如何评价 React Native?

write native apps with React.js?
关注者
7954
被浏览
1130369

80 个回答

本文首发于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
  1. 无需编译,我在第一次编译了ipa装好以后,就再也没更新过app,只要更新云端的js代码,reload一下,整个界面就全变了。
  2. 多数布局代码都是JSX,所有Native组件都是标签化的,这对于前端程序员来说,降低了不少学习成本,也大大减少了代码量。不信你可以看看JSX编译后的代码。
  3. 复用React系统,也减少了一定学习和开发成本,更重要的是利用了React里面的分层和diff机制。js层传给Native层的是一个diff后的json,然后由Native将这个数据映射成真正的布局视图。
  4. css-layout也是点睛之笔,前端可以继续用熟悉的类css方式来编写布局,通过这个工具转换成constrain布局。
  5. 系统只有js-objc的单向调用,就是把原生UI组件的方法通过javascritcore或者webview(低版本iOS)映射到js中来,整个调用过程是异步的,这样的设计令React native可以让js运行在桌面chrome中,通过websocket连接Native code和桌面chrome,极大地方便了调试。对其中的机制Bang的一篇文章写得很详细,我就不拾人牙慧了:React Native通信机制详解 « bang’s blog 。但这样设计也会带来一些问题,后面说。
  6. 点按操作也被抽象成了一组组件(TouchableXXX),这种抽象方式是我在之前做类似工作中没有想到的。facebook还列出Native为什么和web「手感」不同的原因:实时的点按反馈和取消能力。React Native 这套相应机制设计得很完善,能像Native code那样控制整个点按操作的所有过程。
  7. Debug相当方便!修改了js以后,通过内建的nodejs watcher编译成bundle,在模拟器里面按cmd+r就可以看到效果。而且按cmd+d,可以打开一个chrome窗口,所有的js都移到了chrome里面运行,所以什么断点单步打调用栈,都不在话下。

上面的既是特点也是优点,下面说说缺点,或者应该说:「仍然遗留的问题」,在我看来,这个方案已经超越了Hybird方案。

  1. 系统仍然(不得不)依赖原生组件暴露出来的组件和方法。举两个例子,ScrollView这个组件,在Native层是有大量事件的,scrollViewWillBeginDragging, scrollViewWillEndDragging,scrollViewDidEndDragging等等,这些事件在现有的版本都没有暴露,基本上做不了组件联动效果。另外,这个版本中有大量组件是iOS only的:ActivityIndicatorIOS、DatePickerIOS、NavigatorIOS、PickerIOS、SliderIOS、SwitchIOS、TabBarIOS、AlertIOS、AppStateIOS、LinkingIOS、PushNotificationIOS、StatusBarIOS、VibrationIOS,反过来看,剩余的都是一些抽象程度极强的基本组件。这样,用户必须在不同的平台下写两套代码,而且所有能力仍然强烈依赖 React native 开发人员暴露的接口。
  2. 由于最外层是React,初次学习成本高,不像往常的Hybird方案,只要多学几个JS API就可以开始干活了。当然,React的确让后续开发变得简单了一些,这么一套外来的(基于iOS)、残缺不全的(css-layout)在React的包装下,的确显得不那么面目可憎了。

另外,React Native仍然很不完善。文档还不全,我基本上是看着他的示例代码完成的demo,集成到已有app的文档也是今天才出来。按照官方的说法,Android版本要到半年后才发布:Blog | React ,届时整个系统设计可能还会有很大的变化。


PS,在使用Tabbar的时候,我惊喜的发现他们居然用了iconfont方案,我现在手头的项目中也有同样的实现,不过API怎么设计一直很头疼。结果,我发现他是这么写的:

<TabBarItemIOS
                name="blueTab"
                icon={_ix_DEPRECATED('favorites')}
....>

在 _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的Constraint\origin\Center等属性),再比如动画。另外,若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多一些,但你要想查看更多问题,欢迎你到这里查看:github.com/facebook/rea

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 <NSURLProtocolClient>)client {
  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](chrome.google.com/webst)的原因。虽然工具很强大,但是你需要慎用,尤其是你想试试计时器是否起作用的时候:

github.com/facebook/rea

4.动画:

RN的动画真的很难用,至少我是这么认为的。在看完腾讯Alloy Team相关技术文章后,做动画还是很别扭,这种别扭感超过了我在搞Mac开发时做动画的感觉。使用LayoutAnimation可能还能好一些,但是能做的实现是在有限,更不用说原生那种转场动画,做跨组件之间的动画更加蛋疼。

然而这不是关键,如果你如果没有正确的使用动画,会对你业务代码的执行造成影响。比如说我们都知道使用InteractionManager.runAfterInteractions来跑耗时操作,然而里面代码的执行是依赖动画执行情况的,已经有很多人提出类似的issue了,比如这个:

InteractionManager.runAfterInteractions doesn&amp;amp;amp;amp;amp;#x27;t finished properly · Issue #7714 · facebook/react-native

一些针对动画性能的优化上,比如你想对listview的cell的动画做一下深度定制,一些国外的实践经验也是要针对平台优化(也就是写原生的package),而不是琢磨RN的Animation:

medium.com/@talkol/perf

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 ()<UIGestureRecognizerDelegate>

@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<NSString *> *)supportedEvents {
  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的<Text>嵌套简直是灾难(但是官方不这么认为),我顿时怀念YYText了。

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的方式吧。

为什么?