React Native 周报react native学习专题首页投稿(暂停使用,暂停投稿)

React Native 入门之旅

2016-07-19  本文已影响1071人  stefanli

作为一名Android开发,学习React Native其实是一个很陡峭的过程,本文主要记录自己从接触React Native,到能够实现一个比较完整的Demo的过程中,涉及到的一些知识点,以及踩过的一些坑。

一、React Native 简介

<p>

React Native lets you build mobile apps using only JavaScript. It uses the same design as React, letting you compose a rich mobile UI from declarative components.

<p>

With React Native, you don't build a “mobile web app”, an “HTML5 app”, or a “hybrid app”. You build a real mobile app that's indistinguishable from an app built using Objective-C or Java. React Native uses the same fundamental UI building blocks as regular iOS and Android apps. You just put those building blocks together using JavaScript and React.

简单总结一下:ReactNative是由Facebook推出的,可以让开发者使用 JavaScript 和 React 创建基于Web,iOS 和 Android 平台原生应用的一套框架。

二、React Native 案例


在RN的官网上能够看到一些开发案例,不过基本上都是国外的应用,国内有使用到RN开发的应用主要包括QQ空间、QQ音乐、全民K歌等等。

三、React Native 基本概念

RN最基本的概念我认为应该是组件、属性、状态。

import React, { Component } from 'react';
import { AppRegistry, Text } from 'react-native';

class HelloWorldApp extends Component {
   render() {
     return (
     <Text>Hello world!</Text>
   );
 }
}

AppRegistry.registerComponent('HelloWorldApp', () => HelloWorldApp);

组件其实就是界面上可显示的元素,类似于Android里面的View,通过render函数进行渲染,一个组件可以包含很多个子组件。RN内置的组件可以直接通过import { xxx } from 'react-native' 进行导入,当然也可以自定义组件。每个组件都拥有自己的属性和状态。将上面的例子进行完善,加入属性和状态:

import React, { Component } from 'react';
import { AppRegistry, Text, Image } from 'react-native';

class HelloWorldApp extends Component {
   constructor(props) {
     super(props);
     this.state = {showText: true};
   }
   render() {
     let pic = {
       uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
     };
     return (
       <Image source={pic} style={{width: 193, height: 110}}/>
     );
   }
}

AppRegistry.registerComponent('HelloWorldApp', () => HelloWorldApp);

在构造函数中,初始化了组件的状态showText为true,那就可以在其他地方通过this.state.showText访问到该状态的值,在render函数里,组件的source即是组件的一个属性,可以通过this.props来获取属性的值。

可以通过setState函数改变组件的状态,每次状态改变都会重新触发render函数。

四、React Native 技术细节

AsyncStorage :key-value存值方式,支持写入、读取、移除
react-native-sqlite : 支持iOS数据库
react-native-android-sqlite :支持Android数据库
Realm :跨平台,可同时支持iOS和Android(推荐)

这里将navigator以及route params里面的所有字段通过属性进行传递,所以新打开的组件能够获得navigator以及相关参数。
openPage() {
this.props.navigator.push({
component: MainScreen,
params: {
phone: this.state.phone,
yzm: this.state.yzm,
}
})
}

  _back() {
    this.props.navigator.pop();
  }

关于回调,可以定义一个回调函数作为参数传递,网上例子很多不再赘述,这里需要强调的一点是,如果A组件把navigator传递给了B组件,而B组件的子组件也需要使用这个navigator,那么需要B组件在创建子组件的时候,手动把这个参数继续传递下去,比如我的主界面是5个Tab页,需要在点击Tab页内某些组件的时候跳转到新的页面,那么在创建Tab页面的时候就需要手动传递参数:
<Page1 {...route.params} navigator={this.props.navigator}></Page1>

对于Android而言,还存在一个问题是硬件返回,如果是原生的Android应用,点击返回键是返回到上一个界面,但RN构建的应用,点击返回直接退出了应用,所以这里,需要对硬件返回进行监听。所幸,在RN里面有BackAndroid可以监听返回键,示例如下:

  BackAndroid.addEventListener('hardwareBackPress',    
    function() {
      if (!this.onMainScreen()) {
      this.goBack();
      return true;
    }
   return false;
  });

The reason that you have to use file_get_contents('php://input') is because its not form data. It is passing in a raw body request there and so php doesn't know to parse that as JSON by default. You are passing in a JSON body and anytime you use something like that you will have to parse it that way.

  1. 修改RN代码
    toQueryString(obj) {
    return obj ? Object.keys(obj).sort().map(function (key) {
    var val = obj[key];
    if (Array.isArray(val)) {
    return val.sort().map(function (val2) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
    }).join('&');
    }
    return encodeURIComponent(key) + '=' + encodeURIComponent(val);
    }).join('&') : '';
    }

     fetch('https://mywebsite.com/endpoint/', {
        method: 'POST',
        headers: {
         'Accept': 'application/json',
         'Content-Type': 'application/x-www-form-urlencoded',
       },
      body: toQueryString({
       'xxx': 'xxx',
       'xxx': 'xxx',
      })
     })
    

这里特别注意的是,Content-Type要改为application/x-www-form-urlencoded类型。

