阅读

ReactNative开发笔记(持续更新...)

编者按: 红宝书第5版2024年12月1日出炉了,涵盖了JS最新版本ES15的特性,感兴趣的可以去看看,https://u.jd.com/saQw1vP 本文各问题点之间无关联,均为RN开发过程中遇到的问题、坑点的分析及解决方案,希望能帮助读者少走弯路,持续更新中… (2024年12月17日更新)



重要通知:红宝书第5版2024年12月1日出炉了,涵盖了JS最新版本ES15的特性,感兴趣的可以去看看,https://u.jd.com/saQw1vP

- 老款MacBook系统MacOS 12 升级cocoapods遇到的坑点

常见报错如下:

1、Error: *** has been disabled because it is not supported upstream!

2、Error running __rvm_make -j8

解决办法如下:

// 升级或安装RVM
curl -L get.rvm.io | bash -s stable

source ~/.bashrc

source ~/.bash_profile

// 注意zsh是~/.zshrc

// 安装此macOS12支持的ruby最新版本2.7.2
rvm install 2.7.2

// 此时出现了错误Error running '__rvm_make -j8',执行以下命令
brew uninstall --ignore-dependencies openssl@3
rm -rf /usr/local/etc/openssl@1.1
brew reinstall openssl@1.1

// 这里开始报错Error: xxx has been disabled because it is not supported upstream! 编辑此文件
brew edit openssl@1.1

// 编辑内容:vim编辑模式注释掉里面的deprecate! date:... because: :unsupported这一行,
# deprecate! date:... because: :unsupported

// 然后执行:
HOMEBREW_NO_INSTALL_FROM_API=1 brew install openssl@1.1

// HOMEBREW_NO_INSTALL_FROM_API=1 这个环境变量的作用就是告诉brew 不使用api中的formula而是使用你自己编辑后的, 这样就可以正常安装被brew禁止disable的软件包了。


// 上面的执行完再次安装2.7.2就可以成功
rvm install 2.7.2
rvm use 2.7.2 --default

// 安装cocoapods
sudo gem install cocoapods -v 1.16.2 -n /usr/local/bin

// 此时报错提示要安装securerandom,根据报错信息进行安装指定版本
sudo gem install -n /usr/local/bin securerandom -v 0.3.2

// 然后继续安装cocoapods
sudo gem install cocoapods -v 1.16.2 -n /usr/local/bin

// 此时再次报错提示安装activesupport,根据报错信息进行安装指定版本
sudo gem install -n /usr/local/bin activesupport -v 7.1.5.1

// 再次安装cocoapods就成功了
sudo gem install -n /usr/local/bin cocoapods -v 1.16.2

pod --version
1.16.2

tips:
1、cocoapods所有版本
https://rubygems.org/gems/cocoapods/versions
2、ruby可安装的版本信息
rvm list known

- 个推推送自定义通知图标在iOS端没有显示的问题。

采用个推后台下发的形式,测试是否有图标,如果也没有,大概率是Notification Service Extension这个扩展库出问题了。

1. 参考这篇网页:https://docs.getui.com/getui/scene/noticeIcon/ 对自定义通知图标的服务端、客户端等按照文档排查下看有没有问题。
2.参考这篇网页:https://docs.getui.com/getui/mobile/ios/xcode/ 第7条,检查这个扩展库是否集成正确,swift空文件是否已加,加了以后项目是否正常跑得起来
3.上面没问题后,看下 NotificationService 这个Target,在Signing & Capabilities下面,debug和release的证书是否分别对应了NotificationService在苹果申请的开发和打包证书,如果是debug模式跑真机测试推送,但是NotificationService 这个Target下 用的是其release证书,就会出现收不到自定义图标推送。
4.如果2所述证书均设置正确,把target设为NotificationService,Scehma也选择NotificationService,跑真机,弹框里选择项目APP,看能否跑起来项目,跑不起来还是NotificationService这块设置的有问题。
5.如果3跑起来了,NotificationService里面下断点,个推官网下发带自定义图标的推送,看是否走到断点里,排查原因。
6.如果前几步都正常,但是还是官网后台管理平台下发通知类多媒体推送无法出现自定义图标,请联系客服对接专业技术人员联调处理。

