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

原文链接:kovli.com/2018/06/25/rn

作者:Kovli

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

使用这个组件KeyboardAvoidingView

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

解决方案:参考如下例子

<ScrollView stylex={styles.container}>
<KeyboardAvoidingView behavior="position" keyboardVerticalOffset={64}>
...
<TextInput />
...
</KeyboardAvoidingView>
</ScrollView>

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

这个问题关键在ScrollViewkeyboardShouldPersistTaps属性 ,首先TextInput的特殊性(有键盘弹起)决定了其最好包裹在ScrollView里,其次如果当前界面有软键盘,那么点击scrollview后是否收起键盘,取决于keyboardShouldPersistTaps属性的设置。(译注:很多人反应TextInput无法自动失去焦点/需要点击多次切换到其他组件等等问题,其关键都是需要将TextInput放到ScrollView中再设置本属性) - never(默认值),点击TextInput以外的子组件会使当前的软键盘收起。此时子元素不会收到点击事件。 - always,键盘不会自动收起,ScrollView也不会捕捉点击事件,但子组件可以捕获。 - handled,当点击事件被子组件捕获时,键盘不会自动收起。这样切换TextInput时键盘可以保持状态。多数带有TextInput的情况下你应该选择此项。 - false,已过期,请使用never代替。 - true,已过期,请使用always代替。

解决方案:看如下例子

<ScrollView stylex={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
stylex={{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 } fromreact-native-deprecated-custom-components;(注意最后有一个分号)

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

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

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

- 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

<Image stylex={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.

  1. 如果是FlatList或ScrollView等包裹图片,尝试设置

removeClippedSubviews={true}//ios set false

  1. 如果还是有问题,尝试配合react-native-image-progress

还可以谨慎尝试使用react-native-fast-image

- 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 (
<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以及本文地址: kovli.com/2018/06/25/rn


推荐阅读:
查看原文 >>
相关文章