RN实战经验总结


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实战经验总结)