React Native 实战:构建电商 App

课程介绍

React Native 充分利用了 Facebook 的现有轮子,是一个很优秀的集成作品,使用 RN 即可做到无需编译就能远程热更新 App,再加上友好的开发方式、快到爆炸的开发效率、RN 已经完爆了其他的 App 开发方式,即使是一个初入前端的开发者也能迅速开发一套 iOS、Android 双平台的 App。

文章中将分三个部分讲解 RN 的开发。通过开发一个比较完整的电商 App 来逐步带领读者走入 React Native 的世界。读者可以从中汲取到完整的项目经验,从菜鸟到精通也只需要学完这个达人课即可。

第一部分主要讲述封装基本组件、简单页面的开发:这个阶段主要解决开发中遇到的兼容问题,在文中会提出解决方案、避免无用的代码。

第二个部分将讲述复杂的业务逻辑:让第一次做开发的读者也能非常快速的适应业务形态,让开发有底气、不再受制于产品的约束。

第三部分将讲述性能的极致优化、热更新、统计等这些 App 必须的东西,让开发的 App 能够真正的比得上原生 App 和混合 App。

作者介绍

郭方超,技术总监、架构师、产品、运营。现担任泽旭商贸技术总监、达令前端架构师职务,有过多年的前端、后端开发经验,擅长 Node.js、.Net、Java 等开发技术。

开发(参与)过的项目如下: 泽旭商贸 PC、移动、微信、App 项目,达令家 App 的开发,心燃灵动前端库、微信端电商框架的开发,来吧旅行前端项目。开源了以下项目:React-Native 的多个组件、React-Native 的监控系统、Node 爬虫框架、模块化前端脚手架、微信小游戏引擎。

课程内容

导读:为什么选择 React Native

在所有的技术选型之前都有一个为什么。

为什么我选择了 React-Native?选择这个技术到底给我带来了什么样的技术福利?如果你正在考虑 React-Native,或者还在研究选择什么样的技术实现自家的 App,不妨看看这个课程,说不定你就有了不一样的感受。

注:React-Native 以下简称 RN。

优点

选择 RN 之前我也看过了其他的几种技术,之前也使用过其他的技术做 App,比如 HBuilder 的框架,非常的方便、非常的好用、什么都不需要管,但是开发完之后发现性能很差,做一个稍微复杂点的项目就非常上火,后来切了原生,但是使用 Java 做开发也有非常多的问题,开发效率很低。

后来我选择了 RN,它使用 JS 更新虚拟 DOM,通过一个桥接器将需要更新的结果通知到 UI 层,让 Native 执行 UI 的改变。简单来说,就是用 JS 做驱动开发一个类原生的 App,所有的渲染都是和原生一样的,一下子就将原生开发和 JS 开发连接起来。通过这么一个模式,将传统的 Java 者 OC 开发转变成了简单易懂的前端 JS 开发,这是移动开发的一大进步,避免了一个 App 多个平台多套代码的尴尬,同时提升了开发效率,将移动开发带入了一个新的层面。

enter image description here

性能相似

很多人说 RN 的性能比不上原生的 App,这个说法是要看具体的场景。

在一般的应用场景下 RN 的表现和原生 App 是没有太大的差别的,一个 App 也不会到处都是复杂的交互效果。一些简单的点缀动画再加上列表图片等才是一个 App 最常见的内容,这种情况下它们之间的表现是一样的。

RN 本身只是使用 JS 处理了 UI 渲染之前的一些逻辑,在最终的渲染上其实使用的还是原生的逻辑,尤其是渲染完成之后更是和原生的没有半点区别。

我们的案例是一个电商项目,主要渲染逻辑是首页的自定义模板、无限加载的列表等。目前最大的性能瓶颈其实是在事件的优化上,优化之后用户已经感知不到和原生的区别了,我们会在后面的部分提到性能优化,将一个粗糙的 App 通过简单的方法提高 10 倍性能,再通过另外一个稍微复杂的方法减低内存占用。

