美文网首页我爱编程
RN实战经验总结

RN实战经验总结

作者: 大斜丸 | 来源:发表于2018-06-21 14:47 被阅读229次

    title: RN实战经验总结

    前言

    在草稿箱中发现了许久之前写的这篇文章,虽然不搞RN已经大半年了,但是之前写过的东西还是还出来作个纪念。如果能帮到别人那再好不过了。

    Android中集成RN

    这个需要说的都在这里说了,见在原有Android项目中快速集成React Native

    关于RN在项目中Android端的预加载

    此前曾根据网上的做法并结合最新的RN源码做了一个RN的预加载库,不过在后来发现会出现内存泄漏问题。在集成到项目Android端此着手解决了这一问题。
    然而在实际的开发中,几乎有大半的页面是用RN开发,如果全部页面都使用预加载,那么对内存会有很大的压力,而且也没有这个必要。
    首先说一下目前项目的页面组织结构,其实就是目前主流的主Activity(带四个Fragment)+其他Activity,主Activity在应用运行期间是一直存在的,这就为预加载提供了一个绝佳的基础。
    最终使用预加载的是主Activity【我的】Fragment页面。在RN中加载Fragment并不难,在Android中加载RN,无论是在Activity还是Fragment,加载的都只是一个View而已。而给Fragment设置View,只需要Fragment的onCreateView返回RN的View即可。
    具体见:在Android中预加载React Native jsBundle

    优化非预加载初始化属性传递

    在原本的ReactActivity中传递启动属性可以用以下方式

    public class C3RNActivity extends ReactActivity {
        public static final String MAIN_COMPONENT_NAME = C3RNActivity.class.getSimpleName();
    
        protected @Nullable
        String getMainComponentName() {
            return MAIN_COMPONENT_NAME;
        }
    
        @Override
        protected ReactActivityDelegate createReactActivityDelegate() {
            return new ReactActivityDelegate(this, getMainComponentName()) {
                @Nullable
                @Override
                protected Bundle getLaunchOptions() {
                    Bundle bundle=new Bundle();
                    //往bundle中添加启动属性键值对
                    bundle.putString("key","value");
                    return bundle;
                }
            };
        }
    }
    

    这种方式传递是完全没问题的,但是有点局限性。查看ReactActivity的源码,createReactActivityDelegate是在ReactActivity的构造方法调用(在OnCreate之前)。但这样一来就无法在OnCreate通过getItent获取别的Activity传递过来的参数,因此我们需要对原本的ReactActivity进行改造。将createReactActivityDelegate方法调用从ReactActivity移到onCreate方法中,但是在ReactActivityDelegate的onCreate方法之前。这样我们需要重写ReactActivity而不是直接通过继承创建满足我们要求的ReactActivity。

    public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
    
        private MyReactActivityDelegate mDelegate;
    
        /**
         * Returns the name of the main component registered from JavaScript.
         * This is used to schedule rendering of the component.
         * e.g. "MoviesApp"
         */
        protected @Nullable
        String getMainComponentName() {
            return null;
        }
    
        /**
         * Called at construction time, override if you have a custom delegate implementation.
         */
        protected MyReactActivityDelegate createReactActivityDelegate(final Intent intent) {
            return new MyReactActivityDelegate(this, getMainComponentName()){
                @Nullable
                @Override
                protected Bundle getLaunchOptions() {
                    Bundle bundle=new Bundle();
                    //在这里将intent参数放入bundle,作为RN的页面启动参数
                    //例如:
                    bundle.putString("key",intent.getStringExtra("xxx"));
                    return bundle;
                }
            };
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Intent intent=getIntent();
            mDelegate = createReactActivityDelegate(intent);
            mDelegate.onCreate(savedInstanceState);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            mDelegate.onPause();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            mDelegate.onResume();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mDelegate.onDestroy();
        }
    
        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            mDelegate.onActivityResult(requestCode, resultCode, data);
        }
    
        @Override
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
        }
    
        @Override
        public void onBackPressed() {
            if (!mDelegate.onBackPressed()) {
                super.onBackPressed();
            }
        }
    
        @Override
        public void invokeDefaultOnBackPressed() {
            super.onBackPressed();
        }
    
        @Override
        public void onNewIntent(Intent intent) {
            if (!mDelegate.onNewIntent(intent)) {
                super.onNewIntent(intent);
            }
        }
    
        @Override
        public void requestPermissions(
                String[] permissions,
                int requestCode,
                PermissionListener listener) {
            mDelegate.requestPermissions(permissions, requestCode, listener);
        }
    
        @Override
        public void onRequestPermissionsResult(
                int requestCode,
                String[] permissions,
                int[] grantResults) {
            mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    
        protected final ReactNativeHost getReactNativeHost() {
            return mDelegate.getReactNativeHost();
        }
    
        protected final ReactInstanceManager getReactInstanceManager() {
            return mDelegate.getReactInstanceManager();
        }
    
    }
    

    其中,MyReactActivityDelegate 是直接继承ReactActivityDelegate,因为在ReactActivityDelegate中,onCreate,onPause,onDestroy等方法是protect修饰,无法在其他包中引用,所以需要对其复写,实现中只需要调用父类方法即可。

    多入口

    在本次项目中使用的是多注册方式实现RN的多入口,实际上通过启动属性传递需要打开的RN页面参数也是可以的。不过因为使用多注册实现多入口还是踩了一些坑。在多注册方式下,RN的全局变量在iOS客户端是无效的。也就是说,在一个根组件中给一个全局变量赋值,在另外一个根组件中读取到的全局变量值是空的。而在Android端是没有这个问题。

    网络图片加载过渡

    这里不得不提这是RN的一个坑,最新的RN都发布到0.5x了,在Android中依然没有支持默认占位图,默认加载错误图,以及加载进度方法。这三个都只有在iOS端有效,在Android端则需要自己手动实现。

    网络状况判断

    这里不得不提这又是RN的一个坑。在RN中官网推荐判断网络是否可用的方法如下:
    NetInfo.isConnected.fetch().done(
    (isConnected) => { this.setState({isConnected}); }
    );
    然而实际上在iOS端,isConnected返回的永远是false。

    官方文档说上面这个方法是Android和iOS平台通用的,然而你实际使用的时候在iOS端就会发现问题,即使到了0.51,这个问题仍然存在.....
    其实这个解决办法很多,
    具体可见:Github issue:iOS: NetInfo.isConnected returns always false
    其中一种解决办法如下:

    function handleFirstConnectivityChange(isConnected) {
        if (!sConnected) {
         // do action
        } 
        NetInfo.isConnected.removeEventListener('change', handleFirstConnectivityChange);
    }
    
    if (Platform.OS === 'ios') {
        NetInfo.isConnected.addEventListener('change', handleFirstConnectivityChange); 
    } else {
        NetInfo.isConnected.fetch().then(isConnected => {
        if (!sConnected) {
            // do action
        } 
    }
    

    Linking模块在Android release模式下getInitialURL返回为null

    这也是一个大坑。在RN中,如果你的应用被其注册过的外部url调起,则可以在任何组件内这样获取和处理它:

    componentDidMount() {
    Linking.getInitialURL().then((url) => {
    if (url) {
    console.log('Initial url is: ' + url);
    }
    }).catch(err => console.error('An error occurred', err));
    }
    然而在Android端打成Release包时,返回的url偶尔会为空,对,是偶尔,并且概率还比较大,原因暂时未知。所以要通过外部链接和Linking模块来打开RN的话,这种做法是不靠谱的。解决办法是用Android原生的老办法,在Activity的onCreate方法中获取外部链接以及相关参数,并作为启动参数传递给RN。为此,需要重写ReactActivity和ReactActivityDelegate。具体参考:优化非预加载初始化属性传递一节。

    总结:

    从17年4月份开始接触RN,至今如有大半年时间,在这大半年时间里,从入门学习到实际动手写出一个完整的仿实际产品的App出来花了一个月时间,与当初学习Android相比这个时间短得太多了。到17年6月份在我们公司的天翼云iOS客户端其中一个页面试点使用RN,然后前后花了一个月时间,但实际动手接入项目中与从零开始一个RN项目有很大的不同,期间踩了好几个坑,还好都能及时解决。到17年10月份,在我们公司的产品两个客户端都接入RN并且是重度使用,大概有50%~60%页面是使用RN开发。在这一次接近2个月的开发过程中,对RN简直又爱又恨,踩了大大小小好多个坑,看到了RN的许多不足,也看到了原生与RN无法比拟的一些优势。
    先来说一下切身体会的优势:

    • 上手快,即使不懂JS,入门也不用太长时间,半个月时间其实就足够了。上手之后,开发效率其实可以很高。
    • 跨平台,这是一个巨大的优势,虽然RN的代码不能做到100%两个端复用,但是90%还是没问题的。而自然地,可以节省一定的人力成本。
    • 热更新,这一功能在Android中实现比较简单,但是因为苹果爸爸禁用了JSPatch,因此在iOS端能用的热更新方法不多了,而热更新则是其中一个。
    • 更新快,这其实是一个优点也可以说是缺点,说它是优点因为勤快地更新则说明RN加了某些新特性或者修复了一些历史遗留的bug,说它是缺点则是因为更新太快,说不定某些API哪天突然就不能用了,代码的写法又不一样了,RN版本升级的时候也是一件比较痛苦的事情。
    • 调试方便:可以在谷歌浏览器上面单步调试JS代码,双击R(或摇一摇或CMD+R)就能快速reload代码

    不足:

    • 开发过程会时不时就踩到坑,RN作为一个还没正式发布1.0版本的框架,有一些bug是必然的。
    • 列表控件性能仍不能满足要求,在快速滑动时会看到一些空白项。
    • 图片缓存:官方没有很好的支持,第三方库也没有找到比较满意的方案。
    • 动画效果不佳:这个众所周知,动画效果需要自己做优化。

    相关文章

      网友评论

        本文标题:RN实战经验总结

        本文链接:https://www.haomeiwen.com/subject/hlixnxtx.html