博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ReactNative开发笔记(持续更新...)
阅读量:5737 次
发布时间:2019-06-18

本文共 21286 字,大约阅读时间需要 70 分钟。

本文均为RN开发过程中遇到的问题、坑点的分析及解决方案,各问题点之间无关联,希望能帮助读者少走弯路,持续更新中... (2019年3月29日更新)

原文链接:

作者:

- 如何在原生端(iOS和android两个平台)使用ReactNative里的本地图片(路径类似require('./xxximage.png'))。

在ReactNative开发过程中,有时需要在原生端显示RN里的图片,这样的好处是可以通过热更新来更新APP里的图片,而不需要发布原生版本,而ReactNative里图片路径是相对路径,类似'./xxximage.png'的写法,原生端是无法解析这类路径,那么如果将RN的图片传递给原生端呢?

解决方案

1、图片如果用网络图,那只需要将url字符串地址传递给原生即可,这种做法需要时间和网络环境加载图片,不属于本地图片,不是本方案所追求的最佳方式。

2、懒人做法是把RN的本地图片生成base64字符串然后传递给原生再解析,这种做法如果图片太大,字符串会相当长,同样不认为是最佳方案。

其实RN提供了相关的解决方法,如下:

RN端

const myImage = require('./my-image.png');const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');const resolvedImage = resolveAssetSource(myImage);NativeModules.NativeBridge.showRNImage(resolvedImage);复制代码

iOS端

#import 
RCT_EXPORT_METHOD(showRNImage:(id)rnImageData){ dispatch_async(dispatch_get_main_queue(), ^{ UIImage *rnImage = [RCTConvert UIImage:rnImageData]; ... });}复制代码

安卓端

第一步,从桥接文件获取到uri地址

@ReactMethodpublic static void showRNImage(Activity activity, ReadableMap params){     String rnImageUri;     try {        //图片地址        rnImageUri = params.getString("uri");        Log.i("Jumping", "uri : " + uri);                ...        } catch (Exception e) {            return;        } } 复制代码

第二步,创建JsDevImageLoader.java

package com.XXX;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;import android.os.StrictMode;import android.support.annotation.NonNull;import android.util.Log;import com.XXX.NavigationApplication;import java.io.IOException;import java.net.URL;public class JsDevImageLoader {   private static final String TAG = "JsDevImageLoader";   public static Drawable loadIcon(String iconDevUri) {       try {           StrictMode.ThreadPolicy threadPolicy = StrictMode.getThreadPolicy();           StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitNetwork().build());           Drawable drawable = tryLoadIcon(iconDevUri);           StrictMode.setThreadPolicy(threadPolicy);           return drawable;       } catch (Exception e) {           Log.e(TAG, "Unable to load icon: " + iconDevUri);           return new BitmapDrawable();       }   }   @NonNull   private static Drawable tryLoadIcon(String iconDevUri) throws IOException {       URL url = new URL(iconDevUri);       Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());       return new BitmapDrawable(NavigationApplication.instance.getResources(), bitmap);   }}复制代码

第三步,导入ResourceDrawableIdHelper.java

package com.xg.navigation.react;// Copyright 2004-present Facebook. All Rights Reserved.import android.content.Context;import android.graphics.drawable.Drawable;import android.net.Uri;import com.facebook.common.util.UriUtil;import java.util.HashMap;import java.util.Map;import javax.annotation.Nullable;/** * Direct copy paste from react-native, because they made that class package scope. -_-" * Can be deleted in react-native ^0.29 */public class ResourceDrawableIdHelper {    public static final ResourceDrawableIdHelper instance = new ResourceDrawableIdHelper();    private Map
mResourceDrawableIdMap; public ResourceDrawableIdHelper() { mResourceDrawableIdMap = new HashMap<>(); } public int getResourceDrawableId(Context context, @Nullable String name) { if (name == null || name.isEmpty()) { return 0; } name = name.toLowerCase().replace("-", "_"); if (mResourceDrawableIdMap.containsKey(name)) { return mResourceDrawableIdMap.get(name); } int id = context.getResources().getIdentifier( name, "drawable", context.getPackageName()); mResourceDrawableIdMap.put(name, id); return id; } @Nullable public Drawable getResourceDrawable(Context context, @Nullable String name) { int resId = getResourceDrawableId(context, name); return resId > 0 ? context.getResources().getDrawable(resId) : null; } public Uri getResourceDrawableUri(Context context, @Nullable String name) { int resId = getResourceDrawableId(context, name); return resId > 0 ? new Uri.Builder() .scheme(UriUtil.LOCAL_RESOURCE_SCHEME) .path(String.valueOf(resId)) .build() : Uri.EMPTY; }}复制代码