开发效率高

通常情况开发一款 App 需要发布在 Android 和 iOS 两个平台,导致的结果就是一个 App 有两个团队、两套代码,界面几乎一样,为什么不能使用一套代码呢?之前也有大神使用各种手段达成这个目标,但是并不是很理想。

由于使用熟悉的 React 和 JSX 的模式,开发者只需要有前端知识就可以很迅速的上手一个 RN 项目,如果再学一些实战的例子,稍微复杂一些的项目也难不倒各位前端开发者。

Debug 超级方便,一边开发一边看效果再也不是梦。

快速热更新

RN 生成的 JS 文件,只要不涉及原生功能的增减,已经发布的 App 完全不需要重新安装即可完成新版功能的上线,用户只需打开 App 就能体验到最新的 App,省去了下载重装的各种麻烦,把 App 的更新做到了和网页更新一样的方便快捷。

使用 RN 就能达到既有原生的所有能力,又有类似浏览器上的快速更新能力,同时还可以接入各种定制好的网页,将 App 的自由度提高到一个非常高的地步。

大公司背书

RN 的开发者是 Facebook,背靠大树好乘凉,社区更疯狂,Facebook 本身也在尝试使用 RN 技术开发自己的 App,RN 一定会越来越完善,截止写这篇文章的时候 RN 已经更新到了 0.53.0。

RN 本身也是开源的,所有的源代码都是可以看到的,社区的讨论也是比较热烈的,现在可能中文文档还比较少,未来随着开发者的努力,这些坑都会填起来的。

其实最开始的时候也没有想很多,仅仅是冲着 RN 可以快速开发,上线快体验好,经过了这么长时间的开发,我更加喜欢 RN 的这种开发方式,项目中也填了各种各样的坑,后面就用一个实际的例子来展示 RN 是怎样开发的吧。

RN 的缺点

升级快

RN 本身其实还处于测试版,开发组经常会升级 RN,解决一些遗留或者隐藏的 bug,在这个过程中就导致了 RN 本身升级非常快,开发者在使用 RN 开发 App 的过程中应该尽量提高自己的版本,不需要一直是最新的,只要能够跟的上 Facebook 的节奏即可。

自己搞定的问题也是可以合并入 RN 的源代码里的,不要一味的等 RN 更新,有些问题自己解决更方便,建议会 Android 或者 iOS 同学自己动手。

动画难

这里的难指的是复杂的动画在开发中很难去优化,尤其是开发者懂前端但是不懂原生的情况下,好在常见的 App 也不需要多么复杂的动画,一般使用位移变换就足够了,太复杂的动画建议使用 RN 的 SVG 组件来做。

WebView 难用

RN 自带的 WebView 跟浏览器有一定的差别,App 经常要打开一些网页,可能在开发的时候一切正常,但是到了 RN 里面就会有一些奇怪的问题,主要还是受到系统浏览器的影响,会有一些兼容方面的问题,这种情况下不如微信使用自己研发的浏览器,可以畅快的使用 ES 6 之类的新技术。

需要原生支持

简单的东西和界面的展现已经完全放手给了开发者,但是还是有一些功能只能原生去实现,如果原生部分的开发者对 RN 不太了解可能会给 App 带来不可预知的 bug,好在大多数开源看只需要执行 Link 命令就可以把原生部分也安装好。

技术都会有优点和缺点,选择合适的技术才能给项目带来长久的生命力。

其他技术

Weex

核心思想上,这两家其实并没有什么区别,Weex 也可以算是站在 RN 的肩膀上起步的,目前活跃度不高,大多数是在观望中。

开发框架

Weex 使用 Vue,熟悉 Vue 的开发者可能会更熟悉。

RN 使用 React,都是 Facebook 出品,框架融合上会更方便一些,它们都是组件化开发,都属数据绑定,都有虚拟 DOM,社区同样活跃,使用人数也都非常多。

学习成本

