• 摘要 2015年是React Native发展的一年,2016年必定是React Native蓬勃的一年!2016年React Native很可能成为最为成功的开源技术之一。为什么React Native这么火呢?那么React Native相比H5、Native又有哪些优势呢?...

    @王利华,vczero

    “存在即合理”。凡是存在的,都是合乎规律的。任何新事物的产生总要的它的道理;任何新事物的发展总是有着取代旧事物的能力。React Native来的正是时候,一则是因为H5发展到一定程度的受限;二则是移动市场的迅速崛起强调团队快速响应和迭代;三则是用户的体验被放大,用户要求极致的快感,除非你牛x(例如:12306最近修改手机号需要用户自己发短信接收验证码)。
    以下简单的介绍下H5、React Native、Native的含义:

    最近三四年间,国内外的前端与全栈开发者社区都在坚持不懈地追寻使用JavaScript与HTML、CSS技术体系开发App内场景的核心工程技术。这种技术,在国内很多公司与团队中,被通称为H5。——童遥

    这段是取自童老师给小二我新书作的序,没有断章取义的意思。很清楚,H5并不是狭义的HTML5新标签和API,而是工程化的“In App” technology。

    iOS/Android ——原生应用(都懂得,不解释)。

    React Native —— React & Native ,应运而生!

    一、React Native的出现

    React Native的出现,似乎是扛起的反H5的旗子。就像当年Facebook放弃H5,全部转向Native一样。这一点,我们需要认同和保持高度的清醒。那么,React Native是否又是在吞食Native的领地呢?技术的发展,是用户风向标的导向起的作用。任何一门技术的出现,都是当时用户需求的体现。

    我们应该从以下几点看待React Native的出现。

    "鉴往知来"——从过去的教训中总结经验,从用户的角度开拓未来
    “HTML5差强人意,但是与原生应用相比还是有些差距”——为了更高的追求! 用户体验!
    “人才宝贵,快速迭代”——Web开发者相对较多,寻找平衡点
    “跨平台!跨平台!跨平台!”——单一技术栈
    “xx是世界上最好的语言” ——工程学的范畴,没有最好,只有最适合

    HTML5 vs React Native ? HTML5 : React Native
    结论(React Native):
    1、原生应用的用户体验
    2、跨平台特性
    3、开发人员单一技术栈
    4、上手快,入门容易
    5、社区繁荣

    二、3款应用效果

    注:以下所有对比均在iOS平台下



    上面3张图片,如果去掉第一张图的“HybirdApp”的字样,是否分得清哪个是React Native开发?哪个是Native应用。
    你的第一感觉是什么?

    三、工程方案

    为了评估3种方案的技术优势和弱势。我们需要开发功能大致相似的App。这里,我们使用了“豆瓣”的API来开发“豆搜”应用。该应用能够搜索“图书”、“音乐”、“电影”。想当年,豆瓣以“图书评论”走红,尤其是12年当红!豆瓣是一个清新文艺的社区,一个“慢公司”。最近有一则网传消息,注意是网传——“传京东投1.5亿美元控股豆瓣”。今天,不聊豆瓣,我们要聊一个工程化的问题。

    我们需要将3款App的功能做到一致,同时需要保持技术要点一致。比如React Native这里使用了TabBar,那么Native我们也必须使用TabBar。简单而言就是:功能一致,组件 & API一致。我们功能如下图所示:

    1、H5方案
    在H5/Hybird应用中,我们使用AngularJS开发单页webApp,然后将该WebApp内嵌入到iOS WebView中,在iOS代码中,我们使用Navigation稍微控制下跳转。
    WebApp地址:http://vczero.github.io/search/html/index.html
    WebApp项目地址:https://github.com/vczero/search (很简单的一个项目)
    H5/Hybird项目地址:https://github.com/vczero/search_Hybird

    2、React Native
    在React Native中,封装必要的功能组件。
    项目地址:https://github.com/vczero/React-Dou。
    项目结构如下图:

    3、Native(iOS)
    使用React Native大致相同的组件开发App,不使用任何第三方库,代码布局。
    项目地址:https://github.com/vczero/iOS-Dou

    四、对比分析

    很多时候,新技术的采用最希望看到的是数据,而不是简单说“用户体验棒,开发效率高,维护成本低”。不过,生活中也有这样的同学,知一二而能窥全貌。当然,本人生性胆小,也没有那么多的表哥和隔壁的老王,所以不敢早下定论,不敢太放肆。赵本山在《大笑江湖》中有句名言“May the force be with you”(别太放肆,没什么用)。因此,从以下几个方面做一个简单的对比。

    ----------提纲------------

    1、开发方式

    (1)代码结构
    (2)UI布局
    (3)UI截面图
    (4)路由/Navigation
    (5)第三方生态链

    2、性能 & 体验

    (1)内存
    (2)CPU
    (3)动画
    (4)安装包体积
    (5)Big ListView
    (6)真机体验

    3、更新 & 维护

    (1)更新能力
    (2)维护成本
    ----------提纲------------

    1、开发方式

    很多人说React Native的代码不好看,不好理解。那是因为前端工程师都熟悉了Web的开发方式。怎么解决这个问题呢,可以先看看iOS代码,断定不熟悉iOS的同学心里会默念“一万匹**马奔腾”。那时候,你再看React Native,你会觉得使用React Native开发App是件多么美好的事!OK,我们先来看下三者在开始“一款简单App”的代码结构。
    (1)代码结构
    H5/Hybird的开发模式,我们需要维护3套代码,两套是Native(iOS/Android)代码,一套是WebApp版本。这里,我们使用AngularJS作为WebApp单页开发框架。如下图所示。

    在React Native中,同样需要关注部分的Native代码,但是大部分还是前端熟悉的JavaScript。在“豆搜”应用中,代码结构如下:

    在Native开发中,更加强调Native开发者的能力。平台是:iOS/Android。

    结论:从前端角度而言,React Native跨平台特性,不要开发者深入的了解各平台就能开发一款高效App。同时,语言层面而言,JavaScript运用很广泛,入门门槛相对较低。React Native虽然抛弃了MVC分离实践,但是从业务角度而言,更为合理。一切而言:对前端,对移动领域是利好的消息。

    (2)UI布局
    “面容姣好”,合理的UI却总是跟着时间在变。那么UI布局就不是小事。
    Web开发布局目前大多是 DIV + CSS。
    React Native的布局方式是Flexbox。

       //JSX
      <ScrollView style={styles.flex_1}>
        <View style={[styles.search, styles.row]}>
          <View style={styles.flex_1}>
            <Search placeholder="请输入图书的名称" onChangeText={this._changeText}/>
          </View>
          <TouchableOpacity style={styles.btn} onPress={this._search}>
            <Text style={styles.fontFFF}>搜索</Text>
          </TouchableOpacity>
        </View>
        {
          this.state.show ?
          <ListView
            dataSource={this.state.dataSource}
            renderRow={this._renderRow}
            />
          : Util.loading
        }
      </ScrollView>
      //样式
      var styles = StyleSheet.create({
          flex_1:{
            flex:1,
            marginTop:5
          },
          search:{
            paddingLeft:5,
            paddingRight:5,
            height:45
          },
          btn:{
            width:50,
            backgroundColor:'#0091FF',
            justifyContent:'center',
            alignItems:'center'
          },
          fontFFF:{
            color:'#fff'
          },
          row:{
            flexDirection:'row'
          }
        });        

    而Native布局就有种让你想吐的感觉,尤其是iOS的布局。这里不是指采用xib或者Storyboard,而是单纯的代码,例如添加一个文本:

    UILabel *publisher = [[UILabel alloc]init];
    publisher.frame = CGRectMake(bookImgWidth + 10, 50, 200, 30);
    publisher.textColor = [UIColor colorWithRed:0.400 green:0.400 blue:0.435 alpha:1];
    publisher.font = [UIFont fontWithName:@"Heiti TC" size:13];
    publisher.text = obj[@"publisher"];
    [item addSubview:publisher];           

    总结:React Native既综合了Web布局的优势,采用了FlexBox和JSX,又使用了Native原生组件。比如我们使用一个文本组件。
    <Text style={{width:100;height:30;backgroundColor:'red'}}>测试</Text>

    (3)UI截面图
    Hybrid方式截面图

    可以看到第一层列表页是完整的布局,实际上这就是Web页面;而第二层灰色的是Native的WebView组件。
    iOS UI截面图


    可以看到Native页面的组件特别多,即使是列表页,其中某一项都是一个组件(控件)。

    当然,我们就会想,能够完全调用原生组件呢?那样性能是否更好?
    React Native UI截面图


    可以清楚的看到React Native调用的全部是Native组件。并且层次更深,因为React Native做了组件的封装。如上图,蓝色边框的就是RCTScrollView组件。

    (4)路由/Navigation
    在Web单页面应用中,路由由History API实现。
    而React Native采用的路由是原生的UINavigationController导航控制器实现。
    React Native NavigatorIOS组件封装程度高;Navigator可定制化程度高。
    Navigator方法如下:

    getCurrentRoutes() - returns the current list of routes
    jumpBack() - Jump backward without unmounting the current scene
    jumpForward() - Jump forward to the next scene in the route stack
    jumpTo(route) - Transition to an existing scene without unmounting
    push(route) - Navigate forward to a new scene, squashing any scenes that you could jumpForward to
    pop() - Transition back and unmount the current scene
    replace(route) - Replace the current scene with a new route
    replaceAtIndex(route, index) - Replace a scene as specified by an index
    replacePrevious(route) - Replace the previous scene
    immediatelyResetRouteStack(routeStack) - Reset every scene with an array of routes
    popToRoute(route) - Pop to a particular scene, as specified by its route. All scenes after it will be unmounted
    popToTop() - Pop to the first scene in the stack, unmounting every other scene         

    相对Native而言,这些接口更Native还是很相似的。

    //iOS UINavigationController  
    //相对Web而言,不用自己去实现路由,并且路由更加清晰         
    [self.navigationController pushViewController:detail animated:YES];

    "豆搜" WebApp路由(基于AngularJS)如下:

    "豆搜" React Native版本导航如下:

    "豆搜" iOS版本导航代码如下:

    总结:React Native封装的导航控制更容易理解。

    (5)第三方生态链
    “我的是我的,你的也是我的。 ”——我不是“疯狂女友”,我是React Native!
    我们缺少“城市列表”组件,OK,使用JSX封装一个;觉得性能太低,OK,基于React Native方案封装一个原生组件。
    这个iOS图表库不错,拿来用呗! => 完美!
    这一切都是基于React Native提供的模块扩展方案。
    所以说:iOS第三方库 + 部分JavaScript库 = React Native 生态库

    2、性能 & 体验

    我们都很关注一款App性能。因此测试和体验App的性能很重要。以下测试,都是基于相同的case。
    测试平台:模拟器,iphone6,iOS8.4
    (1)内存
    首先,我们来看下Native应用占用的内存情况。一开始,原生应用启动后,占用内存是20~25M;针对相同的case,跑了2min,结果如下图:

    可以看出,峰值是87.9M,均值是72M;内存释放比较及时。

    我们再来看下Hybird App的情况。App已启动,占用内存35~55M;同样,跑了2min以上,结果如下图:

    可以看出,峰值在137.9M,因为整个应用在WebView中,内存释放不明显,存在缓存。

    最后,看下React Native的情况。App启动占用内存35~60M,同样跑2min以上,结果如下图:

    可以看出,峰值在142M,内存相对释放明显。

    总结:React Native和Web View在简单App上相差不大。二者主要:内存消耗主要是在网页数据上。

    (2)CPU
    我们可以看一下Native应用程序CPU的情况,最高值在41%。

    Hybird App的最高值在30%。

    React Native的最高值在34%。

    总结:CPU使用率大体相近,React Native的占用率低于Native。

    (3)动画
    React Native提供了Animated API实现动画。简单效果,基本OK。个人觉得React Native不适合做游戏,尤其布局能力。
    Native Animation提供UIView动画
    H5/Hybird:采用js动画能力
    总结:React Native Animated API / 封装Native动画库 可以满足基本需求

    (4)安装包体积
    Hybird App:
    34(App壳) + 5(HTML) + 125(Angular) + 29(An-route) + 6(min.js) + 4(min.css) = 203 KB。

    React Native:
    不含bundle: 843KB
    含bundle: 995KB

    Native
    83KB

    React Native框架包大小
    843(不含bundle) – 32(Hybird_app空壳,初识项目) = 811KB

    相比快速迭代和热更新,比Native多了811KB一点都不重要,我们将图片素材、静态资源线上更新缓存起来即可减少很多体积。
    总结:牺牲一点体积,换更大的灵活性!(世界上哪有那么美的事,除非丑,就会想得美,:) )。

    (5)Big ListView & Scroll 性能
    循环列表项500次: H5页面惨不忍睹
    React Native还可以接受
    Native 采用UITabView更高效,因为不渲染视图外部分。

    (6)真机体验
    机型:iphone4s,iOS7
    Native > React Native > Hybird
    如果非要给个数字的话,那我个人主观感受是:
    Native: 95%+ 流畅度
    React Native: 85~90% 流畅度
    H5/Hybird: 70% 流畅度

    总结:Native/React Native的体验相对而言更流畅。

    3、更新 & 维护

    (1)更新能力
    H5/Hybird: 随时更新,适合做营销页面,目前携程一些BU全部都是H5页面;但是重要的部分还是Native。
    React Native:React Native部分可以热更新,bug及时修复。
    Native:随版本更新,尤其iOS审核严格,需要测试过关,否则影响用户。

    (2)维护成本
    H5/Hybird: Web代码 + iOS/Android平台支持
    React Native:可以一个开发团队 + iOS/Android工程师;业务组件颗粒度小,不用把握全局即可修改业务代码。
    Native:iOS/Android开发周期长,两个开发团队。

    总结:React Native 统一了开发人员技术栈,代码维护相对容易。

    五、综合

    1、开发方式

    (1)代码结构: React Native更为合理,组件化程度高
    (2)UI布局:Web布局灵活度 > React Native > Native
    (3)UI截面图:React Native使用的是原生组件,
    (4)路由/Navigation:React Native & Native更胜一筹
    (5)第三方生态链:Native modules + js modules = React Native modules

    2、性能 & 体验

    (1)内存:Native最少;因为React Native含有框架,所以相对较高,但是后期平稳后会优于Native。
    (2)CPU:React Native居中。
    (3)动画:React Native动画需求基本满足。
    (4)安装包体积:React Native框架打包后,811KB。相比热更新,可以忽略和考虑资源规划。
    (5)Big ListView
    (6)真机体验:Native >= React Native > H5/Hybrid

    3、更新 & 维护

    (1)更新能力: H5/Hybird > React Native > Native
    (2)维护成本: H5/Hybird <= React Native < Native

    React Native定制难度相比Native有些大;但是具备跨平台能力和热更新能力。
    最后硬广一下我的书:


    展开全文
  •  刚创建的React Native技术交流群(282693535),欢迎各位大牛,React Native技术爱好者加入交流!同时博客左侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!(二)基本介绍 该Text组件为Reac

    转载请标明出处:

    http://blog.csdn.net/developer_jiangqq/article/details/50550518

    本文出自:【江清清的博客】


    ()前言

             【好消息】个人网站已经上线运行,后面博客以及技术干货等精彩文章会同步更新,请大家关注收藏:http://www.lcode.org 

           今天我们一起来看一下Text控件的具体介绍和使用方法,具体环境搭建以及相关配置的请查看之前的相关文章。

            刚创建的React Native技术交流3群(496508742),React Native技术交流4群(458982758),请不要重复加群!欢迎各位大牛,React Native技术爱好者加入交流!同时博客左侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!

    ()基本介绍

             Text组件为React中一个基本组件,该和Android中的TextView组件相类似用来显示基本的文本信息,该控件除了基本的显示布局之外,可以进行嵌套显示,设置样式,以及可以做事件(例如:点击)处理。下面我们来一个实例:

    /**
     * 进行简单Text组件使用实例
     * Sample React Native App  test.android.js
     * https://github.com/facebook/react-native
     */
    'use strict';
    var React =require('react-native');
    var {
      AppRegistry,
      Text,
      StyleSheet,
    } = React;
     
    var styles =StyleSheet.create({
         titleBase:{
            margin:10,
            textAlign:'center',
            color:'red',
            fontSize:28,
            fontFamily:'Cochin',
         },
         title:{
            color:'green',
            fontWeight:'bold',
         },
    });
    var TestText =React.createClass({
      render: function() {
        return (
          <Text style={styles.titleBase}>
             I am root text!
             <Text style={styles.title}>
                  I am chid text!
              </Text>
          </Text>
        );
      }
    });
    AppRegistry.registerComponent('TestText',() => TestText);

    具体运行效果如下:


    上述实例采用TextView的嵌套方式,最外层的Text的Style titleBase定义相关风格,内层的风格style定义相关风格,我们可以看到运行效果,如果内层没有重写外层定义的样式,那么内层会进行继承。如果重写了样式,那么内层会根据自己定义的样式进行渲染,该和CSS样式表差不多。

    上面例子主要定义了布局,字体大小,字体风格,颜色等相关样式,下面我们会着重进行讲解。

    ()属性方法(主要一些可用的属性)

          .allowFontScaling (bool):控制字体是否根据iOS的设置进行自动缩放-iOS平台,Android平台不适用

          .numberOfLines (number):进行设置Text显示文本的行数,如果显示的内容超过了行数,默认其他多余的信息就不会显示了。

         .onLayout (function) 当布局位置发生变动的时候自动进行触发该方法, 其中该function的参数如下:

    {nativeEvent: {layout: {x, y, width, height}}}

         .onPress (fcuntion) 该方法当文本发生点击的时候调用该方法.

    ()风格样式

          1..承可以使View组件所有Style(具体查看http://facebook.github.io/react-native/docs/view.html#style)

          2.color:体颜色                                

          3..fontFamily 字体名称

          4..fontSize  字体大小

          5..fontStyle   字体风格(normal,italic)

          6..fontWeight  字体粗细权重("normal", 'bold', '100', '200', '300', '400', '500','600', '700', '800', '900')

          7..textShadowOffset 设置阴影效果{width: number, height: number}

          8..textShadowRadius 阴影效果圆角       9..textShadowColor 阴影效果的颜色

          10.letterSpacing 字符间距            11.lineHeight行高

          12.textAlign   文本对其方式("auto",'left', 'right', 'center', 'justify')

          13.textDecorationLine  横线位置 ("none", 'underline', 'line-through', 'underlineline-through')

          14.textDecorationStyle  线的风格("solid", 'double', 'dotted','dashed')

          15.textDecorationColor  线的颜色      16.writingDirection  文本方向("auto", 'ltr','rtl')

    ()特别注意点

            5.1.嵌套特点:Web上面一直的设计方案,我们通过嵌套包裹的方案,相同的属性的文本可以用父标签进行包裹,然后内部特殊的文本采用子标签方案,具体例子如下:  

    <Text style={{fontWeight:'bold',fontSize:28}}>
                 I am bold
                <Text style={{color: 'red'}}>
                   and red
               </Text>
          </Text>

    具体运行效果如下:


    我们看到整体字体格式为bold以及字体大小为28,不过后边的'and red'的字体颜色为红色。

     5.2.容器布局规则

            之前我们介绍View组件,我们知道该组件是支持FlexBox(弹性布局),但是Text组件直接是文本布局的,也就是说一个Text接着Text,横向,如果文本已经到末尾了,那就直接换行。

    我们来看一下具体实例代码:

    <Text>
               <Text>One Test </Text>
               <Text>Second Test</Text>
           </Text>

    运行截图如下:


    这样我们可以看到两个Text直接横向排布,第二个Text直接接在第一个Text后面了。但是如果该父控件采用View,View是支持FlexBox布局的,具体看如下实例代码:

    <View>
           <Text>First part and </Text>
           <Text>second part</Text>
    </View>

    默认垂直分布,运行结果如下:


     5.3.样式继承规格

            从以上一些例子中我们也发现了,组件可以嵌套,而且样式还支持继承,也就说父组件定义了相关样式,如果子组件没有重写样式的话,那么该子组件会继承父组件定义的样式。

    ()Text实例

           下面使用以上一些属性和相关样式来演示一下实例:

    /**
     *  Text组件实例演示
     * Sample React Native App
     * https://github.com/facebook/react-native
     */
    'use strict';
    var React =require('react-native');
    var {
      AppRegistry,
      Text,
      View,
      StyleSheet,
    } = React;
    var TestText =React.createClass({
      render: function() {
        return (
          <View>
           <Text style={{color:'red'}}>
              My Text One  红色。
           </Text>
           <Textstyle={{color:'green',fontSize:20}}> My Text Two 绿色和字体大小。</Text>
           <Textstyle={{color:'green',fontFamily:'Cochin'}}> My Text Three 绿色和字体名称。</Text>
           <Textstyle={{color:'pink',fontWeight:'bold'}}> My Text Four 粉色和加粗。</Text>
           <Textstyle={{color:'gray',fontStyle:'italic'}}> My Text Five 灰色和斜体。</Text>
           <Textstyle={{textAlign:'center',fontStyle:'italic'}}> My Text Six 居中和斜体。</Text>
           <TextnumberOfLines={1} style={{textAlign:'center',fontStyle:'italic'}}>测试行数My Text Six 居中和斜体。My Text Six 居中和斜体。 My Text Six 居中和斜体。</Text>
           <Textstyle={{marginLeft:50,marginTop:50,textAlign:'center',fontStyle:'italic'}}>设置文本的间距,居左,居顶部50</Text>
           <Text numberOfLines={2}style={{lineHeight:50,textAlign:'center',fontStyle:'italic'}}>
              测试行高 测试行高 测试行高 测试行高 测试行高 测试行高 测试行高 测试行高 测试行高 测试行高 测试行高
              测试行高 测试行高 测试行高 测试行高 测试行高 测试行高
           </Text>
          </View>
        );
      }
    });
    AppRegistry.registerComponent('TestText',() => TestText);

    具体运行截图如下:


    ()最后总结

              今天我们主要给大家介绍Text组件的相关属性和样式的基本使用方法。大家有问题可以加一下群React Native技术交流群(282693535)或者底下进行回复一下。

             尊重原创,转载请注明:From Sky丶清(http://blog.csdn.net/developer_jiangqq) 侵权必究!

           关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

         关注我的微博,可以获得更多精彩内容

          

     

     

    展开全文
  • 转载 ...在运行ReactNative项目的时候,手机上经常会出现这种错误,解决办法:在当前项目的路径下分别运行以下命令:?123npm uninstall babel-preset...

    转载 https://www.2cto.com/kf/201708/674121.html

    ReactNative:The development server returned response error code: 500。

    这里写图片描述

    在运行ReactNative项目的时候,手机上经常会出现这种错误,解决办法:
    在当前项目的路径下分别运行以下命令:

    1
    2
    3
    npm uninstall babel-preset-react-native
     
    npm install babel-preset-react-native@2.1.0 —save

    如果你是用的yarn的话,需要将npm修改为yarn:

    1
    2
    3
    <code><code><code>yarn remove babel-preset-react-native
     
    yarn add babel-preset-react-native@2.1.0</code></code></code>

    或者直接将根目录下的package.json文件里面的babel-preset-react-native的版本修改为2.1.0,
    然后执行npm update
    这里写图片描述
    最后,重新运行项目即可


    展开全文
  • 2018年 React Native

    2018-07-22 17:21:23
    自从上次发布关于React Native的更新状态已经有一段时间了。 在Facebook内部,我们将React Native技术应用到更多的重要项目中。我们最受欢迎的产品之一是Marketplace,作为我们的顶级应用,每月有8亿人使用。自2015...

    自从上次发布关于React Native的更新状态已经有一段时间了。

    在Facebook内部,我们将React Native技术应用到更多的重要项目中。我们最受欢迎的产品之一是Marketplace,作为我们的顶级应用,每月有8亿人使用。自2015年构建以来,Marketplace已完全使用React Native构建,共计超过100个视图(页面)。

    我们也在应用的许多新部分使用 React Native。如果您看了上个月的F8主题演讲,就会发现 Blood Donations、Crisis Response、Privacy Shortcuts 和 Wellness Checks 的所有新功能都是使用 React Native 构建的。Facebook 主应用以外的项目也在使用 React Native。新的 Oculus Go VR 头戴式设备对应的移动应用程序 就完全使用 React Native 构建。

    当然,我们也使用许多其他技术来构建我们的应用程序。LithoComponentKit是我们在应用程序中广泛使用的两个库;React Native 的目标从来都不是替代其他技术,专注于 React Native 自身,努力使之变得更好,但依旧希望看到其他团队从 React Native 中得到一些想法或灵感,例如将instant reload(即时重载)运用到非 JavaScript 代码中。

    架构

    当我们在2013年启动React Native项目时,React Native 项目的设计初衷是成为 JavaScript 和原生应用之间的一个异步,序列化和批处理的“桥梁”。
    React DOM 将 React 的状态更新变成了命令式、可变的 DOM API 调用,如 document.createElement(attrs).appendChild(),而 React Native 则返回一个单独的 JSON 消息,它列出了要执行的一些操作,如 [["createView", attrs], ["manageChildren", ...]]。就像React DOM将React状态更新变成命令式的,对document.createElement(attrs)和.appendChild()等DOM API的可变调用一样,React Native被设计为返回一个列出要执行的突变的JSON消息,如[[“createView “,attrs],[”manageChildren“,...]]。 我们将整个系统设计为永不依赖获取同步响应,并确保列表中所有的内容都可以完全序列化为 JSON,并可以反序列化回来。这样做是为了提高灵活性:在这个架构之上,可以构建像 Chrome 调试器之类的工具,这些工具可以通过 WebSocket 连接异步运行所有的 JavaScript 代码。

    在过去的五年里,我们发现最初的设计原则加大了某些特性的开发难度。异步桥接(asynchronous bridge)意味着不能直接将 JavaScript 逻辑与很多原生 API 集成在一起,因为这些原生 API 是同步的。批量桥接(本地调用队列)意味着 React Native 应用程序调用本地函数会更加困难。而且串行化的桥接意味着不必要的复制,因为它不是直接在两个世界之间共享内存。对于完全使用 React Native 构建的应用程序,这些限制通常是可承受的。但对于在 React Native 与现有原生代码之间进行复杂集成的应用程序,就非常糟糕了。

    因此,Facebook 正在对 React Native 进行大规模重构,让框架变得更加灵活,并更好地与JavaScript / 原生混合应用中的原生基础设施集成。 通过这个项目,将应用在过去 5 年中学到的知识,逐步让架构走向现代化。我们正在重构 React Native 的核心部分,大部分工作都是在底层进行的,
    现有的 React Native 应用程序几乎不需要做出更改

    为了使 React Native 更轻量化并能更好地适应现有的原生应用,此次重构主要从三个方面进行。首先,我们改变了线程模型:UI 更新不再需要在三个不同的线程上执行,可以在任意线程上同步调用 JavaScript 进行优先更新,同时将低优先级工作推出主线程,以便保持对 UI 的响应。其次,将异步渲染功能引入 React Native 中,允许执行多个渲染并简化异步数据处理。最后,简化桥接,让它更快、更轻量。原生和 JavaScript 之间的直接调用效率更高,并且可以更轻松地构建调试工具,如跨语言堆栈跟踪。

    完成以上工作之后,将带来更紧密更合理的集成。现在,如果不通过复杂 hack 的手段就无法让原生导航和手势处理或原生组件(如 UICollectionView 和 RecyclerView)一起工作。在对线程模型做出更改之后,我们将可以直接构建这样的功能。

    当这个项目将要完工时,Facebook 会披露更多的细节,敬请期待。

    社区

    除了Facebook 内部社区外,我们很高兴在Facebook之外拥有蓬勃发展的React Native用户和合作者。 我们希望与React Native社区有更多的沟通与支持,以此能更好地为React Native用户提供服务并让开发者更易于做出贡献。

    React Native底层体系结构的更改将有利于与其他原生基础架构更加简单地进行交互操作一样,React Native在JavaScript端应该更加简单轻度,以更好地适应JavaScript生态系统,包括VM和bundler之间的通信交换。我们知道变革的步伐很难跟上,所以我们希望找到减少频繁发布新版本的方法。 最后,我们知道现有的文档缺少关于启动优化等诸多方面的内容,这些情况将在未来一年中有所改善。

    如果您使用的是React Native,那么您就是我们社区的一部分; 请将您使用React Native的困惑反馈给我们。

    虽然React Native只是移动开发者工具箱中的一个工具,但我们对其有充分的信心与支持 - 我们每天都在改进,去年有500名贡献者提交了超过2500次提交。

    展开全文
  • 2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了。文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能优化的文章。 本文谈到的 React ...

    2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了。文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能优化的文章。

    本文谈到的 React Native 性能优化,还没到修改 React Native 源码那种地步,所以通用性很强,对大部分 RN 开发者来说都用得着。

    本文的内容,一部分是 React/RN/Android/iOS 官方推荐的优化建议,一部分是啃源码发现的优化点,还有一部分是可以解决一些性能瓶颈的优秀的开源框架。本文总结的内容你很少在网络上看到,所以看完后一定会有所收获。如果觉得写的不错,请不要吝啬你的赞,把这篇 1w 多字的文章分享出去,让更多的人看到。

    看文章前要明确一点,一些优化建议并不是对所有团队都适用的。有的团队把 React Native 当增强版网页使用,有的团队用 React Native 实现非核心功能,有的团队把 React Native 当核心架构,不同的定位需要不同的选型。对于这些场景,我在文中也会提一下,具体使用还需要各位开发者定夺。

     

    目录:

    • 一、减少 re-render
    • 二、减轻渲染压力
    • 三、图片优化那些事
    • 四、对象创建调用分离
    • 五、动画性能优化
    • 六、长列表性能优化
    • 七、React Native 性能优化用到的工具
    • 八、推荐阅读

     

    一、减少 re-render

    因为 React Native 也是 React 生态系统的一份子,所以很多 React 的优化技巧可以用到这里,所以文章刚开始先从大家最熟悉的地方开始。

    对于 React 来说,减少 re-render 可以说是收益最高的事情了。

    1️⃣ shouldComponentUpdate

    📄 文档
    https://react.docschina.org/docs/optimizing-performance.html#shouldcomponentupdate-in-actionsx

    简单式例:

    class Button extends React.Component {
      shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
          return true;
        }
        return false;
      }
    
      render() {
        return <button color={this.props.color} />;
      }
    }

    无论哪篇文章,谈到 React 性能优化,shouldComponentUpdate 一定是座上宾。

    我们通过这个 API,可以拿到前后状态的 state/props,然后手动检查状态是否发生了变更,再根据变更情况来决定组件是否需要重新渲染。

    🔗 官方文档对 shouldComponentUpdate 的作用原理和使用场景已经说的非常清晰了,我就没有必要搬运文章了。在实际项目中,阅文集团的 🔗 React Native 应用「元气阅读」也做了很好的示范,🔗 Twitter 的性能优化分享也做的图文并茂,可有很高的参考价值,对此感兴趣的同学可以点击跳转查看。

    在此我想提醒的是,shouldComponentUpdate 是强业务逻辑相关的,如果使用这个 API,你必须考虑和此组件相关的所有 props 和 state,如果有遗漏,就有可能出现数据和视图不统一的情况。所以使用的时候一定非常小心。

    在此我想提醒的是,shouldComponentUpdate 是强业务逻辑相关的,如果使用这个 API,你必须考虑和此组件相关的所有 props 和 state,如果有遗漏,就有可能出现数据和视图不统一的情况。所以使用的时候一定非常小心。

    2️⃣ React.memo

    📄 文档:https://react.docschina.org/docs/react-api.html#reactmemo

    React.memo 是 React v16.6 中引入的新功能,是一个专门针对 React 函数组件的高阶组件。

    默认情况下,它和 PureComponent 一样,都是进行浅比较,因为就是个高阶组件,在原有的组件上套一层就可以了:

    const MemoButton = React.memo(function Button(props) {
      return <button color={this.props.color} />;
    });

    如果想和 shouldComponentUpdate 一样,自定义比较过程,React.memo 还支持传入自定义比较函数:

    function Button(props) {
      return <button color={this.props.color} />;
    }
    function areEqual(prevProps, nextProps) {
      if (prevProps.color !== nextProps.color) {
          return false;
        }
      return true;
    }
    export default React.memo(MyComponent, areEqual);

    值得注意的是areEqual() 这个函数的返回值和 shouldComponentUpdate 正好相反,如果 props 相等,areEqual()返回的是 trueshouldComponentUpdate 却返回的是 false

    3️⃣ React.PureComponent

    📄 文档:https://react.docschina.org/docs/react-api.html#reactpurecomponent

    简单式例:

    class PureComponentButton extends React.PureComponent {
      render() {
        return <button color={this.props.color} />;
      }
    }

    和 shouldComponentUpdate 相对应,React 还有一个类似的组件 React.PureComponent,在组件更新前对 props 和 state 做一次浅比较。所以涉及数据嵌套层级过多时,比如说你 props 传入了一个两层嵌套的 Object,这时候 shouldComponentUpdate 就很为难了:我到底是更新呢还是不更新呢?

    考虑到上面的情况,我在项目中一般很少用 PureComponent虽然很简单易用,但是面对复杂逻辑时,反而不如利用 shouldComponentUpdate 手动管理简单粗暴。当然这个只是个人的开发习惯,社区上也有其他的解决方案:

    • 把组件细分为很小的子组件,然后统一用 PureComponent 进行渲染时机的管理
    • 使用 immutable 对象,再配合 PureComponent 进行数据比较(🔗 参考链接:有赞 React 优化
    • ......

    在这个问题上仁者见仁智者见智,在不影响功能的前提下,主要是看团队选型,只要提前约定好,其实在日常开发中工作量都是差不多的(毕竟不是每个页面都有必要进行性能优化)。

     

    二、减轻渲染压力

    React Native 的布局系统底层依赖的是 🔗 Yoga 这个跨平台布局库,将虚拟 DOM 映射到原生布局节点的。在 Web 开发中,99% 的情况下都是一个 Virtual DOM 对应一个真实 DOM 的,那么在 React Native 中也是一一对应的关系吗?我们写个简单的例子来探索一下。

    我们先用 JSX 写两个橙色底的卡片,除了卡片文字,第一个卡片还嵌套一个黄色 View,第二个卡片嵌套一个空 View:

    // 以下示例 code 只保留了核心结构和样式,领会精神即可
    render() {
      return (
        <View>
          <View style={{backgroundColor: 'orange'}}>
            <View style={{backgroundColor: 'yellow'}}>
              <Text>Card2</Text>
            </View>
          </View>
          <View style={{backgroundColor: 'orange'}}>
            <View>
              <Text>Card2</Text>
            </View>
          </View>
        </View>
      );
    };

    用 react-devtools 查看 React 嵌套层级时如下所示:

    从上图中可以看出,React 组件和代码写的结构还是一一对应的。

    我们再看看 React Native 渲染到原生视图后的嵌套层级(iOS 用 Debug View Hierarchay,Android 用 Layout Inspector):

    从上图可以看出,iOS 是一个 React 节点对应一个原生 View 节点的;Android 第二个卡片的空白 View 却不见了!

    如果我们翻一翻 React Native 的源码,就会发现 React Native Android UI 布局前,会对只有布局属性的 View(LAYOUT_ONLY_PROPS 源码)进行过滤,这样可以减少 View 节点和嵌套,对碎片化的 Android 更加友好。

    通过这个小小的例子我们可以看出,React 组件映射到原生 View 时,并不是一一对应的,我们了解了这些知识后,可以如何优化布局呢?

    1️⃣ 使用 React.Fragment 避免多层嵌套

    📄 React Fragments 文档:https://zh-hans.reactjs.org/docs/fragments.html

    我们先从最熟悉的地方讲起——React.Fragment。这个 API 可以让一个 React 组件返回多个节点,使用起来很简单:

    render() {
      return (
        <React.Fragment>
          <ChildA />
          <ChildB />
          <ChildC />
        </React.Fragment>
      );
    }
    
    // 或者使用 Fragment 短语法
    render() {
      return (
        <>
          <ChildA />
          <ChildB />
          <ChildC />
        </>
      );
    }

    Fragments 作用还是蛮明显的:避免你多写一层 View。用处还是很广的,比如说自己业务上封装的 React 组件,React Native 官方封装的组件(比如说 ScrollView or Touchable* 组件 ),活用这个属性,可以减少你的 View 嵌套层级。

    2️⃣ 减少 GPU 过度绘制

    我们在业务开发时,经常会遇到这种场景:整个界面的背景色是白色的,上面又加了一个白色背景的卡片组件,卡片内部又包含了一个白色背景的小组件......

    // 以下示例 code 只保留了核心结构和样式,领会精神即可
    render() {
      return (
        <View>
          <View style={{backgroundColor: 'white'}}>
            <View style={{backgroundColor: 'white'}}>
              <Text style={{backgroundColor: 'white'}}>Card1</Text>
            </View>
          </View>
          <View>
            <View>
              <Text>Card2</Text>
            </View>
          </View>
        </View>
      );
    };

    首先我们要明确一点,屏幕上的每个像素点的颜色,是由多个图层的颜色决定的,GPU 会渲染这些图层混合后的最终颜色,但是,iOS 和 Android 的 GPU 渲染机制是不一致的。

    虽然上面的代码最后的的渲染结果在显示上都是白色的,但是 GPU 的优化是不一样的。我们用 iOS 的 Color Blended Layers 和 Android 的🔗 GPU 过度绘制调试工具查看最后的渲染结果:

    对于 iOS 来说,出现红色区域,就说明出现了颜色混合:

    • Card1 的几个 View 都设置了非透明背景色,GPU 获取到顶层的颜色后,就不再计算下层的颜色了
    • Card2 的 Text View 背景色是透明的,所以 GPU 还要获取下一层的颜色进行混合

    对于 Android 来说,GPU 会多此一举地渲染对用户不可见的像素。有一个颜色指示条:白 -> 蓝 -> 绿 -> 粉 -> 红,颜色越往后表示过度绘制越严重。

    • Card1 的几个 View 都设置了非透明背景色,红色表示起码发生了 4 次过度绘制
    • Card2 只有文字发生了过度绘制

    在过渡绘制这个测试上,iOS 和 Android 的实验结果几乎是完全相反的,所以解决方案肯定不是两全其美的,我个人认为,React Native 开发做视图优化时,应该优先优化 Android,所以我们可以从以下几点优化:

    • 减少背景色的重复设置:每个 View 都设置背景色的话,在 Android 上会造成非常严重的过度绘制;并且只有布局属性时,React Native 还会减少 Android 的布局嵌套
    • 避免设置半透明颜色:半透明色区域 iOS Android 都会引起过度绘制
    • 避免设置圆角:圆角部位 iOS Android 都会引起过度绘制
    • 避免设置阴影:阴影区域 iOS Android 都会引起过度绘制
    • ......

    避免 GPU 过度绘制的细节太多了,一般页面不需要这种精细化管理,长列表优化时可以考虑一下这个方向。

     

    三、图片优化那些事

    性能优化的另一个大头就是图片。这里的图片优化不仅仅指减少图片大小,减少 HTTP 带宽占用,我会更多的讨论一些 Image 组件上的优化,比如说缓存控制,图片采样等技术。

    1️⃣ Image 组件的优化项

    React Native 的 Image 图片组件,如果只是作为普通的图片展示组件,那它该有的都有了,比如说:

    • 加载本地/网络图片
    • 自动匹配 @2x/@3x 图片
    • 图片加载事件:onLoadStart/onLoad/onLoadEnd/onError
    • loading 默认图 or loading 指示器
    • ......

    但是,如果你要把它当一个图片下载管理库用时,就会非常的难受,因为 Image 的这几个属性在 iOS/Android 上有不同的表现,有的实现了有的没有实现,用起来非常不顺手。

    在讲解图片优化前,我们先想一下,一个基本的图片下载管理库要实现什么:

    1. 图片类型:首先你的主要职责是加载图片,你起码能加载多种图片类型
    2. 下载管理:在加载多张图片的场景,能管理好多个请求,可以控制图片加载的优先级
    3. 缓存管理:做好三级缓存,不能每个图片都要请求网络,均衡好内存缓存和磁盘缓存的策略
    4. 多图加载:大量图片同时渲染时,如何让图片迅速加载,减少卡顿

    针对上面的 4 条原则,我们来一一刨析 Image 组件。

    1.图片类型

    基础的 png/jpg/base64/gif 格式,支持良好。不过要注意的是,想要 Android 加载的 gif 图片动起来,要在 build.gradle 里面加一些依赖,具体内容可以看这个 🔗 链接

    如果要加载 webp 格式的图片,就有些问题了。作为 Google 推出的一种图片格式,Android 自然是支持的,但是 iOS 就不支持了,需要我们安装一些第三方插件。

    2.下载管理

    先说结论,Image 组件对图片的下载管理能力基本为 0。

    Image基本上只能监听单张图片的加载流程:onLoadStart/onLoad/onLoadEnd/onError,如果要控制多张图片的下载优先级,对不起,没有。

    3.缓存管理

    缓存这里要从两方面说,一是通过 HTTP 头信息管理缓存,二是直接通过一些组件属性管理缓存。

    Image 组件请求网络图片时,其实是可以加 HTTP header 头信息的,这样就可以利用 HTTP 缓存来管理图片,写法如下面代码所示:

    <Image
      source={{
        uri: 'https://facebook.github.io/react/logo-og.png',
        method: 'POST',
        headers: {
          Pragma: 'no-cache',
        },
        body: 'Your Body goes here',
      }}
      style={{width: 400, height: 400}}
    />

    具体的控制参数可以参考 🔗 MDN HTTP 缓存,这里就不细说了。

    直接通过属性控制图片缓存,iOS 有。Android?对不起,没有。

    iOS 可以通过 source 参数里的 cache 字段控制缓存,属性也是非常常见的那几种:默认/不使用缓存/强缓存/只使用缓存。具体的使用可以看 🔗 iOS Image 缓存文档

    4.多图加载

    都快到 5G 时代了,短视频/VLog 大家都天天刷了,更不用说多图场景了,基本上已经是互联网应用的标配了。

    讲图片加载前先明确一个概念:图片文件大小 != 图片加载到内存后的大小

    我们常说的 jpg png webp,都是原图压缩后的文件,利于磁盘存储和网络传播,但是在屏幕上展示出来时,就要恢复为原始尺寸了。

    React Native 性能优化——图片内存优化

    比如说一张 100x100 的 jpg 图片,可能磁盘空间就几 kb,不考虑分辨率等问题,加载到内存里,就要占用 3.66 Mb。

    // 不同的分辨率/文件夹/编码格式,都会带来数值差异
    // 下面的计算只是最一般的场景,领会精神即可
    
    (100 * 100 * 3) / (8 * 1024) = 3.66 Mb
    (长 * 宽 * 每个像素占用字节数) / (8 * 1024) = 3.66 Mb

    上面只是 100x100 的图片,如果图片尺寸增加一倍,图片在内存里的大小是按平方倍数增长的,数量一多后,内存占用还是很恐怖的。

    在多图加载的场景里,经过实践,iOS 不管怎么折腾,表现都比较好,但是 Android 就容易出幺蛾子。下面我们就详细说说 Android 端如何优化图片。

    在一些场景里,Android 会内存爆涨,帧率直接降为个位数。这种场景往往是小尺寸 Image 容器加载了特别大的图片,比如说 100x100 的容器加载 1000x1000 的图片,内存爆炸的原因就是上面说的原因。

    那么这种问题怎么解决呢?Image 有个 resizeMethod 属性,就是解决 Android 图片内存暴涨的问题。当图片实际尺寸和容器样式尺寸不一致时,决定以怎样的策略来调整图片的尺寸。

    • resize小容器加载大图的场景就应该用这个属性。原理是在图片解码之前,会用算法对其在内存中的数据进行修改,一般图片大小大概会缩减为原图的 1/8。
    • scale:不改变图片字节大小,通过缩放来修改图片宽高。因为有硬件加速,所以加载速度会更快一些。
    • auto:文档上说是通过启发式算法自动切换 resize 和 scale 属性。这个启发式算法非常误导人,第一眼看上去还以为是会对比容器尺寸和图片尺寸采用不同策略。但我看了一下源码,它只是单纯的判断图片路径,如果是本地图片,就会用 resize,其他都是 scale 属性,所以 http 图片都是 scale 的,我们还得根据具体场景手动控制。

    顺便提一下,Android 图片加载的时候,还会有一个 easy-in 的 300ms 加载动画效果,看上去会觉得图片加载变慢了,我们可以通过设置 fadeDuration 属性为 0,来关闭这个加载动画。

    2️⃣ 优先使用 32 位色彩深度的图片

    📄 色彩深度 wiki:https://github.com/DylanVann/react-native-fast-image/blob/master/README.md

    色彩深度这个概念其实前面也提了一下,比如说我们常用的带透明度 PNG 图片,就是 32 位的:

    • R:红色,占据 8 bit
    • G:绿色,占据 8 bit
    • B:蓝色,占据 8 bit
    • A:透明通道,占据 8 bit

    为啥推荐使用 32 bit 图片呢?直接原因有 2 个:

    1. Android 推荐使用 🔗 ARGB_8888 格式的图片,因为这种图片显示效果更好
    2. iOS GPU 只支持加载 32 bit 的图片。如果是其他格式的(比如说 24 bit 的 jpg),会先在 CPU 里转为 32 bit,再传给 GPU

     

    虽然推荐 32 bit 图片,但是说实话,这个对前端开发是不可控的,因为图片来源一般就 2 个:

    1. 设计师的切图,由设计师控制
    2. 网络上的图片,由上传者控制

    所以想针对这一点进行优化的话,沟通成本挺高,收益反而不高(一般只在长列表有些问题),但也是图片优化的一个思路,故放在这一节里。

    3️⃣ Image 和 ImageView 长宽保持一致

    前面举了一个 100x100 的 ImageView 加载 1000x1000 Image 导致 Android 内存 OOM 的问题,我们提出了设置 resizeMethod={'resize'} 的方法来缩减图片在内存中的体积。其实这是一种无奈之举,如果可以控制加载图片的大小,我们应该保持 Image 和 ImageView 长宽一致。

    首先我们看看长宽不一致会引起的问题:

    • Image 小于 ImageView:图片不清晰,表情包电子包浆质感
    • Image 大于 ImageView:浪费内存,有可能会引起 OOM
    • 尺寸不一致会带来抗锯齿计算,增加了图形处理负担

    React Native 开发时,布局使用的单位是 pt,和 px 存在一个倍数关系。在加载网络图片时,我们可以使用 React Native 的 🔗 PixelRatio.getPixelSizeForLayoutSize 方法,根据不同的分辨率加载不同尺寸的图片,保证 Image 和 ImageView 长宽一致。

    4️⃣ 使用 react-native-fast-image

    📄 react-native-fast-image 文档:https://github.com/DylanVann/react-native-fast-image/blob/master/README.md

    经过上面的几个 Image 属性分析,综合来看,Image 组件对图片的管理能力还是比较弱的,社区上有个 Image 组件的替代品:react-native-fast-image

    它的底层用的是 🔗 iOS 的 SDWebImage 和 🔗 Android 的 Glide。这两个明星图片下载管理库,原生开发同学肯定很熟悉,在缓存管理,加载优先级和内存优化上都有不错的表现。而且这些属性都是双平台可用,这个库都封装好了,但是官网上只有基础功能的安装和配置,如果想引入一些功能(比如说支持 WebP),还是需要查看 SDWebImage 和 Glide 的文档的。

    引入前我还是想提醒一下,React Native 的 Android Image 组件底层封装了 FaceBook 的 Fresco,引入这个库相当于又引入了 Glide,包体积不可避免的会变大,所以引入之前可能还要均衡一下。

    5️⃣ 图片服务器辅助

    前面说的都是从 React Native 侧优化图片,但是一个产品从来不是单打独斗,借助服务端的力量其实可以省很多事。

    1.使用 WebP

    WebP 的优势不用我多说,同样的视觉效果,图片体积会明显减少。而且可以显著减小 CodePush 热更新包的体积(热更新包里,图片占用 90% 以上的体积)。

    虽然 WebP 在前端解压耗时可能会多一点点,但是考虑到传输体积缩小会缩短网络下载时间,整体的收益还是不错的。

    2.图床定制图片

    一般比较大的企业都有内建图床和 CDN 服务,会提供一些自定制图片的功能,比如说指定图片宽高,控制图片质量。当然一些比较优秀的第三方对象存储也提供这些功能,比如说🔗 七牛云 图片处理

    借用云端图片定制功能,前端可以轻松通过控制 URL 参数控制图片属性

    比如说 Android 通过 resizeMethod 的 resize 更改图片字节大小,虽然也可以解决问题,但是这个算法还是在前端运行的,还是会占用用户内存资源。我们把链接改成:

    https://www.imagescloud.com/image.jpg/0/w/100/h/100/q/80
    // w: 长为 100 px
    // h: 宽最多为 100 px
    // q: 压缩质量为 80

    这样子就可以把计算转移到服务端,减少前端的 CPU 占用,优化前端整体的性能。

     

    四、对象创建调用分离

    对象创建和调用分离,其实更多的是一种编码习惯。

    我们知道在 JavaScript 里,啥都是对象,而在 JS 引擎里,创建一个对象的时间差不多是调用一个已存在对象的 10 多倍。在绝大部分情况下,这点儿性能消耗和时间消耗根本不值一提。但在这里还是要总结一下,因为这个思维习惯还是很重要的。

    1️⃣ public class fields 语法绑定回调函数

    📄 文档:https://zh-hans.reactjs.org/docs/handling-events.html

    作为一个前端应用,除了渲染界面,另一个重要的事情就是处理用户交互,监听各种事件。所以在组件上绑定各种处理事件也是一个优化点。

    在 React 上如何处理事件已经是个非常经典的话题了,我搜索了一下,从 React 刚出来时就有这种文章了,动不动就是四五种处理方案,再加上新出的 Hooks,又能玩出更多花样了。

    最常见的绑定方式应该是直接通过箭头函数处理事件:

    class Button extends React.Component {
      handleClick() {
        console.log('this is:', this);
      }
    
      render() {
        return <button onClick={(e) => this.handleClick(e)}>Click me</button>;
      }
    }

    但这种语法的问题是每次 Button 组件重新渲染时,都会创建一个 handleClick() 函数,当 re-render 的次数比较多时,会对 JS 引擎造成一定的垃圾回收压力,会引起一定的性能问题。

    🔗 官方文档里比较推荐开发者使用 🔗 public class fields 语法 来处理回调函数,这样的话一个函数只会创建一次,组件 re-render 时不会再次创建:

    class Button extends React.Component {
      // 此语法确保 handleClick 内的 this 已被绑定。
      handleClick = () => {
        console.log('this is:', this);
      }
    
      render() {
        return <button onClick={this.handleClick}>Click me</button>;
      }
    }

    在实际开发中,经过一些数据对比,因绑定事件方式的不同引起的性能消耗基本上是可以忽略不计的,re-render 次数过多才是性能杀手。但我认为这个意识还是有的,毕竟从逻辑上来讲,re-render 一次就要创建一个新的函数是真的没必要。

    2️⃣ public class fields 语法绑定渲染函数

    这个其实和第一个差不多,只不过把事件回调函数改成渲染函数,在 React Native 的 Flatlist 中很常见。

    很多新人使用 Flatlist 时,会直接向 renderItem 传入匿名函数,这样每次调用 render 函数时都会创建新的匿名函数:

    render(){
      <FlatList
        data={items}
        renderItem={({ item }) => <Text>{item.title}</Text>}
      />
    }

    改成 public class fields 式的函数时,就可以避免这个现象了:

    renderItem = ({ item }) => <Text>{item.title}</Text>;
    
    render(){
      <FlatList
        data={items}
        renderItem={renderItem}
      />
    }

    同样的道理,ListHeaderComponent 和 ListFooterComponent 也应该用这样写法,预先传入已经渲染好的 Element,避免 re-render 时重新生成渲染函数,造成组件内部图片重新加载出现的闪烁现象。

    3️⃣ StyleSheet.create 替代 StyleSheet.flatten

    📄 文档:https://reactnative.cn/docs/stylesheet/

    StyleSheet.create 这个函数,会把传入的 Object 转为优化后的 StyleID,在内存占用和 Bridge 通信上会有些优化。

    const styles = StyleSheet.create({
      item: {
        color: 'white',
      },
    });
    
    console.log(styles.item) // 打印出的是一个整数 ID

    在业务开发时,我们经常会抽出一些公用 UI 组件,然后传入不同的参数,让 UI 组件展示不一样的样式。

    为了 UI 样式的灵活性,我们一般会使用 StyleSheet.flatten,把通过 props 传入自定义样式和默认样式合并为一个样式对象:

    const styles = StyleSheet.create({
      item: {
        color: 'white',
      },
    });
    
    StyleSheet.flatten([styles.item, props.style]) // <= 合并默认样式和自定义样式

    这样做的好处就是可以灵活的控制样式,问题就是使用这个方法时,会🔗 递归遍历已经转换为 StyleID 的样式对象,然后生成一个新的样式对象。这样就会破坏 StyleSheet.create 之前的优化,可能会引起一定的性能负担。

    当然本节不是说不能用 StyleSheet.flatten通用性和高性能不能同时兼得,根据不同的业务场景采取不同的方案才是正解。

    4️⃣ 避免在 render 函数里创建新数组/对象

    我们写代码时,为了避免传入 [] 的地方因数据没拿到传入 undefined,经常会默认传入一个空数组:

    render() {
      return <ListComponent listData={this.props.list || []}/>
    }

    其实更好的做法是下面这样的:

    const EMPTY_ARRAY = [];
    
    render() {
        return <ListComponent listData={this.props.list || EMPTY_ARRAY}/>
    }
    

    这个其实算不上啥性能优化,还是前面再三强调的思路:对象创建和调用分离。毕竟每次渲染的时候重新创建一个空的数组/对象,能带来多大的性能问题?

    把 [] 改为统一的 EMPTY_ARRAY 常量,其实和日常编码中避免出现 Magic Number 一样,算一种编程习惯,但我觉得这种优化可以归到这个类别里,所以专门提一下。

     

    五、动画性能优化

    动画流畅很简单,在大部分的设备上,只要保证 60fps 的帧率就可以了。但要达到这个目标,在 React Native 上还是有些问题的,我画了一张图,描述了目前 React Native 的基础架构(0.61 版本)。

    • UI Thread:在 iOS/Android 上专门绘制 UI 的线程
    • JS Thread:我们写的业务代码基本都在这个线程上,React 重绘,处理 HTTP 请求的结果,磁盘数据 IO 等等
    • other Thread:泛指其他线程,比如说数据请求线程,磁盘 IO 线程等等

    上图我们可以很容易的看出,JS 线程太忙了,要做的事情太多了。而且 UI Thread 和 JS Thread 之前通信是异步的(Async Bridge),只要其它任务一多,就很难保证每一帧都是及时渲染的。

    分析清楚了,React Native 动画优化的方向自然而然就出来了:

    • 减少 JS Thread 和 UI Thread 之间的异步通信
    • 尽量减少 JS Thread 侧的计算

    1️⃣ 开启 useNativeDrive: true

    📄 文档:https://facebook.github.io/react-native/docs/animations#using-the-native-driver

    JS Thread 和 UI Thread 之间是通过 JSON 字符串传递消息的。对于一些可预测的动画,比如说点击一个点赞按钮,就跳出一个点赞动画,这种行为完全可以预测的动画,我们可以使用 useNativeDrive: true 开启原生动画驱动。

    通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在 UI 线程执行动画,而不用每一帧都在两端间来回沟通。如此一来,动画一开始就完全脱离了 JS 线程,因此此时即便 JS 线程被卡住,也不会影响到动画了。

    使用也很简单,只要在动画开始前在动画配置中加入 useNativeDrive: true 就可以了:

    Animated.timing(this.state.animatedValue, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true // <-- 加上这一行
    }).start();

    开启后所有的动画都会在 Native 线程运行,动画就会变的非常丝滑顺畅。

    经过各种暴力测试,使用原生驱动动画时,基本没有掉帧现象,但是用 JS 驱动动画,一旦操作速度加快,就会有掉帧现象。

    值得注意的是,useNativeDriver 这个属性也有着局限性,只能使用到只有非布局相关的动画属性上,例如 transform 和 opacity。布局相关的属性,比如说 height 和 position 相关的属性,开启后会报错。而且前面也说了,useNativeDriver 只能用在可预测的动画上,比如说跟随手势这种动画,useNativeDriver 就用不了的。

    2️⃣ 使用 setNativeProps

    📄 文档:https://facebook.github.io/react-native/docs/direct-manipulation

    setNativeProps 这个属性,相当于直接操作浏览器的 DOM。React 官方一般是不推荐直接操作 DOM 的,但业务场景千变万化,总会遇到一些场景不得不操作 DOM,在React Native 里也是同样的道理。

    比如说下面的动图,在屏幕中上下滚动时,y 轴上的偏移可以通过 ScrollView#onScroll 属性开启 useNativeDrive: true 来优化滚动体验。但是我们可以看到,随着上下滑动,圆圈里的数字也是随之变化的。

    如果把数字存在 this.state 里, 每次滑动不可避免的要进行大量的 setState,React 端会进行大量的重绘操作,可能会引起掉帧。我们这里就可以用 setNativeProps,避免 React 端重绘,相当于直接修改 DOM 上的数字,这样可以让动画更加流畅。

    3️⃣ 使用 InteractionManager

    📄 文档:https://facebook.github.io/react-native/docs/interactionmanager

    原生应用感觉如此流畅的一个重要原因就是在互动和动画的过程中避免繁重的操作。

    在 React Native 里,JS 线程太忙了,啥都要干,我们可以把一些繁重的任务放在 InteractionManager.runAfterInteractions() 里,确保在执行前所有的交互和动画都已经处理完毕。

    InteractionManager.runAfterInteractions(() => {
      // ...需要长时间同步执行的任务...
    });

    在 React Native 官方提供的组件里,PanResponder、Animated,VirtualizedList 都用了 InteractionManager,为的就是平衡复杂任务和交互动画之间的执行时机。

    4️⃣ 使用 react-native-reanimated 和 react-native-gesture-handler

    📺 视频教程:https://www.youtube.com/channel/UC806fwFWpiLQV5y-qifzHnA

    📄 react-native-gesture-handler 文档:https://github.com/software-mansion/react-native-gesture-handler

    📄 react-native-reanimated 文档:https://github.com/software-mansion/react-native-reanimated

    这两个库是被 Youtube 一个自由软件开发者博主 🔗 William Candillon 安利的,后面查了一下,也是 Expo 默认内置动画库和手势库。

    这两个库目的就是替代 React Native 官方提供的🔗 手势库🔗 动画库,除了 API 更加友好,我认为最大的优势是:手势动画是在 UI Thread 运行的

    我们在前面也说了,useNativeDrive: true 这个属性,只能用在可预测的动画上。跟随手势的动画,是无法使用这个属性的,所以手势捕捉和动画,都是在 JS 侧动态计算的。

    我们举一个简单的例子:小球跟随手势移动

    我们先看看 React Native 官方提供的手势动画,可以看到 JS Thread 有大量的计算,计算结果再异步传输到 UI Thread,稍微有些风吹草动,就会引起掉帧。

    如果使用 react-native-gesture-handler,手势捕捉和动画都是 UI Thread 进行的,脱离 JS Thread 计算和异步线程通信,流畅度自然大大提升:

    所以说,如果要用 React Native 构建复杂的手势动画,使用 react-native-gesture-handler 和 react-native-reanimated,是一个不错的选择,可以大幅度提高动画的流畅度。

    5️⃣ 使用 BindingX

    📄 BindingX 文档:https://alibaba.github.io/bindingx/guide/cn_introduce

    BindingX 是阿里开源的一个框架,用来解决 weex和 React Native上富交互问题,核心思路是将"交互行为"以表达式的方式描述,并提前预置到 Native,避免在行为触发时 JS 与 Native 的频繁通信。

    当然,引入上面几个第三方库会肯定会带来一定的学习成本。对于复杂交互的页面,有的团队可能会采用原生组件来代替,比如说🔗 美团外卖就会用原生组件去实现精细动画和强交互模块,所以具体使用还要看团队的技术储备和 APP 场景。

     

    六、长列表性能优化

    在 React Native 开发中,最容易遇到的对性能有一定要求场景就是长列表了。在日常业务实践中,优化做好后,千条数据渲染还是没啥问题的。

    虚拟列表前端一直是个经典的话题,核心思想也很简单:只渲染当前展示和即将展示的 View,距离远的 View 用空白 View 展示,从而减少长列表的内存占用。

    在 React Native 官网上,🔗 列表配置优化其实说的很好了,我们基本上只要了解清楚几个配置项,然后灵活配置就好。但是问题就出在「了解清楚」这四个字上,本节我会结合图文,给大家讲述清楚这几个配置。

    1️⃣ 各种列表间的关系

    React Native 有好几个列表组件,先简单介绍一下:

    • ScrollView:会把视图里的所有 View 渲染,直接对接 Native 的滚动列表
    • VirtualizedList:虚拟列表核心文件,使用 ScrollView,长列表优化配置项主要是控制它
    • FlatList:使用 VirtualizedList,实现了一行多列的功能,大部分功能都是 VirtualizedList 提供的
    • SectionList:使用 VirtualizedList,底层使用 VirtualizedSectionList,把二维数据转为一维数据

    还有一些其他依赖文件,有个🔗 博文的图总结的挺好的,我这里借用它的图一下:

    我们可以看出 VirtualizedList 才是主演,下面我们结合一些示例代码,分析它的配置项。

    2️⃣ 列表配置项

    讲之前先写个小 demo。demo 非常简单,一个基于 FlatList 的奇偶行颜色不同的列表。

    export default class App extends React.Component {
      renderItem = item => {
        return (
          <Text
            style={{
              backgroundColor: item.index % 2 === 0 ? 'green' : 'blue',
            }}>
            {'第 ' + (item.index + 1) + ' 个'}
          </Text>
        );
      }
    
      render() {
        let data = [];
        for (let i = 0; i < 1000; i++) {
          data.push({key: i});
        }
    
        return (
          <View style={{flex: 1}}>
            <FlatList
          data={data}
              renderItem={this.renderItem}
              initialNumToRender={3} // 首批渲染的元素数量
              windowSize={3} // 渲染区域高度
              removeClippedSubviews={Platform.OS === 'android'} // 是否裁剪子视图
          maxToRenderPerBatch={10} // 增量渲染最大数量
              updateCellsBatchingPeriod={50} // 增量渲染时间间隔
              debug // 开启 debug 模式
            />
          </View>
        );
      }
    }

    VirtualizedList 有个 debug 的配置项,开启后会在视图右侧显示虚拟列表的显示情况。

    这个属性文档中没有说,是翻🔗 源码发现的,我发现开启它后用来演示讲解还是很方便的,可以很直观的学习 initialNumToRender、windowSize、Viewport,Blank areas 等概念。

    下面是开启 debug 后的 demo 截屏:

    上面的图还是很清晰的,右侧 debug 指示条的黄色部分表示内存中 Item,各个属性我们再用文字描述一下:

    1.initialNumToRender

    首批应该渲染的元素数量,刚刚盖住首屏最好。而且从 debug 指示条可以看出,这批元素会一直存在于内存中。

    2.Viewport

    视口高度,就是用户能看到内容,一般就是设备高度。

    3.windowSize

    渲染区域高度,一般为 Viewport 的整数倍。这里我设置为 3,从 debug 指示条可以看出,它的高度是 Viewport 的 3 倍,上面扩展 1 个屏幕高度,下面扩展 1 个屏幕高度。在这个区域里的内容都会保存在内存里。

    将 windowSize 设置为一个较小值,能有减小内存消耗并提高性能,但是快速滚动列表时,遇到未渲染的内容的几率会增大,会看到占位的白色 View。大家可以把 windowSize 设为 1 测试一下,100% 会看到占位 View。

    4.Blank areas

    空白 View,VirtualizedList 会把渲染区域外的 Item 替换为一个空白 View,用来减少长列表的内存占用。顶部和底部都可以有。

    上图是渲染图,我们可以利用 react-devtools 再看看 React 的 Virtual DOM(为了截屏方便,我把 initialNumToRender 和 windowSize 设为 1),可以看出和上面的示意图是一致的。

    5.removeClippedSubviews

    这个翻译过来叫「裁剪子视图」的属性,文档描述不是很清晰,大意是设为 true 可以提高渲染速度,但是 iOS 上可能会出现 bug。这个属性 VirtualizedList 没有做任何优化,是直接透传给 ScrollView 的。

    在 0.59 版本的一次 🔗 commit 里,FlatList 默认 Android 开启此功能,如果你的版本低于 0.59,可以用以下方式开启:

    removeClippedSubviews={Platform.OS === 'android'}

    6.maxToRenderPerBatch 和 updateCellsBatchingPeriod

    VirtualizedList 的数据不是一下子全部渲染的,而是分批次渲染的。这两个属性就是控制增量渲染的。

    这两个属性一般是配合着用的,maxToRenderPerBatch 表示每次增量渲染的最大数量,updateCellsBatchingPeriod 表示每次增量渲染的时间间隔

    我们可以调节这两个参数来平衡渲染速度和响应速度。但是,调参作为一门玄学,很难得出一个统一的「最佳实践」,所以我们在业务中也没有动过这两个属性,直接用的系统默认值。

    3️⃣ ListLtems 优化

    📄 ListLtems 优化 文档:https://reactnative.cn/docs/optimizing-flatlist-configuration/#list-items

    文档中说了好几点优化,其实在前文我都介绍过了,这里再简单提一下:

    1.使用 getItemLayout

    如果 FlatList(VirtualizedList)的 ListLtem 高度是固定的,那么使用 getItemLayout 就非常的合算。

    在源码中(#L1287#L2046),如果不使用 getItemLayout,那么所有的 Cell 的高度,都要调用 View 的 onLayout 动态计算高度,这个运算是需要消耗时间的;如果我们使用了 getItemLayout,VirtualizedList 就直接知道了 Cell 的高度和偏移量,省去了计算,节省了这部分的开销。

    在这里我还想提一下几个注意点,希望大家使用 getItemLayout 要多注意一下:

    • 如果 ListItem 高度不固定,使用 getItemLayout 返回固定高度时,因为最终渲染高度和预测高度不一致,会出现页面跳动的问题【🔗 问题链接
    • 如果使用了 ItemSeparatorComponent,分隔线的尺寸也要考虑到 offset 的计算中【🔗 文档链接
    • 如果 FlatList 使用的时候使用了 ListHeaderComponent,也要把 Header 的尺寸考虑到 offset 的计算中【🔗 官方示例代码链接

    2.Use simple components & Use light components

    使用简单组件,核心就是减少逻辑判断和嵌套,优化方式可以参考「二、减轻渲染压力」的内容。

    3.Use shouldComponentUpdate

    参考「一、re-render」的内容。

    4.Use cached optimized images

    参考「三、图片优化那些事」的内容。

    5.Use keyExtractor or key

    常规优化点了,可以看 React 的文档 🔗 列表 & Key

    6.Avoid anonymous function on renderItem

    renderItem 避免使用匿名函数,参考「四、对象创建调用分离」的内容。

     

    七、React Native 性能优化用到的工具

    性能优化工具,本质上还是调试工具的一个子集。React Native 因为它的特殊性,做一些性能分析和调试时,需要用到 RN/iOS/Android 三端的工具,下面我就列举一下我平常用到的工具,具体的使用方法不是本文的重点,如有需要可根据关键词自行搜索。

    1.React Native 官方调试工具

    这个官网说的很清楚了,具体内容可见🔗 直达链接

    2.react-devtools

    React Native 是跑在原生 APP 上的,布局查看不能用浏览器插件,所以要用这个基于 Electron 的 react-devtools。写本文时 React Native 最新版本还是 0.61,不支持最新 V4 版本的 react-devtools,还得安装旧版本。具体安装方法可见这个🔗 链接

    3.XCode

    iOS 开发 IDE,查看分析性能问题时可以用 instruments 和 Profiler 进行调试。

    4.Android Studio

    Android 开发 IDE,查看性能的话可以使用 Android Profiler🔗 官方网站写的非常详细。

    5.iOS Simulator

    iOS 模拟器,它的 Debug 可以看一些分析内容。

    6.Android 真机 -> 开发者选项

    Android 开发者选项有不少东西可看,比如说 GPU 渲染分析和动画调试。真机调试时可以开启配合使用。

     

    八、推荐阅读

    【React Native 性能优化指南】到此就算写完了,文中内容可能有不严谨 or 错误的地方,请各位前端/iOS/Android 大佬多多指教。

    全文参考近 50 个链接,全放文末太占篇幅了,所以我都分散在文章各处了,我以 emoji 表情🔗标记的方式进行提示,大家有疑惑的地方可以去原文查看。

    在此我还要推荐一下我以前写的关于 Webpack 的文章,两篇都是全网独创

    展开全文
  • React Native / React调试技巧 做过原生APP开发的同学们都清楚,我们在Xcode和studio中就可以直接对编写的代码进行断点调试,很方便,但是web开发断点调试就不能直接在开发工具中完成了,需要借助浏览器来完成,...
  • React Native学习心得

    2019-04-15 18:24:37
    结合网址进行学习:https://reactnative.cn/ 讨论社区:https://www.facebook.com/groups/react.native.community React Native - 调试技巧及调试菜单说明(模拟器调试、真机调试):...
  • React Native 充分利用了 Facebook 的现有轮子,是一个很优秀的集成作品,使用 RN 即可做到无需编译就能远程热更新 App,再加上友好的开发方式、快到爆炸的开发效率、RN 已经完爆了其他的 App 开发方式,即使是一个...
  • 关掉node后台进程,用npm start -- --reset-cache命令重新启动packager服务
  • 携程React Native实践

    2016-11-10 09:28:01
    React Native(下文简称 RN)开源已经一年多时间,国内各大互联网公司都在使用,携程也在今年 5 月份投入资源开始引入,并推广给多个业务团队使用,本文将会分享我们遇到的一些问题以及我们的优化方案。一、背景和...
  • react native 红屏

    2018-01-22 01:30:26
    最经在学习react native时出现红屏的问题 大致问题是找不到index.android的问题 执行代码为react-native run-android 其实问题出在你的硬件设备没有连接本地开发的node服务 官网的说法是执行adb reverse tcp:...
  • 如果使用React Native开发上面的效果,需要注意几个问题: 1、 在下拉的时候有动画过度效果; 2、下拉菜单出现后点击菜单项,菜单项可选择,并触发对应的事件; 3、下拉菜单中的项目可以配置;要实现弹框效果,...
  • React Native官方文档:https://reactnative.cn/docs/getting-started/ 项目GitHub地址:https://github.com/zhouwei1994/nativeCase.git 在写自定义Toast弹窗之前我们要先创建一个React Native第二视图层。 ...
  • 1. 服务端返回500 解决方案 方案1:使用 yarn 工具 $ yarn remove babel-preset-react-native $ yarn add babel-preset-react-native@2.1.0 方案2:使用 npm 工具 $ npm uninstall --save-dev babel-...
  • React Native开源已经接近2年时间,京东、携程、58同城等互联网公司都在使用,公司于今年也开始使用,并推广到各个新项目。本文重点分享我们遇到的一些问题以及优化方案。 一、为什么会引入React Native? 1. APP...
  • 解决方法: 1.将babel-preset-react-native的版本改为:2.1.0 2.运行 :npm install 3.运行:react-native run-android
  • ReactNative资料全

    2017-02-13 09:18:09
    React Native优秀博客,以及优秀的Github库列表(很多英文资料源自于awesome-react-native) https://github.com/LeoMobileDeveloper/ReactNativeMaterials#%E4%B8%AD%E6%96%87%E5%8D%9A%E5%AE%A2 关于开源...
  • react native Windows红屏报错500 -react-native run-android出现 这个内容其实很简单,这个主要是兼容的问题,因为react native 是Facebook使用苹果操作系统来完成的所以Windows的兼容不是很好现在当下版本为...
1 2 3 4 5 ... 20
收藏数 2,739
精华内容 1,095
热门标签