基础概念
**特别注意: **
React-Native
是基于React
实现的,更多语法可以参考React 开发手册- 如果是自己开发新产品,那么希望每次都把各个基础组件升级到最新稳定版。
React Native
开发的优点
- 拥有系统级别的通知或提醒
- 可以访问本地通讯录、相册等资源
- 可以针对不同的平台提供不同的体验
React Native
采用的是ES2015
(即ES6)的语法标准,模板上使用了自己的JSX
语法(在代码中嵌入结构标记)。
环境搭建
1 | 初始化项目 |
prop&&state
两者可以说是大同小异,在大多数情况下,两者没什么很大的差别。两者的改变的时候,渲染的地方都会重新渲染。
prop: 一个组件的设置参数,可以理解为初始化参数或者对象的静态变量,并且可以在父组件中设置,在子组件中不可改变,但是可以一直往下传递至子子孙孙。
state: 更像是对象的一些变量,并且确实是经常改变的,只是父子之间不能传递。
1 | // 动态设置某个状态值 |
布局
不用css,但是类似css。所有的组件都有style
属性。样式名是将默认的css的命名更改为了驼峰命名。一般使用StyleSheet.create
在组件外面集中定义组件的样式。例如:
1 | // 指定固定的高度和宽度用width和height,React Native中的尺寸都是无单位的。 |
Flexbox布局
规定某个组件的子元素的布局。flex
的值就类似于栅栏布局中的row宽度,一个2一个1,那么画面总共可以分成三份这种,如果直接flex:1
,那么就表示直接占据整个。
1 | // 父视图属性 |
居中问题
1 | // 图标悬浮与图片的正中间,两者均居中对齐 |
定位问题
1 | // 获取屏幕尺寸 |
组件
Animated动画
第三方库里面那些酷炫的效果均是通过动画来实现的
1 | const top = useRef(new Animated.Value(100)).current; // 将一个属性变为可以执行动画的属性 |
Button基础按钮
这个组件的样式是固定的,如果需要自定义,那么高级的按钮参考Touchable
系列
1 | <Button |
Image
图片组件,如果我们在同一个目录里面同时包含a.png/a@2x.png,a@3x.png
那么react native
就能通过屏幕的分辨率自动选择不同尺寸的图片,并且在代码里面仅需要require(./img/check.png)
就行了。
Navigation/Component导航组件/路由/route
Navigation文档,Navigation已经单独成为一个模块,强烈建议不再使用老的导航器,导航器对比,在这里有其更详细的文档。在0.44
版本移除了Navigator
,该模块被移动到react-native-custom-components现在也仅用于兼容老版本。使用前得先安装npm install --save react-navigation
。有如下三种类型的导航器
1 | 看官网的意思就是要安装这些东西 |
StackNavigator
类似于普通的Navigator,体现在屏幕上方的导航栏
1 | // StackNavigator用于创建多页面应用。其中每一个都是一个Component |
TabNavigator
类似于ios的TabBarController
,屏幕下方的标签栏
DrawerNavigator
侧边弹出的抽屉效果
SafeAreaView
- 使用该组件包裹可以自动实现异形屏的padding,也不用考虑android还是iOS
1 | import { SafeAreaView } from 'react-native' // 如果不工作,就使用下面的方式 |
ScrollView滚动
可以在该组件下面添加任意组件,能轻松实现几个组件的共同滑动
1 | const scrollViewRef = useRef<ScrollView>(null); |
StatusBar状态栏
Text
- 默认情况下,系统字体的大小会直接影响到APP里面的显示,我们需要防止这种情况,防止用户把字体调得太大,可以在app.tsx中全局设置:
1
2
3
4
5
6
7
8
9
10import {Text, TextInput} from 'react-native';
if (Text.defaultProps == null) {
Text.defaultProps = {};
Text.defaultProps.allowFontScaling = false;
}
if (TextInput.defaultProps == null) {
TextInput.defaultProps = {};
TextInput.defaultProps.allowFontScaling = false;
}
1 | <Text |
TextInput输入框
TextInput默认宽度与父节点相同。如果想要其在没有文字的时候也能占据宽度,可以设置flex:1
并且父View
也得设置flex:1
1 | <TextInput |
Touchable*系列
hitSlop
属性可以让可以点击的区域比实际的要大,非常适合在移动端的点击操作包括了触摸的相关事件(触摸、点击、长按、反馈等):
onPressIn: 触摸开始
onPressOut: 触摸离开
onPress: 单击事件
onLongPress: 长按事件
TouchableHighlight
触摸点击高亮效果。点击的时候,不透明度会降低,同时会看到变暗或者变量。只支持一个子节点,如果要多个子视图组件,可以用View进行包装。
1 | <TouchableHighlight onPress={this._onPressButton.bind(this)} underlayColor="white"> |
TouchableNativeFeedback
仅限android。
TouchableOpacity
透明度变化。
TouchableWithoutFeedback
不带反馈效果的。
API
Share分享功能
1 | import { Share } from 'react-native'; |
JSX语法
1 | // 使用循环 |
样式stylesheet
- 官方建议不要将stylesheet放在render函数中
- 最好不同的组件使用不同的名称,不要全都用
styles
命名 - 原生不支持scss那样的嵌套语法,好像也没有啥好用的嵌套方式,就是感觉原生就是不支持什么复杂的样式
1 | const page = StyleSheet.create({ |
网络请求
React Native
使用的网络请求是Fetch API,但是,统治js的http请求库明显是axios
,所以我还是喜欢用axios
,另外,网络请求天生就应该是异步的,这两个库都是不支持同步的。
1 | // 安装npm install --save axios |
Debug
如果是真机,可以通过摇一摇弹出debug菜单,但是基本上没啥用,最有用的可能就是Chrome里面调试了,至少能看到打印出来的object的详情
LogBox
在release/production
中是自动禁用的
常用插件推荐
Awesome React Native
- 包含很多的react native的插件扩展
customauth-react-native-sdk
torus sdk
如果运行不起来可以试试它项目里面的example,虽然文档少了,但是那个example还是更新的挺及时的,照着看有没有遗漏的,我在1.0.1版本上发现有这些需要额外配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45// ios/Podfile,具体行数参考example中的配置
use_modular_headers
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec', :modular_headers => false
installer.pods_project.build_configurations.each do |config|
# config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
installer.pods_project.targets.each do |target|
if target.name == "web3.swift"
target.build_configurations.each do |config|
config.build_settings["SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]"] = "$(inherited) $(PODS_CONFIGURATION_BUILD_DIR)/BigInt $(PODS_CONFIGURATION_BUILD_DIR)/GenericJSON $(PODS_TARGET_SRCROOT)/web3swift/lib/**"
config.build_settings["SWIFT_INCLUDE_PATHS[sdk=iphoneos*]"] = "$(inherited) $(PODS_CONFIGURATION_BUILD_DIR)/BigInt $(PODS_CONFIGURATION_BUILD_DIR)/GenericJSON $(PODS_TARGET_SRCROOT)/web3swift/lib/**"
end
end
end
// AppDelegate.m,我最开始就是点了登录后没反应,后来发现是它根本没有监听openURL
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
NSString *myString = url.absoluteString;
NSLog(@"String to handle : %@ ", myString);
if (@available(iOS 10.0, *)) {
[RNCustomAuthSdk handle:myString];
} else {
// Fallback on earlier versions
}
// Your additional URL handling (if any) goes here.
return NO;
}
// ios/xxx/Info.plist,添加url scheme
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>torusapp</string>
</array>
</dict>
react-native-geolocation-service
谷歌定位插件,能够获取当前的定位
如果出现获取不到地理位置,经常提示
timed out
并且time out设置为很大依然报错,可以参考这个issueLocation request timed out most of the time,下载谷歌地图然后定位一下,再重新安装一下应用试试如果出现Location settings are not satisfied: 根据我的尝试,可能是因为国内或者说是因为小米手机的问题,ios和android得不同的设置才行:
1
2
3
4
5Geolocation.getCurrentPosition(
(position) => {console.log(position)},
(error) => {console.log(error)},
Platform.OS === 'ios' ? { enableHighAccuracy: true, timeout: 25000, maximumAge: 20000 } : { enableHighAccuracy: false, maximumAge: 20000, forceRequestLocation: true, forceLocationManager: true, distanceFilter: 250, accuracy: { android: 'balanced', ios: 'threeKilometers' } }
);
react-native-async-storage
- 能够用来持久化mobx等的状态,在应用退出后不会清空
React-native iOS, Async storage error: "Invalid key - must be at least one character. Key:
出现这个错误是因为在getItem/setItem的时候key的值为空,需要修改一下,注意如果key的值修改后可能需要重新build才能生效
react-native-bottom-sheet
- 一个比较好用的底部弹出功能,drawer,抽屉
- snapPoints: 定义弹出的区域的高度,这之外的地方不能点击
enablePanDownToClose
: 向下滑自动关闭- 如果是多个sheet叠加显示,好像DOM后面的就是最上层
react-native-dotenv
react-native-config
react-native-config
比react-native-dotenv
更通用,不用为每个环境变量声明typescript,并且它支持不同的环境使用不同的环境变量使用
.env
文件来加载环境变量需要注意的是,它是有缓存的,如果变量更改了记得参考文档清理cache
如果使用的是typescript,最好参考文档使用
Option 2: specify types manually
react-native-drop-shadow
- 拖动的时候的阴影
react-native-elements
- element 的UI套件/UI框架
react-native-fs
- 文件操作,下载文件,保存文件
react-native-iap
- 用于google play和apple store的内购组件
- Android平台能够通过getProducts获取产品列表,但是购买的时候却报错That item is unavailable: 具体原因还未知,在github提交了discussion,但目前没有回复。最后不知道怎么就解决了,尝试过这些方法:
- 上传一个signed release到internal testing和closed testing,但是第一次上传审核时间有点久,且审核通过后可能也要等几小时才可以
- Google Play Console -> Setup -> API access: 打开了
Play Android Developer API
,应该和这个无关 - License testing得添加设备登录的google账号
App -> Setup -> Advanced settings -> App availability
设置为Published
App -> Setup -> Advanced settings -> Managed Google Play
设置为Turn on
下面的留空就行
react-native-paper
- material-ui在react-native平台的替代品,同样遵循material design,但是最后不推荐,集成的本来就不多,还不大好用
- 在使用Menu.Item的时候,如果要自定义menu和整个container的高度,需要设置minHeight和maxHeight才行,不知道为啥container会默认设置为100,源码里没看到哪个地方有设置
ActivityIndicator
就是一个loading图标,非常好用
react-native-picker
- 滚动时间或者select选择器
react-native-qr-decode-image-camera
- 至少从图片里面解析二维码只有这个好用点
react-native-share
- 弹出原生的分享组件,例如分享airdrop,保存到文件夹
react-native-swipeable-list
esthor/react-native-swipeable-list: 官方推荐的这个,但是很多错误,不能用了
可以左滑菜单的列表
react-native-snap-carousel
- 触摸滚动组件,但是已经几年没维护了,且可以直接用原生的VirtualizedList替代
react-native-text-input-mask
- 比如电话号码输入的mask模式
- 如果出现TypeError: null is not an object (evaluating ‘RNTextInputMask’)in v3.0.0,需要添加这行配置到podfile文件:
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text', :modular_headers => true
swiper
- 轮播图片
- 非常强大,各种框架几乎都支持
@testing-library/react-native
- 测试框架
性能优化
最重要的是将需要变化的状态细化到单独的组件,这样状态变化时就不会影响到其他不需要的地方
Touchable系列组件不能很好的响应
- 通过
requestAnimationFrame
,可以让组件的透明度改变效果很快切换回来,而不会卡在那儿
1 | requestAnimationFrame(() => { |
开发原生相关问题
在真实设备上调试以及打包到真实设备
在真实设备上调试,只需要在Xcode
中Run
到你自己连接的设备即可,这时候安装在手机上面的,是和电脑上面模拟器出来的一模一样,也能进行调试,但是断开usb后应用不能使用。如果要将应用直接整体打包到设备上面,看看真实使用的效果,可以按照这个教程进行设置https://facebook.github.io/react-native/docs/running-on-device.html
,主要就是修改AppDelegate.m
中的jsCodeLocation
的值,将其改变成如下状态即可。
1 | jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; |
APP图标设置
参考Xcode
中的图标设置,也只能在xcode
中设置,即直接将图标拖如Images.xcassets
原生库
开发者会将很多原生库打包成一些静态库,或者由js直接封装好了的静态库。一般比较好的静态库都能够使用命令自动链接:react-native link 某已安装的具体库名
,如果手动链接可以参考文档linking-libraries-ios
TroubleShooting
“:CFBundleIdentifier” Does Not Exist: 可能是因为你的代码依赖的是老的
react native
或者node
版本或者xcode
版本,可以执行以下命令升级依赖:react nativeupgrade
undefined is not an object evaluating React.PropTypes.string: 仍然是版本的问题,新版的已经将
React.PropTypes
移到单独的库了(prop-types)。需要注意的是React.PropTypes.func
更改成了PropTypes.function
了,其他的名字没有改,只是位置变了。undefined is not an object(evalauating ‘WeChat.registerApp’): 引入
react-native-wechat
之后手动去linkNo bundle url present: 启动的时候报错,有以下几种解决方案:
- 全部关了以后,看看8081端口是否被占用,然后重新
react-native run-ios
- 上面方法多次尝试不行以后直接删除
node_modules
目录,重新安装依赖
- 全部关了以后,看看8081端口是否被占用,然后重新
isMounted(…) is deprecated warning: 目前来看,并没有什么解决方案。
闪退: 有如下几种情况
- 没有给API添加对应的权限,具体权限列表可以参见: Swift开发MacOS应用
_this._registerevents is not a function: 升级的时候没有顺便升级
react-native-cli
cross-env: command not found:
npm install cross-env
unable to load script from assets index.android.bundle: 这样做能够解决(来自于Stackoverflow):
1
2
3
4
5
6mkdir android/app/src/main/assets
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
react-native run-android
可以将上面的命令放到package.json的scripts中去,这样以后直接npm run android-linux即可
"android-linux": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && react-native run-android"**
SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.
**原因是没有定义android sdk的位置,首先下载android sdk或者安装android studio(会自动下载sdk),最后将地址写在local.properties
文件或者直接设置为环境变量ANDROID_HOME
**com.android.builder.testing.api.DeviceException: No connected devices! **得去android studio把安卓模拟器打开
react-native run-android
命令提示Android project not found. Maybe run react-native android first
,但是执行react-native android
却说命令没找到: 首先看当前目录有没有android
文件夹,如果没有,那么使用react-native eject
命令生成,如果有,那么就用android studio
来运行一次,看看是不是有哪些基础环境没有安装Invalid YGDirection ‘row’ should be one of: (inherit, ltr, rtl): 需要将
<Flex direction="row"
修改为<Flex flexDirection="row"
Print: Entry, ":CFBundleIdentifier", Does Not Exist
解决方法如下1
2
3
4首先关闭XCode
cd node_modules/react-native/third-party/glog-{X}.{X}.{X}/
./configure
然后重新打开xcdoe即可Text strings must be rendered within a
component : 首先最基本的,文字必须在text组件里面,但这还是比较容易排查,而不好排查的情况一般是我们在做判断的时候没有使用布尔值,例如1
2{icon && {icon}} // 这样会报错
{!!icon && {icon}} // 将对象转换为布尔值即可输入框键盘挡住了部分视图: 这时候需要使用
KeyboardAvoidingView
来包装一下view
,该组件可以自动根据键盘的高度,调整自身的height或底部的padding来避免遮挡,有时候也需要再配合ScrollView
来使用,注意它可以不需要在整个页面外层包装,可以只包裹住form那部分即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { useHeaderHeight } from '@react-navigation/elements'
const height = useHeaderHeight()
<KeyboardAvoidingView
behavior={Platform.OS == "ios" ? "padding" : "height"}
style={styles.container}
keyboardVerticalOffset={height + 47} // 如果发现高度差那么一点可以这样设置
>
...
</KeyboardAvoidingView>
<!--如果有时候KeyboardAvoingView不起作用,可以尝试https://www.npmjs.com/package/react-native-keyboard-aware-scroll-view,例如用react-native-google-places-autocomplete的时候-->
<KeyboardAwareScrollView extraScrollHeight={75}>
<GooglePlacesAutocomplete />
</KeyboardAwareScrollView>ARCHS[@]: unbound variable in Xcode 12或者YogaKit.modulemap not found: 需要把
Build Settings -> Architectures -> Excluded Architecture
设置成这样(来自Stackoverflow):You must have a keystore.properties file in the
/android/ folder or set the environments variables : Android目录下新建文件keystore.properities
,内容如下即可:1
2
3
4STORE_FILE=app.keystore
KEY_ALIAS=app_alias
STORE_PASSWORD=your_password
KEY_PASSWORD=your_passwordAndroid Studio build签名APK的时候报错index.js not found:可能是因为使用了typescript,文件现在是index.tsx,可以在
build.gradle
文件中指定entryFile
:1
2
3
4project.ext.react = [
enableHermes: false, // clean and rebuild if changing
entryFile: "index.tsx" // 指定为tsx文件
]Android Studio报错:ERROR: Could not find method compile() for arguments: 可能是依赖的包在调用老的java的api,找到错误日志中的文件,将
compile 'xxx'
修改为implementation 'xxx'
Android Studio真机测试报错: Unable to load script. Make sure you’re either running a Metro server (run ‘react-natvie start’) or that your bundle ‘index.android.bundle’ is packaged correctly for release. 如果metro没打开就start,如果打开了,可以尝试执行这个命令:
adb reverse tcp:8081 tcp:8081
**Could not resolve project :react-native-camera.**这是个已经没有维护的库了,参考doc,在
android/app/build.gradle
中添加missingDimensionStrategy 'react-native-camera', 'general'
即可xcrun: error: SDK “iphoneos” cannot be located: 尝试执行
sudo xcode-select --switch /Applications/Xcode.app
Command PhaseScriptExecution failed with a nonzero exit code: 如果无法查看错误详情,可以尝试运行Archive,可能会显示错误详情,可能就是下面这个问题,node路径没有找到
React-Native env: node: No such file or directory: 尝试执行
sudo ln -s "$(which node)" /usr/local/bin/node
**No simulator found with name “iPhone 13”**运行时可以指定模拟器的名称:
yarn ios --simulator="iPhone 14"
扩展阅读
浅谈前端移动开发(Ionic与React Native)