React 的 JSX 初期会比较难上手,CSS 的写法也跟前端的样式写法不一样,Weex 使用模板的形式,直接 html+css 开发,上手会稍微简单一些。

异步

Weex 只支持 callback 的形式,RN 支持 promise 的形式,这些都是可以解决的,不是什么问题。

社区

RN 开源早,有 Facebook 支持,社区的组件库已经比较丰富,社区活跃度比较高。Weex 开源晚,社区活跃不高,以阿里系比较多。

Flutter

Flutter 是 Google 推出的一个跨平台(Android 和 iOS)移动开发框架,使用的是 Dart 语言。

Flutter 的目标是用来创建高性能、高稳定性、高帧率、低延迟的 Android 和 iOS 应用,并且开发出来的应用在不同的平台用起来跟原生应用具有一样的体验。不同的平台的原生体验应该得到保留,让该应用看起来同整个系统更加协调;不同平台的滚动操作、字体、图标 等特殊的特性应该和该平台上的其他应用保持一致,让用户感觉就像操作原生应用一样。比如,返回图标 Android 和 iOS 是不一样的;滚动内容滚动到底的反馈也是不一样的。

兼容

Flutter 不使用系统提供的组件,自己实现了一套渲染机制,所以在性能优化、跨平台方面表现优秀。实际体验上,性能比 RN 要高不少。

enter image description here

RN 最终调用的还是系统的组件,虽然 Facebook 已经很努力了,但是在某些时候还是会有兼容性需要处理。

组件

Flutter 内置了对 Material Design 的支持,给开发者提供了丰富的 UI 控件库选择,同时所有的组件都有扩展,保持了很高的灵活性。

RN 通过 React 也做到了组件式开发,跟 Flutter 相比,多了一个桥接器的转换,性能上肯定不如 Flutter。

开发语言

Flutter 使用 Dart 实现,Dart 号称要完全取代 JS,不过目前离这个目标还非常远,初期上手还是有一些难度的。

RN 使用 JS 开发,做过前端的都非常熟悉,上手很容易。

enter image description here

Flutter 现在还在实验阶段,不排除 Google 使用别的框架替换它的可能性,Dart 语言也处于成长阶段,只有 Google 的浏览器在支持,或许在 Flutter 持续发展到一个阶段之后,才会有很多支持者。

在写文章的时候 Google 放出了第一个测试版,感兴趣的可以下载下来玩玩儿。

相比于其他几种技术,RN 是目前社区最活跃,开发效率最高的一种选择,选择 RN 也是需要在一个比较短的时间内能够完成 App 的开发。尤其现在前端开发者可以非常容易的从网页开发转到 App 开发上。对于我们包含 App、微信、小程序这样的三个平台更是需要 RN 这样的技术,一个团队就可以维护项目的持续增长。

如果需要 RN 来开发自己的项目,那就继续往下看吧,我们将从简单的界面开发、数据更新等开始逐步深入,后面涉及到性能优化、自定义原生部分等。

第01课:项目初始化

知识点:

  • 快速创建 RN 项目
  • 添加使用路由功能
  • 不要在 RN 中使用的功能

从这里开始,我们将一步一步的创建一个可以真正使用的 App:第一部分讲述开发一个 App 的大致过程,第二部分将开始优化性能、开发效率等,第三部分介绍添加热更新、支付、分享这些功能。大多数第三方组件可以很方便的 link 到项目里,部分需要手动导入甚至主动开发一些东西,这里也会在用到的时候讲出来。

创建项目的前提条件