- 安卓如果报错以下内容

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'com.facebook.react.bridge.JavaScriptModule com.facebook.react.bridge.ReactContext.getJSModule(java.lang.Class)' on a null object reference
问题在于reactContext还没拿到的情况下,给RN发消息了,可以用一下方法解决:

the solution was to check if reactContext is not null otherwise create listener and wait for it:

ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if(reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("url", params);
} else {
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("url", params);
reactInstanceManager.removeReactInstanceEventListener(this);
}
});
}

- ScrollView does not work inside position: absolute on Android.

如果scrollview在absolute定位下的父视图内,

If anyone else is having this issue (like me), then using react-native-gesture-handler instead actually seems to fix the issue.

In your imports remove ScrollView and instead import:

import { ScrollView } from 'react-native-gesture-handler';

- react-native 安卓状态栏 一直无法透明,占位不能沉浸式,网上方法都无效的情况下

检查三方库,react-native-keyboard-controller的 
<KeyboardProvider statusBarTranslucent={true}>

- ReactNative二维码扫描通用方案

react-native-camera 3+版本,坑少,安卓加上missingDimensionStrategy 'react-native-camera', 'general'

- 使用最新版本RN(0.73.6)遇到的一些问题记录。

  • iOS运行时报错Library not loaded: @rpath/hermes.framework/hermes on iOS问题
    报错信息
    Library not loaded: @rpath/hermes.framework/hermes xxxx Debug-iphonesimulator/hermes.framework/hermes

解决方案

xcode > build phases > Link binary with Libraries 搜索并添加 hermes.xcframework
  • iOS运行时报错Unresolved dSYMs path for hermes.xcframework问题
    报错信息
    error: Missing path (project/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework/ios-arm64_x86_64-simulator/dSYMs) from XCFramework 'hermes.xcframework' as defined by 'DebugSymbolsPath' in its `Info.plist` file (in target 'iOSTarget' from project 'Project')
    ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/dSYMs

解决方案
podfile 增加installer.pods_project.targets.each do及下面的内容,然后pod install:

post_install do |installer|
PLIST_BUDDY_PATH = '/usr/libexec/PlistBuddy'

react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)

installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if target.name == "hermes-engine"
installer.pods_project.files.each do |fileref|
if fileref.path.end_with? "hermes.xcframework"
hermes_plist_file = "#{fileref.real_path}/Info.plist"
# Patch Hermes to remove the debug symbols entry from the Info.plist (as it's not shipped with it)
# This might be removed once Hermes starts to ship with Debug symbols or we remove our
# direct dependency from the Main iOS target on "hermes.xcframework"
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:0:DebugSymbolsPath', hermes_plist_file)
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:1:DebugSymbolsPath', hermes_plist_file)
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:2:DebugSymbolsPath', hermes_plist_file)
end
end
end
end
end

end

上述两个报错的根源在于gem,ruby,cocoapod不是最新版本,亦可尝试更新到最新版本而不用上面的解决办法

  • 安卓运行时报错CLI: unexpected token '?'问题
    报错信息
    CLI: unexpected token '?'

解决方案

1、确保node版本在18以上
2、如果使用了nvm管理工具,请确保默认node版本在18以上,例如可以如下设置
nvm alias default v18.20.0
3、android studio 的【终端】键入以下命令
node -v 检查是否在18以上
4、重启android studio 或者 file - Sync Project with Gradle Files

- XCode14+跑真机遇到Command PhaseScriptExecution failed with a nonzero exit code"问题

报错信息

Command PhaseScriptExecution failed with a nonzero exit code

解决方案