第四步,创建BitmapUtil.java

package com.XXX;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;import android.net.Uri;import android.provider.MediaStore;import android.text.TextUtils;import com.XXX.NavigationApplication;import com.XXX.JsDevImageLoader;import com.XXX.ResourceDrawableIdHelper;import java.io.IOException;public class BitmapUtil {    private static final String FILE_SCHEME = "file";    public static Drawable loadImage(String iconSource) {        if (TextUtils.isEmpty(iconSource)) {            return null;        }        if (NavigationApplication.instance.isDebug()) {            return JsDevImageLoader.loadIcon(iconSource);        } else {            Uri uri = Uri.parse(iconSource);            if (isLocalFile(uri)) {                return loadFile(uri);            } else {                return loadResource(iconSource);            }        }    }    private static boolean isLocalFile(Uri uri) {        return FILE_SCHEME.equals(uri.getScheme());    }    private static Drawable loadFile(Uri uri) {        Bitmap bitmap = BitmapFactory.decodeFile(uri.getPath());        return new BitmapDrawable(NavigationApplication.instance.getResources(), bitmap);    }    private static Drawable loadResource(String iconSource) {        return ResourceDrawableIdHelper.instance.getResourceDrawable(NavigationApplication.instance, iconSource);    }    public static Bitmap getBitmap(Activity activity, String uri) {        if (activity == null || uri == null || TextUtils.isEmpty(uri)) {            return null;        }        Uri mImageCaptureUri;        try {            mImageCaptureUri = Uri.parse(uri);        } catch (Exception e) {            e.printStackTrace();            return null;        }        if (mImageCaptureUri == null) {            return null;        }        Bitmap bitmap = null;        try {            bitmap = MediaStore.Images.Media.getBitmap(activity.getContentResolver(), mImageCaptureUri);        } catch (IOException e) {            e.printStackTrace();            return null;        }        return bitmap;    }}复制代码

第五步,使用第一步里的rnImageUri地址

...BitmapUtil.loadImage(rnImageUri)...复制代码

第六步,显示图片

import android.widget.RelativeLayout;import android.support.v7.widget.AppCompatImageView;import android.graphics.drawable.Drawable;...final RelativeLayout item = (RelativeLayout) mBottomBar.getChildAt(i);final AppCompatImageView itemIcon = (AppCompatImageView) item.getChildAt(0);itemIcon.setImageDrawable(BitmapUtil.loadImage(rnImageUri));...复制代码

- 升级旧RN版本到目前最新的0.57.8如果采用手动升级需要注意如下。

I upgraded from react-naitve 0.55.4 to react-native 0.57.0 and I get this error bundling failed: Error: The 'decorators' plugin requires a 'decoratorsBeforeExport' option, whose value must be a boolean. If you are migrating from Babylon/Babel 6 or want to use the old decorators proposal, you should use the 'decorators-legacy' plugin instead of 'decorators'.

解决方案:参考如下例子

First install the new proposal decorators with npm install @babel/plugin-proposal-decorators --save-dev or yarn add @babel/plugin-proposal-decorators --dev

Then, inside of your .babelrc file, change this:

{  "presets": ["react-native"],  "plugins": ["transform-decorators-legacy"]}To this:{  "presets": [    "module:metro-react-native-babel-preset",    "@babel/preset-flow"  ],  "plugins": [    ["@babel/plugin-proposal-decorators", { "legacy" : true }]  ]}复制代码

EDIT:

After you've updated your .babelrc file, make sure to add preset-flow as well with the command yarn add @babel/preset-flow --dev or npm install @babel/preset-flow --save-dev

- ReactNative输入框TextInput点击弹起键盘,如果键盘遮挡了重要位置,如何让界面自动跟随键盘调整?

使用这个组件KeyboardAvoidingView

本组件用于解决一个常见的尴尬问题:手机上弹出的键盘常常会挡住当前的视图。本组件可以自动根据键盘的位置,调整自身的position或底部的padding,以避免被遮挡。

解决方案:参考如下例子

...
...
复制代码

- ReactNative输入框TextInput点击弹起键盘,然后点击其他子组件,例如点击提交按钮,会先把键盘收起,再次点击提交按钮才响应提交按钮,得点击两次,如何做到点击提交按钮的同时收起键盘并响应按钮?

这个问题关键在ScrollViewkeyboardShouldPersistTaps属性 ,首先TextInput的特殊性(有键盘弹起)决定了其最好包裹在ScrollView里,其次如果当前界面有软键盘,那么点击scrollview后是否收起键盘,取决于keyboardShouldPersistTaps属性的设置。(译注:很多人反应TextInput无法自动失去焦点/需要点击多次切换到其他组件等等问题,其关键都是需要将TextInput放到ScrollView中再设置本属性)

  • 'never'(默认值),点击TextInput以外的子组件会使当前的软键盘收起。此时子元素不会收到点击事件。
  • 'always',键盘不会自动收起,ScrollView也不会捕捉点击事件,但子组件可以捕获。
  • 'handled',当点击事件被子组件捕获时,键盘不会自动收起。这样切换TextInput时键盘可以保持状态。多数带有TextInput的情况下你应该选择此项。
  • false,已过期,请使用'never'代替。
  • true,已过期,请使用'always'代替。

解决方案:看如下例子

...
//按钮点击事件注意收起键盘 _checkAndSubmit = () => { Keyboard.dismiss(); };复制代码