使用 React-Native 创建一个新的项目请确保电脑上已经满足下面的这些条件。

  • nodejs:RN 的所有库都是从 NPM 上安装的,请确保电脑上已经安装了 NPM,可以使用npm -v来查看当前是否已安装,当前项目使用的 Node 版本是 8.9.3,NPM 的版本是 5.5.3。
  • react-native-cli:这个通常用作 RN 的初始化和启动模拟器等,使用 NPM 可以安装到电脑上。
  • Python:RN 里面有些脚本是使用 Python 写的,请确保电脑上已经安装了 Python 2.7 以上的版本。
  • JDK 1.8:安卓项目需要使用 JDK 1.8,请在电脑上安装好,安装的教程可以搜索其他资料。
  • Android Studio:调试以及编译安卓代码需要使用到,请在安装 Android Studio 之后安装好 Android SDK 以及模拟器,模拟器可以使用市面上的安卓模拟器,它们普遍比自带的模拟器要快,闲麻烦的读者可以直接连接手机调试。在安装好 IED 之后请下载好需要使用到的 Android SDK,下载 SDK 需要科学上网或者把下载地址替换成国内的几个下载源。
  • Git:后面的项目会加入到 Git 中,使用 Git 做版本管理的好处不言而喻。
  • Xcode(仅 iOS 项目中):安装了 Xcode 才能使用 iOS 模拟器,iOS 模拟器下的开发速度要明显优于安卓下,后面的开发过程大多数也是在 iOS 模拟器中进行的。在模拟器中的表现都差不多,有些不一样的效果可以在看到的时候单独调试。
  • Watchman(仅 Mac 系统用到):Watchman 用来监听文件变动等,Mac 下必须安装 Watchman。
  • VSCode:文中演示项目使用的编辑器是 VSCode,这里也推荐使用 VSCode 开发前端项目。

创建项目

我们先在本地创建一个可运行的项目,同时这个项目会加入到 Git 的版本管理中。

(1)执行react-native init anxintao --version 0.53.0

enter image description here

  • 图中1代表之前安装的 react-native-cli 的命令。
  • 图中2代表初始化命令。
  • 图中3代表项目的名称,这里是anxintao
  • 图中4代表指定 RN 的版本号,这个参数不传默认使用最新版。
  • 图中5代表 RN 具体使用的版本号。

(2)使用 VSCode 打开项目,在项目根目录下执行命令启动初始化之后的项目,Mac 下推荐react-native run-ios,Window 下推荐react-native run-android启动默认的项目。如果能启动说明项目初始化完成,否则说明项目的某些东西没有安装好。

enter image description here

这里推荐把启动的命令写入到 package.json 文件中,比如,输入npm run ios即可代替原来的react-native run-ios,输入命令的速度快了不少,也可以给 VSCode 安装一个启动 RN 的插件,不过效果跟输命令差不多,具体要看个人习惯了。

(3)到这里就说明项目创建成功了,这个项目现在还很简单,它的原生部分只有一个简单的空壳,这个空壳仅仅是初始化了一个 RN 的 Activity,所有的 JS 都是运行在根视图上的。

(4)这里注意一下,新建的项目提示了按键可以刷新页面或者调出菜单,这显示的是 iOS 模拟器,按键为command R刷新和command D调出菜单。

添加路由

一个完整的项目不能没有路由,这里使用 React Navigation

在写文章的时候已经有一个路由组件react-native-navigation热度超过了 react-navigation,它更多的使用的是原生的路由切换,效果更好,想用的读者可以去尝试一下。

  • 安装路由,在根目录下执行npm install --save react-navigation

  • 在根目录下新建src目录,所有页面放入这个文件夹下。

  • 新建一个首页,给后面的路由调用,页面路径为根目录/src/home/index.js

    'use strict';import React from 'react';import {    StyleSheet,    View,    Text} from 'react-native';export default class extends React.Component {    render() {        return <View style={{ marginTop: 200 }}>            <Text>这是首页</Text>        </View>    }}

修改根目录下的index.js,添加整个项目的路由。

import { AppRegistry } from 'react-native';import Pages from './src';//启动AppRegistry.registerComponent('anxintao', () => Pages);

在 src 目录下新建index.js文件,在这个文件里添加路由,这里从简单的一个页面开始。

