本文均为RN开辟过程当中碰到的题目、坑点的剖析及处置惩罚计划,各题目点之间无关联,愿望能协助读者少走弯路,延续更新中… (2019年3月29日更新)
原文链接:http://www.kovli.com/2018/06/…
作者:Kovli
– 如安在原生端(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 <React/RCTConvert.h>
RCT_EXPORT_METHOD(showRNImage:(id)rnImageData){
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *rnImage = [RCTConvert UIImage:rnImageData];
...
});
}
安卓端
第一步,从桥接文件猎取到uri地点
@ReactMethod
public 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<String, Integer> 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,以防止被遮挡。
处置惩罚计划:参考以下例子
<ScrollView style={styles.container}>
<KeyboardAvoidingView behavior="position" keyboardVerticalOffset={64}>
...
<TextInput />
...
</KeyboardAvoidingView>
</ScrollView>
– ReactNative输入框TextInput点击弹起键盘,然后点击其他子组件,比方点击提交按钮,会先把键盘收起,再次点击提交按钮才相应提交按钮,得点击两次,如何做到点击提交按钮的同时收起键盘并相应按钮?
这个题目症结在ScrollView
的keyboardShouldPersistTaps
属性
,起首TextInput
的特殊性(有键盘弹起)决议了其最好包裹在ScrollView里,其次假如当前界面有软键盘,那末点击scrollview
后是不是收起键盘,取决于keyboardShouldPersistTaps
属性的设置。(译注:很多人回响反映TextInput
没法自动落空中心/须要点击屡次切换到其他组件等等题目,其症结都是须要将TextInput
放到ScrollView
中再设置本属性)
- ‘never’(默认值),点击TextInput之外的子组件会使当前的软键盘收起。此时子元素不会收到点击事宜。
- ‘always’,键盘不会自动收起,ScrollView也不会捕捉点击事宜,但子组件能够捕捉。
- ‘handled’,当点击事宜被子组件捕捉时,键盘不会自动收起。如许切换TextInput时键盘能够坚持状况。多半带有TextInput的状况下你应当挑选此项。
- false,已过期,请运用’never’替代。
- true,已过期,请运用’always’替代。
处置惩罚计划:看以下例子
<ScrollView style={styles.container}
keyboardShouldPersistTaps="handled">
<TextInput />
...
</ScrollView>
//按钮点击事宜注重收起键盘
_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');
<Image
style={{width:100, height:100}}
source={{uri: 'file://' + RNFS.DocumentDirectoryPath + '/myAwesomeSubDir/my.png', scale:1}}
– ReactNative如何做到图片宽度稳定,宽高坚持比例,高度自动调解。
RN图片均须要指定宽高才会显现,假如图片数据的宽高不定,但又愿望宽度坚持稳定、差别图片的高度依据比例动态变化,就须要用到下面这个库,营业场景常用于文章、商品概况的多图展现。
处置惩罚计划:运用react-native-scalable-image
– 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启动会涌现一闪而过白屏,能够经由过程启动页耽误加载要领来防止这类白屏,能够用下面的库
处置惩罚计划:react-native-splash-screen
– ReactNative如何做到无感热更新
无论是整包热更新照样差量热更新,均须要终究替代JSBundle等文件来完成更新过程,完成道理是js来掌握启动页的消逝时候,等原生把bundle包下载(或合并成新bundle包)解压到目次今后,关照js消逝启动页,因为热更新时候平常很短,发起运用差量热更新,一秒摆布,所以用户等启动页消逝后看到的就是最新的版本。
处置惩罚计划(以整包更新为例):
- 原生端完成更新及革新操纵,注重内里的
[_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;
}
- JS端
// 原生端经由过程回调效果关照JS热更新状况,JS端
UpdateModule.gotoUpdateJS(jsUrl).then(resp => {
if ( resp ) {
// 胜利更新关照隐蔽启动页
DeviceEventEmitter.emit("hide_loading_page",'hide');
} else {
// 出题目也要隐蔽启动页,用户继承运用旧版本
DeviceEventEmitter.emit("hide_loading_page",'hide');
// 其他处置惩罚
}
});
- 启动页消逝,用户看到的是新版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图片一般显现,然则安卓却只能显现部份收集图片,形成这个的缘由有多种,参考下面的处置惩罚计划。
处置惩罚计划:
- 安卓增添resizeMethod属性并设置为resize
<Image style={styles.imageStyle} source={{uri: itemInfo.imageUrl || ''}} 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.
- 假如是FlatList或ScrollView等包裹图片,尝试设置
removeClippedSubviews={true}//ios set false
- 假如照样有题目,尝试合营react-native-image-progress
还能够郑重尝试运用react-native-fast-image
– ReactNative推断及监控收集状况要领总结
提早猎取用户的收集状况很有必要,RN重要靠NetInfo来猎取收集状况,不过跟着RN版本的更新也有一些变化。
处置惩罚计划:
- 较新的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();
}
});
};
- 老版本
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 (
<Input
{...this.props}
ref={(ref) => { 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
版权声明:
转载时请说明作者Kovli以及本文地点:
http://www.kovli.com/2018/06/…