React-Native實踐

RiverLi發表於2018-06-03

關於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原生專案

相關文章