'use strict';import React from 'react';import {    StyleSheet} from 'react-native';//添加路由组件import Navigation from 'react-navigation';//添加展示用的首页import Home from './home/index'//创建路由const Pages = Navigation.StackNavigator({    'Home': {        screen: Home    }}, {    //这里做了一个页面跳转的动画    transitionConfig: () => ({        screenInterpolator: sceneProps => {            const { layout, position, scene } = sceneProps;            const { index } = scene;            //设置页面跳转的动画            const translateX = position.interpolate({                inputRange: [index - 1, index, index + 1],                outputRange: [layout.initWidth, 0, 0]            });            const opacity = position.interpolate({                inputRange: [index - 1, index - 0.99, index, index + 0.99, index + 1],                outputRange: [0, 1, 1, 0.3, 0]            });            return { opacity, transform: [{ translateX }] };        }    }),    navigationOptions: {        header: null    }});//创建一个自己的容器,方便以后对路由做一些处理export default class extends React.Component{    constructor(props) {        super(props);    }    render() {        return <Pages onNavigationStateChange={this.listenChange.bind(this)}></Pages>;    }    //监听路由的跳转    listenChange(state1, state2, action) {    }}

添加完成之后删除掉初始化项目之后的 App.js,这个时候在模拟器中使用快捷键command+R即可刷新刷新页面。

479e89e0-1876-11e8-8087-9b6b3b447adf

至此就完成了简单的路由设置,之后只需要添加页面并在路由中注册即可使用。

路由升级版

简单的路由并不能起到很好的作用,我们还是创建一个更实用的路由吧,比如带 3 个 tab 切换的首页,这也是大多数 App 使用套路。

添加 4 个 tab 切换页,我们假定未来需要 4 个切换页,分别是首页、分类页、购物车、个人中心,在 home 下分别创建他们。

084841b0-187a-11e8-8087-9b6b3b447adf

修改路由所在的 index 文件,引入下面要用到的几个组件和页面。

添加新加入的页面:

import React from 'react';import {    StyleSheet,    Image} from 'react-native';//添加路由组件import Navigation from 'react-navigation';//添加展示用的首页import Home from './home/index'import Products from './home/products'import Shop_Cart from './home/shop_cart'import My from './home/my'

创建底部的样式:

//创建tab页的顶部样式const styles = StyleSheet.create({    tab: {        height: 40,        backgroundColor: '#fbfafc',        borderTopColor: '#efefef'    },    tabIcon: {        width: 20,        height: 20    },    tabLabel: {        marginBottom: 4    }})

创建一个 tab 路由,为了简单这里只展示 2 个页面的,具体的代码可以去 Git 仓库查看。

//创建首页的tab页const Tabs = Navigation.TabNavigator({    'Home': {        screen: Home,        navigationOptions: ({ navigation, screenProps }) => {            return {                tabBarLabel: '首页',                tabBarIcon: (opt) => {                    if (opt.focused) return <Image source={{ uri: require('./images/tab-home-active') }} style={styles.tabIcon}></Image>;                    return <Image source={{ uri: require('./images/tab-home') }} style={styles.tabIcon}></Image>;                }            }        }    },    'Products': {        screen: Products,        navigationOptions: ({ navigation, screenProps }) => {            return {                tabBarLabel: '产品分类',                tabBarIcon: (opt) => {                    if (opt.focused) return <Image source={{ uri: require('./images/tab-products-active') }} style={styles.tabIcon}></Image>;                    return <Image source={{ uri: require('./images/tab-products') }} style={styles.tabIcon}></Image>;                }            }        }    },}, {    //设置tab使用的组件    tabBarComponent: Navigation.TabBarBottom,    //点击哪个才加载哪个tab里的页面    lazy: true,    //设置tab放在界面的底部    tabBarPosition: 'bottom',    //设置tab里面的样式    tabBarOptions: {        style: styles.tab,        labelStyle: styles.tabLabel,        activeTintColor: '#d0648f'    }});

替换 Pages 里的第一个页面为刚才创建的 tab 路由。由于默认加载第一个,所以需要将第一个设置成 tab 页。

