手抓手,带你入门react-native

react-native

一、安装环境

按照文档安装开发环境

我们可以查看安装的环境

react-native --version
react-native-cli: 2.0.1
react-native: 0.61.5

这一环节常见问题常见的问题都是 android studio 的配置问题

android 开发者文档

常见的如:

  1. google api 连接不上的问题

    解决办法: 在你的 \android\build.gradle 添加如下代码

    allprojects {
         repositories {
             google()
             jcenter()
         }
     }
    
    
    allprojects {
         repositories {
             google()
             jcenter()
             mavenCentral()
             mavenLocal()
         }
     }
    
  2. 连不上模拟器或者真机

    Execution failed for task ':app:installDebug'.
    >com.android.builder.testing.api.DeviceException: No connected devices!

    这种问题就查看你的模拟器是否连接,你的手机是否连接正常,并打开了开发者模式

  3. gradle 的缓存问题:

    task: app:transformNativeLibsWithMergeJniLibsForDebug FAILED

    这种类型的问题,不一定就是报这个错误,你可以尝试一下以下命令,或许会对你有点帮助,

    cd android && gradlew clean
    
    cd .. && npm run android
    

二、添加你需要的组件

这里我们用到了 react-native-vector-icons @ant-design/react-native 等

  1. 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


      iconfont.png

      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";
    
    

三、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,而且你搜到的绝大部的文章都是关于这个的。
那么我们来看看这个路由的使用吧

  1. 先安装
    https://reactnavigation.org/docs/en/getting-started.html

  2. 然后看代码
    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 文件里



这样我们就可以愉快的使用 http 请求了。

那么你就会问了,我要使用 https 请求怎么办呢

5.2 https 配置

https 配置

但是有时候你会发现及时做了如上配置还是无法使用 https 请求到数据,这个时候你要就要检查一下使用的接口是否做了认证,通过电脑浏览器访问是否能访问到,如果没有做认证,可能就需要通过以下方法来解决

  1. 等待请求的接口做 https 认证
  2. 修改 android 的请求方法,使之跳过 ssl 认证 ReactNative 如何忽略 Https 的证书验证
  3. 如果是临时使用可以通过 proxy 来代理请求

5.3 使用 fetch

react-native 使用的是 fetch,但是 react-antive 的 fetch 是没有超时机制的,我们需要自己做处理,你可以自己封装也可以使用三方库,当然我们在 react-native 项目里依然可以使用 web 中常用的 axios 库

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;
  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

社区 codepush
社区 文档

简单介绍一下 react-native-update

$ cd android
$ gradlew aR
$ pushy uploadApk android/app/build/outputs/apk/release/app-release.apk  # 推到push server
$ pushy bundle --platform   # 推送更新

十一、移动端本地服务

这是后面加的内容,是对 webview 的优化,当我们的 webview 要加载的内容比较多,可以把文件打包进 apk 里

优点

  1. 减少用户的流量消耗,
  2. 同时提高加载速度, 减少用户的等待时间
  3. 减轻服务器压力,降低流量消耗

缺点

  1. 增大 app 的体积 (跟优点比起来,微不足道)

需求

  1. 一个能跨平台的静态服务器 (必须)
  2. 最好是能提供 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 文档

你可能感兴趣的:(手抓手,带你入门react-native)