react-native
一、安装环境
我们可以查看安装的环境
react-native --version
react-native-cli: 2.0.1
react-native: 0.61.5
这一环节常见问题常见的问题都是 android studio 的配置问题
常见的如:
-
google api 连接不上的问题
解决办法: 在你的 \android\build.gradle 添加如下代码
allprojects { repositories { google() jcenter() } }
allprojects { repositories { google() jcenter() mavenCentral() mavenLocal() } }
-
连不上模拟器或者真机
Execution failed for task ':app:installDebug'.
>com.android.builder.testing.api.DeviceException: No connected devices!这种问题就查看你的模拟器是否连接,你的手机是否连接正常,并打开了开发者模式
-
gradle 的缓存问题:
task: app:transformNativeLibsWithMergeJniLibsForDebug FAILED
这种类型的问题,不一定就是报这个错误,你可以尝试一下以下命令,或许会对你有点帮助,
cd android && gradlew clean cd .. && npm run android
二、添加你需要的组件
这里我们用到了 react-native-vector-icons @ant-design/react-native 等
-
react-native-vector-icons 使用自定义字体
\src\local_modules\react-native-vector-icons\Fonts 添加我们自己的字体文件 iconfont.ttf
-
新建文件 src\local_modules\react-native-vector-icons\glyphmaps\iconfont.json 并写入对应的字体名称和 unicode
unicode 需要转换成十进制的
文件内容类型下面{ "mini-mn_pengyou": 58880, "mini-shouye": 58897, "mini-wo": 58891, "mini-sousuo": 58899 }
新建文件 \src\local_modules\react-native-vector-icons\iconfont.js,写入如下代码
import createIconSet from "./lib/create-icon-set"; import glyphMap from "./glyphmaps/iconfont.json"; const iconSet = createIconSet(glyphMap, "iconfont", "iconfont.ttf"); export default iconSet; export const Button = iconSet.Button; export const TabBarItem = iconSet.TabBarItem; export const TabBarItemIOS = iconSet.TabBarItemIOS; export const ToolbarAndroid = iconSet.ToolbarAndroid; export const getImageSource = iconSet.getImageSource;
这样我们就可以在项目里使用我们的自定义 icon 了
使用如下import Icon from "react-native-vector-icons/iconfont"; <Icon name="mn_pengyou">
三、UI 组件库的选择
这里面我主要对比了 native-base material-ui ant-design,
组件库 | star | 文档 | 扩展 | 风格 |
---|---|---|---|---|
native-base | 12k | 较好 | 一般 | 较老 |
material-ui | 3k | 较差 | 良好 | 良好 |
ant-design | 3k | 很好(唯一的中文文档) | 很好 | 良好 |
根据我司风格和使用体验实际使用,最终选择了 ant-design
四、路由选择
这个我是真的踩了很多坑,市面上的文章对我产生了很多误导,我们在 google 里搜 react-native route
映入你眼帘的就是react-navigation,而且你搜到的绝大部的文章都是关于这个的。
那么我们来看看这个路由的使用吧
先安装
https://reactnavigation.org/docs/en/getting-started.html然后看代码
https://reactnavigation.org/docs/en/hello-react-navigation.html
使用之后我们会发现这个组件的优点和缺点都非常突出
优点 |
---|
路由和 UI 结合,对于没有特殊 UI 需求的项目,非常方便 |
路由和 页面 绑定,对于路由不了解的用户,项目路由不复杂的项目不用去过多关注路由问题 |
缺点 |
---|
UI 写死,很多 UI 无法定制,比如你想使用你自定义的 tabbar,你会发现无处下手 |
路由和页面写死了,如果我们需要在页面以外使用路由,你就会发现你进入了一个黑洞,google 上各种在套路如何在页面以外使用路由的黑科技,慢慢找,或许能找到了 |
如果你是一个前端开发人员的话你一定会受不了这种使用方式,非常的愚蠢
那么我们怎么来处理路由呢,难道要自己写一个吗?
当然不是,正在我这么想的时候我突然意识到 react-router 好像就提攻略 native 下的路由
,网址如下 react-router
优点 |
---|
可以在任何组件或者页面使用路由 |
不与 UI 绑定,可以跟任何 ui 库配合 |
path 可以自定义 |
五、请求
5.1 http 配置
在使用网络之前我们需要做网络权限配置,需要在你的\android\app\src\main\AndroidManifest.xml
文件里
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
这样我们就可以愉快的使用 http 请求了。
那么你就会问了,我要使用 https 请求怎么办呢
5.2 https 配置
但是有时候你会发现及时做了如上配置还是无法使用 https 请求到数据,这个时候你要就要检查一下使用的接口是否做了认证,通过电脑浏览器访问是否能访问到,如果没有做认证,可能就需要通过以下方法来解决
- 等待请求的接口做 https 认证
- 修改 android 的请求方法,使之跳过 ssl 认证 ReactNative 如何忽略 Https 的证书验证
- 如果是临时使用可以通过 proxy 来代理请求
5.3 使用 fetch
react-native 使用的是 fetch,但是 react-antive 的 fetch 是没有超时机制的,我们需要自己做处理,你可以自己封装也可以使用三方库,当然我们在 react-native 项目里依然可以使用 web 中常用的 axios 库
六、Image 的配置
默认情况下,在 android 里 Image 不支持gif
图,
打开文件 \android\app\build.gradle
配置如下
对于 Rn< 0.6
dependencies: {
...
compile 'com.facebook.fresco:fresco:1.+'
// For animated GIF support
compile 'com.facebook.fresco:animated-gif:1.+'
// For WebP support, including animated WebP
compile 'com.facebook.fresco:animated-webp:1.+'
compile 'com.facebook.fresco:webpsupport:1.+'
}
对于 Rn >0.6
implementation 'com.facebook.fresco:animated-gif:1.12.0' //instead of
implementation 'com.facebook.fresco:animated-gif:2.0.0' //use
这样我们可以开始愉快的写 RN 代码了
七、flex 布局
react-native 中无法像 css 那样随意的修改样式,首先是布局,采用的是 flex 布局,
flex 的用法可以参考 http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
其次是他的样式是类似下面的样子
import { StyleSheet } from "react-native";
export default StyleSheet.create({
homeWrap: {
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#f5f5f5"
}
});
那么我们就会想知道到底有哪些属性是我们可以用的
interface ViewStyle {
backfaceVisibility?: "visible" | "hidden";
backgroundColor?: string;
borderBottomColor?: string;
borderBottomEndRadius?: number;
borderBottomLeftRadius?: number;
borderBottomRightRadius?: number;
borderBottomStartRadius?: number;
borderBottomWidth?: number;
borderColor?: string;
borderEndColor?: string;
borderLeftColor?: string;
borderLeftWidth?: number;
borderRadius?: number;
borderRightColor?: string;
borderRightWidth?: number;
borderStartColor?: string;
borderStyle?: "solid" | "dotted" | "dashed";
borderTopColor?: string;
borderTopEndRadius?: number;
borderTopLeftRadius?: number;
borderTopRightRadius?: number;
borderTopStartRadius?: number;
borderTopWidth?: number;
borderWidth?: number;
opacity?: number;
testID?: string;
/**
* Sets the elevation of a view, using Android's underlying
* [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).
* This adds a drop shadow to the item and affects z-order for overlapping views.
* Only supported on Android 5.0+, has no effect on earlier versions.
*
* @platform android
*/
elevation?: number;
}
interface FlexStyle {
alignContent?:
| "flex-start"
| "flex-end"
| "center"
| "stretch"
| "space-between"
| "space-around";
alignItems?: FlexAlignType;
alignSelf?: "auto" | FlexAlignType;
aspectRatio?: number;
borderBottomWidth?: number;
borderEndWidth?: number | string;
borderLeftWidth?: number;
borderRightWidth?: number;
borderStartWidth?: number | string;
borderTopWidth?: number;
borderWidth?: number;
bottom?: number | string;
display?: "none" | "flex";
end?: number | string;
flex?: number;
flexBasis?: number | string;
flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
flexGrow?: number;
flexShrink?: number;
flexWrap?: "wrap" | "nowrap" | "wrap-reverse";
height?: number | string;
justifyContent?:
| "flex-start"
| "flex-end"
| "center"
| "space-between"
| "space-around"
| "space-evenly";
left?: number | string;
margin?: number | string;
marginBottom?: number | string;
marginEnd?: number | string;
marginHorizontal?: number | string;
marginLeft?: number | string;
marginRight?: number | string;
marginStart?: number | string;
marginTop?: number | string;
marginVertical?: number | string;
maxHeight?: number | string;
maxWidth?: number | string;
minHeight?: number | string;
minWidth?: number | string;
overflow?: "visible" | "hidden" | "scroll";
padding?: number | string;
paddingBottom?: number | string;
paddingEnd?: number | string;
paddingHorizontal?: number | string;
paddingLeft?: number | string;
paddingRight?: number | string;
paddingStart?: number | string;
paddingTop?: number | string;
paddingVertical?: number | string;
position?: "absolute" | "relative";
right?: number | string;
start?: number | string;
top?: number | string;
width?: number | string;
zIndex?: number;
/**
* @platform ios
*/
direction?: "inherit" | "ltr" | "rtl";
}
interface ShadowStyleIOS {
shadowColor?: string;
shadowOffset?: { width: number; height: number };
shadowOpacity?: number;
shadowRadius?: number;
}
export interface TransformsStyle {
transform?: (
| PerpectiveTransform
| RotateTransform
| RotateXTransform
| RotateYTransform
| RotateZTransform
| ScaleTransform
| ScaleXTransform
| ScaleYTransform
| TranslateXTransform
| TranslateYTransform
| SkewXTransform
| SkewYTransform
)[];
transformMatrix?: Array<number>;
rotation?: number;
scaleX?: number;
scaleY?: number;
translateX?: number;
translateY?: number;
}
更多详情,可以打开类似这个地址 C:\Users\用户名\AppData\Local\Microsoft\TypeScript\3.7\node_modules\@types\react-native\index.d.ts
查看
八、安卓打包
https://reactnative.cn/docs/signed-apk-android/
常见问题:
he following project options are deprecated and have been removed: android.useDeprecatedNdk.
API 'variantOutput.getProcessManifest()' is obsolete and has been replaced with 'variantOutput.getProcessManifestProvider()'. It will be removed at the end of 2019.
类似这样的警告
详情参考 Android 中一些过时 API 的汇总以及处理(持更)。
九、安卓发布
注册 => 登录 => 添加应用 => 填写 app 信息 => 提交审核
我们还有内测发布地址
蒲公英
十、 codepush
简单介绍一下 react-native-update
$ cd android
$ gradlew aR
$ pushy uploadApk android/app/build/outputs/apk/release/app-release.apk # 推到push server
$ pushy bundle --platform <ios|android> # 推送更新
十一、移动端本地服务
这是后面加的内容,是对 webview 的优化,当我们的 webview 要加载的内容比较多,可以把文件打包进 apk 里
优点
- 减少用户的流量消耗,
- 同时提高加载速度, 减少用户的等待时间
- 减轻服务器压力,降低流量消耗
缺点
- 增大 app 的体积 (跟优点比起来,微不足道)
需求
- 一个能跨平台的静态服务器 (必须)
- 最好是能提供 proxy 的功能 (不必须),如果接口采用的是 nginx 分发的就需要,并且还要相应的后台开放白名单,如果本身网页版就是通过 cors 访问的,那就不要这个功能
我们找到了这个组件 react-native-static-server
我们来看他的示例代码
import StaticServer from "react-native-static-server";
let server = new StaticServer(8080, path, {
localOnly: true, // 是否只对本app有用
keepAlive: true // 当app在后台运行时,是否要保持
});
// Start the server
server.start().then(url => {
console.log("Serving at URL", url);
});
// Stop the server
server.stop();
// Check if native server running
const isRunning = await server.isRunning();
// isRunning - true/false
注意: 这个服务器是监听在 app 安装之后的目录 例如 android 上就是 /data/data/com.packagename/ 这个文件夹下
现在服务器起来了,那我们就要把文件给他了,我们以 android 为例,同时还能满足热更的要求, 我们的想法是这样的
build.zip -> android/src/main/assets/build.zip -> /data/data/com.packagename/files/www
那我们需要一个跨平台的文件库,
react-native-fs
还需要一个文件解压的库 react-native-zip-archive
剩下的看代码
// 声明 server
import StaticServer from "react-native-static-server";
import RNFS from "react-native-fs";
import { zip, unzip, unzipAssets, subscribe } from "react-native-zip-archive";
const path = RNFS.DocumentDirectoryPath + "/www";
class LiveServer {
startServer = async () => {
await this.copyAssetsToDocumentDirPath();
this.server = new StaticServer(8080, path, {
localOnly: true,
keepAlive: true
});
this.server.start().then(url => {
console.log("static Server running", url);
});
};
// 把文件www.zip从assets 搬到 path 文件夹下,并解压
copyAssetsToDocumentDirPath = async () => {
let files = [];
try {
files = await RNFS.readDir(path);
} catch (error) {
await RNFS.mkdir(path); // 新建www 文件
files = await RNFS.readDir(path);
}
files.forEach(item => {
RNFS.unlink(item.path); // 删除原来的www 文件夹
});
await RNFS.copyFileAssets("www/www.zip", path + "/www.zip"); // 拷贝文件到
unzip(path + "/www.zip", path); //解压
};
reStartServer = async () => {
this.server.stop();
this.startServer();
};
}
const liveServer = new LiveServer();
// 使用
liveServer.startServer(); // 启动
startServer.reStartServer(); //重启服务
十二、react-native 接口缓存
接口数据的缓存一般是有两个目的
目的 |
---|
1. 降低请求数量,减轻服务器并发压力 |
2. 减少流量消耗 |
在 pc 端上我们采用的是 indexDB, 但是在移动端没有这个这个数据库,所以不能使用了
于是就找到了 realm
兼容 |
---|
兼容 ios 和 android |
支持 React Native 0.31.0 和更高版本 |
请注意,Expo 不支持 Realm,create-react-native-app 将无法使用。
支持下列基本类型 | 对应的 js 的数据类型 |
---|---|
bool | boolean |
int | number 存储为 64 位 |
double | number 存储为 64 位 |
float | number 存储为 32 位 |
string | string |
data | ArrayBuffer |
date | Date |
下面简单介绍一下他的用法
import Realm from "realm";
// Define your models and their properties
const CarSchema = {
name: "Car",
primaryKey: "id", // 设置主键
properties: {
id: "string",
make: "string?", // ? 表示可选
model: "string",
miles: { type: "int", default: 0 } // 设置默认值
}
};
// 官方推荐的用法 异步
Realm.open({ schema: [CarSchema] }).then(realm => {
const cars = realm
.objects("Car")
.filtered("miles > 1000")
.sorted("miles"); // 查询
// 如果需要插入 或者更新数据 必须要在write 事务里处理
realm.write(() => {
realm.create("Car", {
id: "12",
make: "Honda",
model: "Accord",
drive: "awd"
}); // 插入数据
realm.create(
"Car",
{ id: "12", make: "Honda", model: "Accord2", drive: "awd2" },
true // v 4.0 后改为 "modified"
); // 更新数据
realm.delete({
id: "12"
}); // 删除 id 为12 的这条数据
});
});
// 我们也可以使用同步方法 // 官方不太推荐
const realm = new Realm({ schema: [CarSchema] });
const cars = realm
.objects("Car")
.filtered("miles > 1000")
.sorted("miles"); // 查询
// 如果需要插入 或者更新数据 必须要在write 事务里处理
realm.write(() => {
realm.create("Car", {
id: "12",
make: "Honda",
model: "Accord",
drive: "awd"
}); // 插入数据
realm.create(
"Car",
{ id: "12", make: "Honda", model: "Accord2", drive: "awd2" },
true // v 4.0 后改为 "modified"
); // 更新数据
realm.delete({
id: "12"
}); // 删除 id 为12 的这条数据
});
详情可参考 Realm api 文档