'Tabs': {        screen: Tabs }

现在再刷新模拟器,就会发现底部的 tab 切换已经好了,点击可以切换不同的页面。

da200e80-187e-11e8-8087-9b6b3b447adf

这里将图片转化成 base 64 的方式再引入到图片组件中,好处是打包之后会变成一个整体,坏处是打包之后的 bundle 文件会变大,做增量更新也比较麻烦。

不推荐使用的东西

  • 投影:安卓不支持投影,在开发的时候如果没有必要就使用别的方式代替吧,比如使用图片代替投影。
  • 边框色:在长列表中尽量不要使用边框色,在某些安卓手机下会闪退。
  • 使用了圆角的情况再使用背景色:iOS 手机会出现边框颜色异常或者异常色块,去掉背景即可。
  • 过于深层次的结构。
  • 过于频繁的刷新 state。
第02课:封装自己的库

知识点:

  • 封装一个自己的日志,代替原生的 console
  • 封装一个自己的请求库,代替原生的 fetch
  • 自适应方法,兼容各种手机屏幕
  • 封装一个本地存储
  • 集成 Toast 弹出提示
  • 让 iOS 支持 HTTP 地址
  • 安卓需要证书才能编译

在正式开始之前,我们先封装几个要用到的库。

自定义本地日志

自定义日志的一个好处就是省的每次都要手动注释 console,而且还可以同时将日志存在本地,或者发到日志服务器,一个方法一举多得了。

在 src 目录下新建一个 utils 文件夹,封装一个日志输出类,开发模式下使用console.log命令,正式情况下记录在变量中,方便在手机上查看日志。

新建一个 log.js 文件,路径为根目录/src/utils/log.js

80223590-1886-11e8-8087-9b6b3b447adf

新建一个数组变量logs,用来临时存放日志信息。

98604390-1886-11e8-8087-9b6b3b447adf

将日志分成信息、警告和错误 3 种,分别给出 3 个可调用的方法,同时给第一个参数加一个好看的颜色。

cc840ad0-1886-11e8-8087-9b6b3b447adf

在 index 中引入日志组件,写几个方法来看看调用的结果。

enter image description here

这里稍微定义一下日志的要求,参数 0,字符串;参数 1,对象;参数 2,字符串。

enter image description here

自定义请求

RN 默认提供了 fetch 方法去请求远程数据,我们再封装一次,这个方法将会针对现有的项目做封装,在使用请求的时候能够更适合、更方便。这里使用 header 保存了一些临时的变量,算是一个小小的全局缓存吧。

创建 request.js 文件,目录:根目录/src/utils/request.js

80223590-1886-11e8-8087-9b6b3b447adf

将请求 header 里的信息单独出来,每次请求都需要带上这个共享 header 数据。

29975590-1889-11e8-8087-9b6b3b447adf

创建一个 Request 类,并将这个类对外公开,这里将请求初始化一次,以后用到别的请求的时候也可以单独实例化一次。

/** * 请求库 */class Request {}export default new Request();

每次请求都将 header 中的内容带入请求中,单独检测 httpcode 和后端返回的 code 值,这里可以直接做权限检测,在需要的时候跳转到登录页。

