本文均為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


推薦閱讀:
查看原文 >>
相关文章