本文均为RN开发过程中遇到的问题、坑点的分析及解决方案,各问题点之间无关联,希望能帮助读者少走弯路,持续更新中… (2018年12月19日更新)原文链接:http://www.kovli.com/2018/06/…作者:Kovli- 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.ignoredYellowBoximport { 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替换原TextInputimport 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/…