/** * 请求库 */class Request {    /**    * 检测返回状态码    * @param {*} status     * @param {*} res     */    async _checkStatus(status, res, url) {        if (status !== 200) {            logWarm('请求失败参数', await res.text(), url, headers);            throw new Error('网络连接失败,请检查网络');        }    }    /**    * 检查后端返回的状态码    * @param {*} status     */    _checkAppStatus(json, url) {        if (json.status != 0) {            logWarm('返回状态报错', json, url);            throw new Error(`${json.errorMsg}`);        }    }    /**     * 内部实现网络请求     * @param {*} url      * @param {*} options      */    async _request(url, options, type) {        url = url.indexOf('http') == 0 ? url : url.indexOf('/api') == 0 ? domain + url : baseUrl + url;        let res = await fetch(url, options);        this._checkStatus(res.status, res, url)        if (type === 'json') return await this._jsonFactory(res, url, options)        return await this._jsonFactory(res, url, options)    }    /**         * 处理json数据         * @param {*} res          * @param {*} url          */    async _jsonFactory(res, url, options) {        let json;        let txt = '';        try {            txt = await res.text();        } catch (e) {            log('未拿到返回字符串', { url: url, txt: txt });            throw new Error('数据格式错误');        }        try {            json = JSON.parse(txt);        } catch (e) {            logErr('返回数据格式错误', { url: url, txt: txt });            throw new Error('数据格式错误');        }        this._checkAppStatus(json, url)        log("请求返回", json, url, options);        return json.data;    }    /**     * get请求     * @param {*} url      */    async get(url, data) {        if (data) data = urlEncoded(data);        if (url.indexOf('?') < 0 && data) url += '?' + data;        return this._request(url, {            method: 'GET',            headers: headers,            timeout: 10000        }, 'json')    }    /**     * post请求     * @param {*} url      * @param {*} data      */    async post(url, data) {        return this._request(url, {            method: 'POST',            headers: Object.assign(headers, { 'Content-Type': 'application/x-www-form-urlencoded' }),            timeout: 10000,            body: urlEncoded(data)        }, 'json')    }}

调用一次远程端口并查看日志输出,这里调用的也是案例中要使用到的获取 banner 的接口,该接口不需要用户权限,后面还会遇到需要用户权限的接口。

enter image description here

自适应方法

这里推荐一种自适应的方法,同时也是前端在开发移动端页面的时候常用的方法,将手机屏幕宽度默认为 750 像素,然后将所有的宽高按照这个比例去缩放,这要求设计出的设计稿宽度也是 750 像素。

在 utils 下新建一个px.js文件,按照出入的大小根据当前屏幕的宽度获取到缩放的比例并返回结果。

ab16ed00-188a-11e8-8087-9b6b3b447adf

在首页引入 px 方法,查看使用 px 之后的效果。

d4992580-188a-11e8-8087-9b6b3b447adf

d97950c0-188a-11e8-8087-9b6b3b447adf

可以看到使用 px 将 500 像素缩放之后的效果和最开始设置的纯数字 200 效果是一致的,这里使用的是 iOS 模拟器,真实的屏幕宽是 375,按照 750 宽去算的话会把传入的参数统统除以 2。

封装本地存储

RN 提供的 AsyncStorage 可以根据 key 存储相应的字符串,我们这里改进一下,让它可以存储所有类型的字段,利用的是将传入的参数改造成对象,然后使用 JSON 的方法将对象转化成一个可以存储的字符串。

在 utils 下新建一个Storage.js

3ddb9950-188c-11e8-8087-9b6b3b447adf

将传入的对象转化为字符串并存入 AsyncStorage。

'use strict';import { AsyncStorage } from 'react-native';/** * 获取存储的数据 * @param {*} key  */exports.getItem = async (key) => {    let item = await AsyncStorage.getItem(key);    if (!item) {        return null;    }    return JSON.parse(item).v || null;}/** * 存入数据 * @param {*} key  * @param {*} value  */exports.setItem = (key, value) => AsyncStorage.setItem(key, JSON.stringify({    v: value}));/** * 删除已经存在的数据 * @param {*} key  */exports.removeItem = (key) => AsyncStorage.removeItem(key);/** * 清除所有 */exports.clear = () => AsyncStorage.clear();/** * 获取所有的key */exports.getAllKeys = () => AsyncStorage.getAllKeys();

在首页使用 setItem 存入数据,然后第二次进入页面再使用 getItem 获取数据。

e9e272a0-188c-11e8-8087-9b6b3b447adf

这里用到了 componentDidMount 这个方法,该方法是在组件生命周期中的初始化完成之后执行的。

enter image description here

添加弹出 Toast

