ReactNative开发笔记(持续更新...)
本文均為RN開發過程中遇到的問題、坑點的分析及解決方案,各問題點之間無關聯,希望能幫助讀者少走彎路,持續更新中... (2019年3月29日更新)
原文鏈接:http://www.kovli.com/2018/06/25/rn-anything/
作者: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();}}@NonNullprivate 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;}@Nullablepublic 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');<Imagestyle={{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消失啟動頁,由于熱更新時間一般很短,建議使用差量熱更新,一秒左右,所以用戶等啟動頁消失后看到的就是最新的版本。
解決方案(以整包更新為例):
注意做好容錯,例如弱網無網環境下的處理,熱更新失敗下次保證再次熱更新的處理,熱更新時間把控,超過時間下次再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官方解釋
resizeMethod enum('auto', 'resize', 'scale') 當圖片實際尺寸和容器樣式尺寸不一致時,決定以怎樣的策略來調整圖片的尺寸。默認值為auto。auto:使用啟發式算法來在resize和scale中自動決定。resize: 在圖片解碼之前,使用軟件算法對其在內存中的數據進行修改。當圖片尺寸比容器尺寸大得多時,應該優先使用此選項。scale:對圖片進行縮放。和resize相比, scale速度更快(一般有硬件加速),而且圖片質量更優。在圖片尺寸比容器尺寸小或者只是稍大一點時,應該優先使用此選項。關于resize和scale的詳細說明請參考http://frescolib.org/docs/resizing-rotating.html.removeClippedSubviews={true}//ios set false
如果還是有問題,嘗試配合react-native-image-progress
還可以謹慎嘗試使用react-native-fast-image
- ReactNative判斷及監控網絡情況方法總結
提前獲取用戶的網絡情況很有必要,RN主要靠NetInfo來獲取網絡狀態,不過隨著RN版本的更新也有一些變化。
解決方案:
- 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/25/rn-anything/
轉載于:https://www.cnblogs.com/kovli/p/rn_anything.html
總結
以上是生活随笔為你收集整理的ReactNative开发笔记(持续更新...)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MacOS安装react。问题 --
- 下一篇: Hibernate上传数据到数据库,从数