生命周期 调用次数 能否使用setState
getDefaultProps 1(全局调用一次)
getInitialState 1
componentWillMount 1
render >=1
componentDidMount 1
componentWillReceiveProps >=0
shouldComponentUpdate >=0
componentWillUpdate >=0
componentDidUpdate >=0
componentWillUnmount 1

组件的生命周期,可以通过在组件里面打印log,观察具体调用。我遇到的一个问题是,需要在应用界面不可见的时候执行某些操作,对应Android相当于点击了Home键,那如何知道RN构建的应用当前是处于前台还是后台呢?所幸,RN里面有AppState这个API。

App States

五、React Native 签名打包

在开发过程中,无论是真机还是模拟器,都需要启动JS server,然后通过这个server下载相关的bundle文件加载运行,但在实际发布的时候,我们需要对应用进行签名,把相关的js文件和资源文件进行打包,当然,这里是针对Anroid的情况,以下是React Native生成正式包的步骤:

其中,签名apk路径:android/app/build/outputs/apk
bundle文件路径:android/app/build/intermediates/assets/release/

如果要导出bundle文件和资源文件,还可以执行以下两个命令:

六、React Native 动态更新

最开始选择学习RN,其中很大一个原因是RN的动态更新能力,虽然目前有很多hotfix框架,但或多或少都存在一些兼容性问题,并且能力有限,只能小范围的修改源代码,而不能替换资源文件。

我们知道RN运行的时候,是去读取assets目录下的bundle文件,所以只要能够动态的替换这个文件,那么就能够做到动态更新,但显然,assets是不允许写入文件的,不过所幸的是,ReactActivity是允许重新指定bundle加载路径的:

/** 
* Returns a custom path of the bundle file. 
* This is used in cases the bundle should be loaded from a custom path. 
* By default it is loaded from Android assets, from a path specified by 
* {@link getBundleAssetName} e.g. 
* "file://sdcard/myapp_cache/index.android.bundle"
*/

protected @Nullable String getJSBundleFile() { return null;}

我们可以在MainActivity重写这个方法,指定bundle文件路径,如果该路径下的文件存在,则返回指定文件路径,如果不存在,就返回null,默认到assets路径下加载bundle文件,所以当需要更新的时候,只需要下载相关的bundle文件到指定目录就可以了。

@Nullable
@Override
protected String getJSBundleFile() {
   String jsBundleFile = getFilesDir().getAbsolutePath() + "/index.android.bundle";
   File file = new File(jsBundleFile);    
   return file != null && file.exists() ? jsBundleFile : null;
}

当然,仅仅替换bundle文件并没有解决所有问题,我们知道,bundle文件只包含了js代码,那资源文件又该如何处理呢?在更新的时候,如果只是下载了bundle文件,会导致原有项目中所有图片都不可见,这又是为什么呢?打开node_modules/react-native/Libraries/Image/Image.android.js文件,查看Image的render函数,然后逐层追踪源码,会在resolveAssetSource.js文件中看到一个很重要的函数:

function getBundleSourcePath(): ?string {
  if (_bundleSourcePath === undefined) {
    const scriptURL = SourceCode.scriptURL;
    if (!scriptURL) {
      // scriptURL is falsy, we have nothing to go on here
      _bundleSourcePath = null;
      return _bundleSourcePath;
    }
    if (scriptURL.startsWith('assets://')) {
      // running from within assets, no offline path to use
      _bundleSourcePath = null;
      return _bundleSourcePath;
    }
    if (scriptURL.startsWith('file://')) {
      // cut off the protocol
      _bundleSourcePath = scriptURL.substring(7, scriptURL.lastIndexOf('/') + 1);
    } else {
      _bundleSourcePath = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
    }
  }
  return _bundleSourcePath;
}

在AssetSourceResolver.js中看到两个比较重要的函数:

defaultAsset(): ResolvedAssetSource {
  if (this.isLoadedFromServer()) {
    return this.assetServerURL();
  }

 if (Platform.OS === 'android') {
    return this.isLoadedFromFileSystem() ?
    this.drawableFolderInBundle() :
    this.resourceIdentifierWithoutScale();
  } else {
    return this.scaledAssetPathInBundle();
  }
}

drawableFolderInBundle(): ResolvedAssetSource {
  const path = this.bundlePath || '';
  return this.fromSource(
    'file://' + path + getAssetPathInDrawableFolder(this.asset)
  );
}

源码不再具体分析,简单总结一下,如果我们指定了bundle文件的加载路径,那我们的图片资源也会在该路径下去加载,比如:

/data/data/com.rndemo/files/index.android.bundle
/data/data/com.rndemo/files/drawable-mdpi/image_splash_img.png

那么问题又来了,是不是每次更新,都需要去下载所有的资源图片,其实并不需要,因为我们有RN的源码,所以直接在源码里面修改加载策略就可以了,具体不再赘述。

这篇文章主要简单介绍了RN入门的一些东西,还有很多模块的内容,我一边学习再一边总结,文末附上传送门,是我参考过的一些文章。

附录传送门

上一篇 下一篇

猜你喜欢

热点阅读