1、用VSCode打开 ios/App/App.xcodeproj/project.pbxproj
2、全局搜索 EXCLUDED_ARCHS
3、把EXCLUDED_ARCHS = arm64这类的
统一改为:EXCLUDED_ARCHS = ""
4、找到下面的文件
node_modules/react-native/scripts/find-node.sh

搜索 set -e

改为 set +e

5、XCode> Product> Clean Build Folder

- yarn 操作时遇到Integrity check failed for ...(computed integrity doesn't match our records, got "sha512-... sha1-..."问题

报错信息

error https://registry.npm.taobao.org/uglify-es/download/uglify-es-3.3.10.tgz: Integrity check failed for "uglify-es" (computed integrity doesn't match our records, got "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== sha1-DBxPBwC+2NvBJM2zBNJZLKID5nc=")
info Visit https://yarnpkg.com/en/docs/cli/remove for documentation about this command.

网上试了各种清缓存方法无效,如果删除lockfile是可以成功,但是会重新生成lockfile,里面的版本号有些会更新,不符合之前锁版本的预期,想要保留locakfile,接下来的才是适合国情的全网最正确解决方案!
解决方案

yarn config set registry https://registry.yarnpkg.com
yarn --update-checksums
yarn config set registry https://registry.npm.taobao.org

- React Native的View自适应内部文本Text宽度

加View的style 增加一个 alignSelf
解决方案

alignSelf: 'flex-start/center/flex-end...',

- iOS pod intall 报错CDN: trunk Repo update failed的解决方案

报错信息

[!] CDN: trunk Repo update failed - 24 error(s):
CDN: trunk URL couldn't be downloaded: https://cdn.jsdelivr.net/cocoa/Specs/3/2/5/FlipperKit/0.128.0/FlipperKit.podspec.json Response: SSL connect error`

解决方案

pod repo remove trunk
pod install

- iOS真机联调红屏报错Error: EISDIR: illegal operation on a directory的解决方案

报错信息

Error: EISDIR: illegal operation on a directory, read
at Object.readSync (fs.js:568:3)
at tryReadSync (fs.js:353:20)
at Object.readFileSync (fs.js:390:19)

  • 临时解决方案:摇一摇iPhone,弹出debug菜单,倒数第二项点进去,分别输入PC端ip地址,8081,index,然后再reload就可以了
  • 问题根源在debug模式没有找到index bundle文件,检查iOS原生部分jsCodeLocation的获取是否有问题,例如下面的获取方式里index是否写成了index.ios的老版本写法
    jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

- RN的层叠关系中,一个滑动View的zIndex设置为2,当滑动到其父View的兄弟View时,iOS的子View会被盖住,安卓正常的解决方案

在叠层关系中,子View的zIndex不能高于父View的zIndex,故而导致子View了zIndex设置无效;
解决方法:

提升该View的父View的zIndex 为2,再提升子View的zIndex才可生效;

PS:要zIndex生效,还要设置子view的position的位置。

- react-native-scrollable-tab-view的goToPage方法在安卓端出现先返回第一页再跳转到指定页的闪烁问题的解决方案

解决方案
1、<ScrollAbleView增加 prerenderingSiblingsNumber = {Infinity}
Infinity可修改为所需的数字
prerenderingSiblingsNumber (Integer) - pre-render nearby # sibling, Infinity === render all the siblings, default to 0 === render current page.

2、跳转增加setTimeOut,否则就停在第一页不跳了

setTimeout(() => {
this.goToPage(1);
}, 0);

- react-native-webview 使用injectedJavaScript方法注入网页js代码时的坑点:

1、获取dom元素当document.getElementById('loginId').value = "test-mobile"失效时采用这种方式获取:

const input = document.querySelector('#loginId');
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')
.set.call(input, storage["loginId"] || "");

2、安卓有效,iOS的injectedJavaScript不生效的解决方案:
增加onMessage

injectedJavaScript={jsCode}
onMessage={(event) => {}}

3、安卓有效,iOS的document.getElementByIdObject.getOwnPropertyDescriptor均无效的解决方案:

Fixed using a timer.

const jsCode = `
setTimeout(() => {
document.getElementById("name").value = "${name}";
document.f1.submit();
}, 100);
`;

解决方案参考地址:https://github.com/react-native-webview/react-native-webview/issues/341

完整案例:

let jsCode = `
// document.getElementById('loginId').value = "test-mobile";
// document.getElementById('password').value = "test123";
var storage=window.localStorage;
if(storage){
setTimeout(() => {
const input = document.querySelector('#loginId');
const pwd = document.querySelector('#password');
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')
.set.call(input, storage["loginId"] || "");
if (storage["password"]) {
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')
.set.call(pwd, window.atob(storage["password"]) || "");
}
input.dispatchEvent(new Event('change', { bubbles: true }));
pwd.dispatchEvent(new Event('change', { bubbles: true }));
document.querySelector('#loginBtn').onclick = function(){
if (document.getElementById('loginId').value && document.getElementById('password').value) {
//写入loginId字段
storage["loginId"]=document.getElementById('loginId').value;
//写入password字段
storage.password=window.btoa(document.getElementById('password').value);
}
};
}, 100);
}
`;
let isSomePage = false
return (
<WebView
ref={webview => {
this.webview = webview;
}}
style={{
backgroundColor: UIConstant.Common_Color_Background,
}}
source={{uri: this.props.url}}
injectedJavaScript={jsCode}
onMessage={(event) => {}}
// thirdPartyCookiesEnabled={true}
// domStorageEnabled={true}
// allowFileAccess={true}
// cacheEnabled={true}
// sharedCookiesEnabled={true}
// sharedCookiesEnabled={true}
// javaScriptEnabled={true}
// scalesPageToFit={true}
// injectedJavaScript={patchPostMessageJsCode}//注入js代码
// onMessage={(isSomePage) ? null : this._onReceiveWebMessage}
// onNavigationStateChange={this._onNavigationStateChange}
// onLoadEnd={this._onLoadEnd}
// onLoadStart={this._onLoadStart}
renderError={(errorDomain, errorCode, errorDesc) => this._loadError(errorDomain, errorCode, errorDesc)}
onError={(errorDomain, errorCode, errorDesc) => this._loadError(errorDomain, errorCode, errorDesc)}
// mixedContentMode='always'
/>

- RN安卓编译时出现如下报错的解决方案

报错信息:React-Native :java.lang.UnsatisfiedLinkError: couldn’t find DSO to load: libhermes.so

解决方案:清空gradlew再编译

cd android
./gradlew clean

- 目前最新的Xcode版本13.1跑RN0.64.2初始化工程报错的解决方案(iOS)

报错信息:Showing All Errors Only Undefined symbol: __swift_FORCE_LOAD_$_swiftDataDetection

解决方案:注意报错提示哪几个就加哪几个,低版本的Xcode不要加

needs to be added to the Link Binary with Libraries phase call libSwiftDataDetection.tbd. Error went away after adding this.

Unfortunately, adding this library breaks compilation with Xcode 11, so you'll have to add & remove it depending on which version of Xcode you're using.

- 如何在原生端(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.XXX;// 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点击弹起键盘,然后点击其他子组件,例如点击提交按钮,会先把键盘收起,再次点击提交按钮才响应提交按钮,得点击两次,如何做到点击提交按钮的同时收起键盘并响应按钮?

这个问题关键在ScrollViewkeyboardShouldPersistTaps属性
,首先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

从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消失启动页,由于热更新时间一般很短,建议使用差量热更新,一秒左右,所以用户等启动页消失后看到的就是最新的版本。
解决方案(以整包更新为例):

  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;
    }
  2. 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 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.

  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();
    }
    });

    };
  2. 老版本

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

鼓励一下

如果觉得我的文章对您有用,欢迎打赏(右边栏二维码),您的支持将鼓励我继续创作!”


咨询联系方式

  • 请发邮件: admin@kovli.com