React-Native实践

关于React-Native

React-Native号称是跨平台开发且具有原生应用的体验,早在两年前我曾经尝试过使用React-Native,但是环境搭建过程及其复杂,可参见我之前写的文章:iOS现有项目手动集成ReactNative。最近公司新开项目需要实现iOS和Android平台热更新方案,以便快速开展业务。本文用来梳理React-Native在iOS上的一些实践。包括如下内容:

  • 搭建一个React-Native项目。
  • 现有iOS项目中集成React-Native模块。
  • 以bundle的形式在真机上运行React-Native。
  • 热更新中bundle的拆分。

由于React-Native项目一直在更新,不同的版本在使用的过程中会遇到不同的问题,所以强烈建议你使用英文官方文档作为开发参考。

搭建一个React-Native项目

根据React-Native的官方文档一路能一路很顺利的搭建成功。我这里简单说一下主要步骤,详细可参考:安装文档

  1. 安装依赖,包括Node、Watchman、React Native命令行界面、Xcode。

    1. 建议使用homebrew安装node和Watchman
    brew install node
    brew install watchman
    
    1. 安装 React Native CLI
    npm install -g react-native-cli
    
    1. Xcode,作为一个iOS开发,就不多说了。
  2. 创建项目

    react-native init AwesomeProject
    
  3. 运行项目

    cd AwesomeProject
    react-native run-ios
    
  4. 如果顺利的话你可以看到你的第一个React-Native项目了。

集成React Native到现有项目

同样我建议你参考官方文档,我现有项目是Swift,你可以根据你的项目情况选择对应的安装方法。

  1. 调整项目结构如下,新建iOS目录,将iOS相关的代码都放到iOS目录下。
  2. 在iOS同级目录下创建package.json文件
{
  "name": "MyReactNativeApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "postinstall": "sed -i '' 's/#import <RCTAnimation\\/RCTValueAnimatedNode.h>/#import <React\\/RCTValueAnimatedNode.h>/' ./node_modules/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h; sed -i '' 's/#import <fishhook\\/fishhook.h>/#import <React\\/fishhook.h>/' ./node_modules/react-native/Libraries/WebSocket/RCTReconnectingWebSocket.m"
  },
  "dependencies": {
    "react": "^16.4.0",
    "react-native": "^0.55.4"
  }
}

  1. 安装yarn
brew install yarn
  1. 安装React-native
yarn add react-native

如果出现警告

warning "react-native@0.52.2" has unmet peer dependency "react@16.2.0".

则你需要安装React

yarn add react@version_printed_above // version_printed_above 具体版本号
  1. 在iOS目录里添加podfile文件
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

target 'ReactDemo' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ReactDemo

pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'CxxBridge', # Include this for RN >= 0.47
'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
'RCTText',
'RCTNetwork',
'RCTWebSocket', # needed for debugging
 Add any other subspecs you want to use in your project
]
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

# Third party deps podspec link
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'


  target 'ReactDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'ReactDemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

def fix_cplusplus_header_compiler_error
    filepath = '../node_modules/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.h'
    
    contents = []
    
    file = File.open(filepath, 'r')
    file.each_line do | line |
        contents << line
    end
    file.close
    
    if contents[32].include? "&"
        contents.insert(26, "#ifdef __cplusplus")
        contents[36] = "#endif"
        
        file = File.open(filepath, 'w') do |f|
            f.puts(contents)
        end
    end
end

def fix_unused_yoga_headers
    filepath = './Pods/Target Support Files/yoga/yoga-umbrella.h'
    
    contents = []
    
    file = File.open(filepath, 'r')
    file.each_line do | line |
        contents << line
    end
    file.close
    
    if contents[12].include? "Utils.h"
        contents.delete_at(15) # #import "YGNode.h"
        contents.delete_at(15) # #import "YGNodePrint.h"
        contents.delete_at(15) # #import "Yoga-internal.h"
        contents.delete_at(12) # #import "Utils.h"
        
        file = File.open(filepath, 'w') do |f|
            f.puts(contents)
        end
    end
end

def react_native_fix
    fix_cplusplus_header_compiler_error
    fix_unused_yoga_headers
end

post_install do |installer|
    react_native_fix
end

执行pod install命令。

  1. 在iOS同级目录创建index.ios.js,写入相关内容。
import React from 'react';
import {AppRegistry, StyleSheet, Text, View} from 'react-native';

class RNHighScores extends React.Component {
  render() {
    var contents = this.props['scores'].map((score) => (
      <Text key={score.name}>
        {score.name}:{score.value}
        {'\n'}
      </Text>
    ));
    return (
      <View style={styles.container}>
        <Text style={styles.highScoresTitle}>2048 High Scores!</Text>
        <Text style={styles.scores}>{contents}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
  },
  highScoresTitle: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  scores: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

// Module name
AppRegistry.registerComponent('RNHighScores', () => RNHighScores);
  1. 在iOS项目中调用React Native模块
@IBAction func highScoreButtonTapped(sender : UIButton) {
  NSLog("Hello")
  let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")
  let mockData:NSDictionary = ["scores":
      [
          ["name":"Alex", "value":"42"],
          ["name":"Joel", "value":"10"]
      ]
  ]

  let rootView = RCTRootView(
      bundleURL: jsCodeLocation,
      moduleName: "RNHighScores",
      initialProperties: mockData as [NSObject : AnyObject],
      launchOptions: nil
  )
  let vc = UIViewController()
  vc.view = rootView
  self.present(vc, animated: true, completion: nil)
}
  1. 配置info.plist
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>
  1. 启动npm
npm start
  1. 运行
react-native run-ios

真机运行

在真机上运行ReactNative项目有两种方式,本文只说第二种

  • 手机和电脑在同一个局域网,将上述代码的localhost改为机器ip。
  • 打包出jsbundle

在iOS目录创建release_ios文件夹,执行如下命令

react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output release_ios/main.jsbundle --assets-dest release_ios/

会将js打包成main.jsbundle,将图片等输出到asset目录。将这些内容拖入工程,修改加载React-Native代码

let jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")

这样就可以直接在真机上运行了。

热更新bundle的拆分与合并

这方面的详解我推荐necfol的方案,我就不多说废话了,人家的图那么清晰,还有Demo,我只能推荐了。

参考

含泪导入React-native 0.54到Swift原生项目

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容