- ReactNative本地图片如何获取其base64编码?(一般指采用<Image source={require('./icon.png'.../>这类相对路径地址的图片资源如何获取到绝对路径)

关键是要获取到本地图片的uri,用到了Image.resolveAssetSource方法,ImageEditor.cropImage方法和ImageStore.getBase64ForTag方法,具体可以查询官方文档

解决方案:看如下代码

import item from '../../images/avator_upload_icon.png';      const info = Image.resolveAssetSource(item);      ImageEditor.cropImage(info.uri, {        size: {          width: 126,          height: 126        },        resizeMode: 'cover'      }, uri => {        ImageStore.getBase64ForTag(uri, base64ImageData => {          // 获取图片字节码的base64字符串          this.setState({            avatarBase64: base64ImageData          });        }, err => {          console.warn("ImageStoreError" + JSON.stringify(err));        });      }, err => {        console.warn("ImageEditorError" + JSON.stringify(err));      });复制代码

- ReactNative如何读取iOS沙盒里的图片?

解决方案:看如下代码

let RNFS = require('react-native-fs');        

- ReactNative如何做到图片宽度不变,宽高保持比例,高度自动调整。

RN图片均需要指定宽高才会显示,如果图片数据的宽高不定,但又希望宽度保持不变、不同图片的高度根据比例动态变化,就需要用到下面这个库,业务场景常用于文章、商品详情的多图展示。

解决方案:使用

- navigor 无法使用的解决办法

从0.44版本开始,Navigator被从react native的核心组件库中剥离到了一个名为react-native-deprecated-custom-components的单独模块中。如果你需要继续使用Navigator,则需要先npm i facebookarchive/react-native-custom-components安装,然后从这个模块中import,即import { Navigator } from 'react-native-deprecated-custom-components'

如果报错如下参考下面的解决方案

React-Native – undefined is not an object (“evaluating _react3.default.PropTypes.shape”)

解决方案

如果已经安装了,先卸载npm uninstall --save react-native-deprecated-custom-components

用下面的命令安装 npm install --save https://github.com/facebookarchive/react-native-custom-components.git

在我们使用Navigator的js文件中加入下面这个导入包就可以了。

import { Navigator } from'react-native-deprecated-custom-components';(注意最后有一个分号)

就可以正常使用Navigator组件了。

- ReactNative开发的APP启动闪白屏问题

由于处理JS需要时间,APP启动会出现一闪而过白屏,可以通过启动页延迟加载方法来避免这类白屏,可以用下面的库 解决方案

- ReactNative如何做到无感热更新

无论是整包热更新还是差量热更新,均需要最终替换JSBundle等文件来完成更新过程,实现原理是js来控制启动页的消失时间,等原生把bundle包下载(或合并成新bundle包)解压到目录以后,通知js消失启动页,由于热更新时间一般很短,建议使用差量热更新,一秒左右,所以用户等启动页消失后看到的就是最新的版本。 解决方案(以整包更新为例):

  1. 原生端完成更新及刷新操作,注意里面的[_bridge reload]
//前往更新js包RCT_EXPORT_METHOD(gotoUpdateJS:(NSString *)jsUrl andResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){  if (!jsUrl) {    return;  }    //jsbundle更新采用静默更新  //更新  NSLog(@"jsbundleUrl is : %@",  jsUrl);  [[LJFileHelper shared] downloadFileWithURLString:jsUrl finish:^(NSInteger status, id data) {    if(status == 1){      NSLog(@"下载完成");      NSError *error;      NSString *filePath = (NSString *)data;      NSString *desPath = [NSString stringWithFormat:@"%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]];      [SSZipArchive unzipFileAtPath:filePath toDestination:desPath overwrite:YES password:nil error:&error];      if(!error){        [_bridge reload];        resolve([NSNumber numberWithBool:true]);        NSLog(@"解压成功");              }else{        resolve([NSNumber numberWithBool:false]);        NSLog(@"解压失败");      }    }  }];  reject = nil;}复制代码
  1. JS端
// 原生端通过回调结果通知JS热更新情况,JS端UpdateModule.gotoUpdateJS(jsUrl).then(resp => {    if ( resp ) {       // 成功更新通知隐藏启动页       DeviceEventEmitter.emit("hide_loading_page",'hide');    } else {       // 出问题也要隐藏启动页,用户继续使用旧版本       DeviceEventEmitter.emit("hide_loading_page",'hide');       // 其他处理    }});复制代码
  1. 启动页消失,用户看到的是新版APP
async componentWillMount() {        this.subscription = DeviceEventEmitter.addListener("hide_loading_page", this.hideLoadingPage);        appUpdateModule.updateJs();    }    hideLoadingPage = ()=> {        SplashScreen.hide();    };复制代码

注意做好容错,例如弱网无网环境下的处理,热更新失败下次保证再次热更新的处理,热更新时间把控,超过时间下次再reload,是否将热更新reload权利交给用户等等都可以扩展。

- ReactNative如何取消部分警告

debug模式下调试经常会有黄色的警告,有些警告可能是短时间不需要处理,通过下面的解决方法能忽略部分警告提示

解决方案:使用console.ignoredYellowBox

import { AppRegistry } from 'react-native';import './app/Common/SetTheme'import './app/Common/Global'import App from './App';console.ignoredYellowBox = ['Warning: BackAndroid is deprecated.  Please use BackHandler instead.',    'source.uri should not be an empty string','Remote debugger is in a background tab which',    'Setting a timer',    'Encountered two children with the same key,',    'Attempt to read an array index',];AppRegistry.registerComponent('ReactNativeTemplate', () => App);复制代码

- ReactNative开发遇到android网络图片显示不出来的问题

开发过程中有时会遇到iOS图片正常显示,但是安卓却只能显示部分网络图片,造成这个的原因有多种,参考下面的解决方案。

解决方案

  1. 安卓增加resizeMethod属性并设置为resize
复制代码

resizeMethod官方解释

resizeMethod  enum('auto', 'resize', 'scale') 当图片实际尺寸和容器样式尺寸不一致时,决定以怎样的策略来调整图片的尺寸。默认值为auto。auto:使用启发式算法来在resize和scale中自动决定。resize: 在图片解码之前,使用软件算法对其在内存中的数据进行修改。当图片尺寸比容器尺寸大得多时,应该优先使用此选项。scale:对图片进行缩放。和resize相比, scale速度更快(一般有硬件加速),而且图片质量更优。在图片尺寸比容器尺寸小或者只是稍大一点时,应该优先使用此选项。关于resize和scale的详细说明请参考http://frescolib.org/docs/resizing-rotating.html.复制代码
  1. 如果是FlatList或ScrollView等包裹图片,尝试设置

removeClippedSubviews={true}//ios set false

  1. 如果还是有问题,尝试配合

    还可以谨慎尝试使用

- ReactNative判断及监控网络情况方法总结

提前获取用户的网络情况很有必要,RN主要靠NetInfo来获取网络状态,不过随着RN版本的更新也有一些变化。 解决方案:

  1. 较新的RN版本(大概是0.50及以上版本)
this.queryConfig();    queryConfig = ()=> {        this.listener = NetInfo.addEventListener('connectionChange', this._netChange);    };            // 网络发生变化时    _netChange = async(info)=> {        const {            type,            //effectiveType        } = info;        const netCanUse = !(type === 'none' || type === 'unknown' || type === 'UNKNOWN' || type === 'NONE');        if (!netCanUse) {            this.setState({                isNetError : true            });            this.alertNetError(); //或者其他通知形式        } else {            try {                // 注意这里的await语句,其所在的函数必须有async关键字声明                let response = await fetch(CONFIG_URL);                let responseJson = await response.json();                const configData = responseJson.result;                if (response && configData) {                    this.setState({                        is_show_tip: configData.is_show_tip,                        app_bg: CONFIG_HOST + configData.app_bg,                        jumpUrl: configData.url,                        isGetConfigData: true                    }, () => {                        SplashScreen.hide();                    })                } else {                    // 错误码也去壳                    if ( responseJson.code === 400 ) {                        this.setState({                            isGetConfigData: true                        }, () => {                            SplashScreen.hide();                        })                    } else {                        this.setState({                            isGetConfigData: false                        }, () => {                            SplashScreen.hide();                        })                    }                }            } catch (error) {                console.log('queryConfig error:' + error);                this.setState({                    isGetConfigData: true                }, () => {                    SplashScreen.hide();                })            }        }    };                        alertNetError = () => {        setTimeout(()=> {            SplashScreen.hide();        }, 1000);        if ( ! this.state.is_show_tip &&  this.state.isGetConfigData ) {            return        } else {            Alert.alert(                'NetworkDisconnected',                '',                [                    {text: 'NetworkDisconnected_OK', onPress: () => {                        this.checkNetState();                    }},                ],                {cancelable: false}            );        }    };                        checkNetState = () => {        NetInfo.isConnected.fetch().done((isConnected) => {            if ( !isConnected ) {                this.alertNetError();            } else {                this.queryConfig();            }        });    };复制代码
  1. 老版本
async componentWillMount() {        this.queryConfig();    }    checkNetState = () => {        NetInfo.isConnected.fetch().done((isConnected) => {            console.log('111Then, is ' + (isConnected ? 'online' : 'offline'));            if (!isConnected) {                this.alertNetError();            } else {                this.queryConfig();            }        });    };    alertNetError = () => {        setTimeout(()=> {            SplashScreen.hide();        }, 1000);        console.log('111111');        if (!this.state.is_show_tip && this.state.isGetConfigData) {            console.log('222222');            return        } else {            console.log('33333');            Alert.alert(                'NetworkDisconnected',                '',                [                    {                        text: 'NetworkDisconnected_OK', onPress: () => {                        this.checkNetState();                    }                    },                ],                {cancelable: false}            );        }    };    queryConfig = ()=> {               NetInfo.isConnected.addEventListener(            'connectionChange',            this._netChange        );    };    // 网络发生变化时    _netChange = async(isConnected)=> {        console.log('Then, is ' + (isConnected ? 'online' : 'offline'));            if (!isConnected) {            console.log('666');            this.setState({                isNetError: true            });            this.alertNetError();        } else {            try {                // 注意这里的await语句,其所在的函数必须有async关键字声明                let response = await fetch(CONFIG_URL);                let responseJson = await response.json();                const configData = responseJson.result;                if (response && configData) {                    this.setState({                        is_show_tip: configData.is_show_tip,                        app_bg: CONFIG_HOST + configData.app_bg,                        jumpUrl: configData.url,                        isGetConfigData: true                    }, () => {                        SplashScreen.hide();                        this.componentNext();                    })                } else {                    this.setState({                        isGetConfigData: false                    }, () => {                        SplashScreen.hide();                        this.componentNext();                    })                }            } catch (error) {                console.log('queryConfig error:' + error);                this.setState({                    isGetConfigData: true                }, () => {                    SplashScreen.hide();                    this.componentNext();                })            }        }    };复制代码

- ReactNative版本升级后报错有废弃代码的快速解决方法

使用第三方库或者老版本升级时会遇到报错提示某些方法被废弃,这时候寻找和替换要花不少时间,而且还容易漏掉。

解决方案: 根据报错信息,搜索废弃的代码,例如

报错提示:Use viewPropTypes instead of View.propTypes.

搜索命令:grep -r 'View.propTypes' .

替换搜索出来的代码即可。

这是用于查找项目里的错误或者被废弃的代码的好方法

- 解决ReactNative的TextInput在0.55中文无法输入的问题

此问题主要体现在iOS中文输入法无法输入汉字,是0.55版RN的一个bug

解决方案:使用下面的MyTextInput替换原TextInput

import React from 'react';import { TextInput as Input } from 'react-native';export default class MyTextInput extends React.Component {    static defaultProps = {        onFocus: () => { },    };    constructor(props) {        super(props);        this.state = {            value: this.props.value,            refresh: false,        };    }    shouldComponentUpdate(nextProps, nextState) {        if (this.state.value !== nextState.value) {            return false;        }        return true;    }    componentDidUpdate(prevProps) {        if (prevProps.value !== this.props.value && this.props.value === '') {            this.setState({ value: '', refresh: true }, () => this.setState({ refresh: false }));        }    }    focus = (e) => {        this.input.focus();    };    onFocus = (e) => {        this.input.focus();        this.props.onFocus();    };    render() {        if (this.state.refresh) {            return null;        }        return (             { this.input = ref; }}                value={this.state.value}                onFocus={this.onFocus}            />        );    }}复制代码

ReactNative集成第三方DEMO编译时遇到RCTSRWebSocket错误的解决方法

报错信息如下

Ignoring return value of function declared with warn_unused_result attribute复制代码

解决方案

StackOverFlow上的解决方法:

在navigator双击RCTWebSocket project,移除build settings > custom compiler 下的flags复制代码

版权声明:

转载时请注明作者以及本文地址:


你可能感兴趣的文章
Struts2 学习小结
查看>>
烂泥:wordpress迁移到docker
查看>>
.扒渣机的性能及优势 
查看>>
Linux下磁盘保留空间的调整,解决df看到的空间和实际磁盘大小不一致的问题
查看>>
RSA 生成公钥、私钥对
查看>>
C# ASP.NET 权限设计 完全支持多数据库多语言包的通用权限管理系统组件源码
查看>>
测试工具综合
查看>>
asp.net中调用COM组件发布IIS时常见错误 80070005解决方案
查看>>
分享一段ios数据库代码,包括对表的创建、升级、增删查改
查看>>
如何书写高质量的jQuery代码
查看>>
Activity的生命周期整理
查看>>
【记录】JS toUpperCase toLowerCase 大写字母/小写字母转换
查看>>
在 Linux 系统中安装Load Generator ,并在windows 调用
查看>>
Visifire charts ToolBar
查看>>
Mysql查询
查看>>
数据传输流程和socket简单操作
查看>>
利用广播实现ip拨号——示例
查看>>
ProbS CF matlab源代码(二分系统)(原创作品,转载注明出处,谢谢!)
查看>>
OC中KVC的注意点
查看>>
JQ入门(至回调函数)
查看>>