之前公司使用的是自己开发的提示方法,该方法需要改变原生代码,非常的不方便,这里推荐使用第三方的开源组件react-native-root-toast,只需要安装一下就好了。

执行命令,安装 Toast:npm i --save react-native-root-toast

在 utils 目录下新建 toast.js 文件,添加 Toast 的默认方法并填入默认参数,这里设置显示时间为 1000 毫秒,背景颜色是一个半透明的黑色,其目的也是为了方便调用,如果需要多种效果的就定义多个即可。

enter image description here

在首页引入 Toast 并看看实际的效果。

41c80400-18b3-11e8-8087-9b6b3b447adf

让 iOS 支持 Http 协议

苹果之前推荐使用 Https 协议,现在默认是不支持 Http 的,如果需要支持 Http 则要单独设置,案例中的项目也用到了 Http,所以需要修改 info.plist 文件,让 iOS 可以访问 Http 的地址。

使用 Xcode 选择打开其他项目。

0da257a0-1919-11e8-8087-9b6b3b447adf

打开项目下的 iOS 文件夹,选择项目文件并打开。

4d6ce8a0-1919-11e8-8087-9b6b3b447adf

选择 info.plist 文件,在右边选择第一行并点击 + 号添加一项。

enter image description here

选择 App Transport Security Setting 这一项,会弹出提示,单击“确定”按钮即可自动刷新。

01e614a0-191a-11e8-8087-9b6b3b447adf

在上面添加的新配置中添加一个新的配置 Allow Arbitrary Loads,同时设置为 YES。

enter image description here

改完配置还需要编译一次,单击左上角的三角形或者菜单中 product 下的 build 选项。

enter image description here

这里我使用的是 Xcode 修改配置文件,如果你发现配置文件没有变化,也可以自己改 info.plist 文件的内容。

编译安卓客户端

使用 Android Studio 打开根项目下的 Android 目录,打开 build.gradle 文件,这个就是项目的 Gradle 配置文件,通常使用这个文件对整个项目进行描述。

3d6d4f10-204b-11e8-94c8-43a1e6ab60e0

经过一定时间的等待,IDE 就会初始化整个项目,如果有一些需要下载的文件也会在这个时间通知下载。

单击菜单build/gennerate signed apk,这个就是编译一个可以安装在安卓手机上的安装包,也可以通过单击make project来看看项目是否可以编译通过。

enter image description here

单击 Next 按钮,IDE 会提示需要选择一个证书,这里可以选择一个已有的并输入密码,也可以通过单击create new来创建一个,后面一直单击 Next 按钮就可以了,IDE 会在生成 APK 之后弹出通知。

021b3160-204c-11e8-8063-17489f4fcf26

选择创建一个新的证书,根据提示填入相应的内容,之后单击 OK 按钮即可生成,记得选择 remember password,下次直接填入密码。

8d33ddb0-204c-11e8-a1dd-77d9e3f43337

经过一整机器躁动之后,IDE 弹出编译结果,点击蓝色字可以快速打开 APK 的地址。

enter image description here

将 APK 文件发送到手机上安装即可,一个自己开发的 App 就安装好了。

14e47fd0-204d-11e8-94c8-43a1e6ab60e0

这里要注意,在打包之前要把 RN 打包生成的 bundle 文件放入 Android 文件下的 assets 目录中,否则 Android 会因为找不到启动文件而报错。

第03课:首页模板和商品列表
第04课:组件和商品详情
第05课:使用 WebView 和运营页通讯
第06课:个人中心设置及 Debug 日志
第07课:添加自定义组件
第08课:注册登录
第09课:全局状态管理购物车
第10课:实现购买(1)
第11课:实现购买(2)
第12课:订单管理
第13课:添加优化动画
第14课:优化 App,提升10倍效率
第15课:使用 ESlint 规范化代码
第16课:使用 TypeScript 编写代码
第17课:集成极光
第18课:热更新
第19课:总结

阅读全文: http://gitbook.cn/gitchat/column/5aa8a68b0bb9e857450e2308

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页