使用duxapp開發 React Native App 事半功倍

少恭写代码發表於2024-09-26

Taro的React Native端開發提供了兩種開發方式,一種是將殼和程式碼分離,一種是將殼和程式碼合併在一起開發

  • 殼是用來打包除錯版或者發版安裝包使用的
  • 程式碼是執行在殼上的js程式碼
  • Taro殼子的程式碼倉庫https://github.com/NervJS/taro-native-shell

duxapp中更進一步,你不需要太關注殼子什麼的,你只需要安裝好安卓和ios的編譯環境,用一個命令就能編譯apk或者ios,並且這個編譯的過程和duxapp的模組化理念高度繫結,透過指定 --app= 指定不同的入口,就能打包出不同的專案,就像下面這樣

# 編譯 duxuiExample 的安卓除錯版本
yarn android --app=duxuiExample

# 編譯 duxuiExample 的IOS除錯版本
yarn ios --app=duxuiExample

# 編譯成功後啟動Metro程式碼編譯服務
yarn start --app=duxuiExample

下面我來詳細介紹一下,在duxapp中是如何對RN進行最佳化的

配置化

對於Taro的殼子,或者原生React Native,都會存在 android ios這兩個資料夾,而在duxapp中,這些資料夾的內容是自動生成的,那麼對於需要在這些資料夾中修改的配置內容,例如包名、版本號、新架構開關等,都透過配置檔案的方式配置了,而不需要需修改具體的檔案

這個配置檔案是專案配置資料夾下的 configs/duxuiExample/duxapp.js,其中 duxuiExample 就是我透過--app=duxuiExample 指定的入口模組

這個配置檔案的內容就像下面這樣,可以清晰的看到,對安卓配置了包名、名稱、版本號等資訊,IOS同樣如此

const config = {
  android: {
    appid: 'com.duxapp.duxui',
    appName: 'duxUI庫',
    versionCode: 2,
    versionName: '1.1.0',
    keystore: {
      storeFile: 'duxui.keystore',
      keyAlias: 'duxui',
      storePassword: 'TN62eyasJAKm2ksD',
      keyPassword: 'TN62eyasJAKm2ksD'
    }
  },
  ios: {
    BundleId: 'com.duxapp.duxui',
    appName: 'duxUI庫',
    versionCode: 1,
    versionName: '1.0.0',
    team: '',
    plist: {
      'duxapp/Info.plist': {
        NSCameraUsageDescription: 'duxUI庫需要拍照用於APP內圖片上傳更換頭像',
        NSContactsUsageDescription: 'duxapp需要訪問你的通訊錄,將客戶資訊儲存到通訊錄中',
        NSLocalNetworkUsageDescription: 'App需要訪問你的本地網路,用於和伺服器建立連線',
        NSLocationAlwaysAndWhenInUseUsageDescription: '使用你的位置資訊用於地圖定位和位置選擇',
        NSLocationAlwaysUsageDescription: '使用你的位置資訊用於地圖定位和位置選擇',
        NSLocationWhenInUseUsageDescription: '使用你的位置資訊用於地圖定位和位置選擇',
        NSPhotoLibraryAddUsageDescription: 'duxUI庫需要儲存宣傳圖到你的相簿用於分享',
        NSPhotoLibraryUsageDescription: 'duxUI庫需要訪問相簿用於APP內圖片上傳更換頭像',
      }
    }
  }
}

module.exports = config

內容複製

上面這個配置檔案已經解決了大部分打包需要用到的配置,但是你開發過RN的話你會看出來,證書他是一個檔案,這裡只指定了證書檔名稱,但是並未指定證書具體內容,還有打包一個app,它總是需要一個app圖示的,包括安卓和ios的圖示,那麼這些內容,可以透過配置檔案中的copy資料夾,將這些專案檔案複製到安卓或者ios對應的檔案位置

這個資料夾內容看起來是這樣的

copy結構

那麼你又會發現,好像這些檔案的結構,以及如何生成這些檔案,又是一個頭疼的問題,duxapp-cli,幫你解決了這個麻煩的問題,只需要兩個簡單的命令,就可以自動建立這個些檔案

首先是安卓證書檔案,需要注意的是,這裡是指定--config=,而不是指定 --app=

yarn duxapp android keystore --config=duxuiExample

建立成功後,需要手動將命令列列印的配置內容,放進duxapp.js相應位置

然後是logo建立,需要將你專案的logo檔案放在配置檔案根目錄,也就是 configs/duxuiExample/logo.png

yarn duxapp rn logo --config=duxuiExample

命令使用成功後,他會自動把logo放進對應位置,你就不需要進一步操作了

這樣是不是就簡單起來了,下面來看看,要如何使用第三方外掛,例如微信外掛、高德地圖外掛等

以上所有提到的 duxuiExample 都是以 UI庫示例 這個模組專案來舉例的,在你的專案中根據實際情況替換

三方模組

你的專案或多或少都要用一些第三方的外掛,React Native基礎模組中已經包含了很多基礎常用外掛,你可以透過三方模組檢視到,包含的基礎外掛

傳統的方法是將他們新增到 package.json 依賴中,然後根據文件內容修改安卓或者ios資料夾對應的內容,在duxapp中提供了另外一種方式來實現第三方外掛的使用

像這個react-native-view-shot安裝方式很簡單的外掛,他只要求你將他新增到 package.json 的依賴中就可以使用了

那麼我們結合模組,在你需要用到這個功能的模組配置檔案中,一樣的新增上這個依賴即可,像下面這個duxui模組的配置檔案一樣

{
  "name": "duxui",
  "description": "DUXUI庫",
  "version": "1.0.42",
  "dependencies": [
    "duxapp"
  ],
  "npm": {
    "dependencies": {
      "b-validate": "^1.5.3",
      "react-native-view-shot": "~3.8.0",
      "react-native-fast-shadow": "~0.1.1",
      "array-tree-filter": "^2.1.0"
    }
  }
}

其實開源的大多數第三方外掛都是這樣的,只需要新增到依賴中,重新打包就能用了,但是很少數的外掛,他就是要改一些安卓或者ios裡面的原生內容,像微信外掛,它需要的改動還挺多的,我根據他文件需求,列舉了下面這些

安卓:

  • 新增 proguard
  • 新建 WXEntryActivity.java 用於回撥處理
  • 新建 WXPayEntryActivity.java 用於支付回撥處理
  • 新增 <package android:name="com.tencent.mm" /> 用於跳轉到微信的白名單
  • 新增 .wxapi.WXEntryActivity
  • 新增 .wxapi.WXPayEntryActivity

ios:

  • 由於外掛bug,需要新增 pod 依賴項 pod 'WechatOpenSDK'
  • 修改 AppDelegate.h 入口檔案
  • 修改 AppDelegate.mm 檔案進行一些處理
  • Info.plist 新增 Schemes 和 BundleURLTypes 和 applinks
  • 在專案配置中,新增 UniversalLink

其他:

  • 透過patch修復當前版本的一個bug

首先還是要在模組中新增依賴

{
  "name": "wechat",
  "description": "端微信模組依賴,APP端和h5端",
  "version": "1.0.15",
  "dependencies": [
    "duxappReactNative"
  ],
  "npm": {
    "dependencies": {
      "react-native-wechat-lib": "^3.0.4",
      "wechat-jssdk": "^5.1.0"
    }
  }
}

那麼在duxapp前面提到,安卓和ios資料夾的內容都是自動生成的,我又是如何處理這些修改的呢?這裡就需要用到 duxapp-cli 提供的模組更新指令碼來處理

針對微信外掛的處理指令碼檔案位於 src/wechat/update/index.js,這個檔案的內容是下面這樣的

// eslint-disable-next-line import/no-commonjs
module.exports = ({ config }) => {
  const { android, option } = config
  return {
    // 描點插入
    insert: {
      'android/app/proguard-rules.pro': {
        'content': `
  ##### 微信 ######
  -keep class com.tencent.mm.opensdk.** { *; }
  -keep class com.tencent.wxop.** { *; }
  -keep class com.tencent.mm.sdk.** { *; }`
      },
      'ios/Podfile': {
        'podEnd': `  pod 'WechatOpenSDK'`
      },
      'ios/duxapp/AppDelegate.h': {
        import: '  #import "WXApi.h"',
        'appDelegate.protocol': '  ,WXApiDelegate'
      },
      'ios/duxapp/AppDelegate.mm': {
        import: '#import <React/RCTLinkingManager.h>',
        appDelegate: `// react-native-wechat-lib start

  - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
      return  [WXApi handleOpenURL:url delegate:self];
  }

  - (BOOL)application:(UIApplication *)application
    continueUserActivity:(NSUserActivity *)userActivity
    restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable
    restorableObjects))restorationHandler {
    // 觸發回撥方法
    [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
    return [WXApi handleOpenUniversalLink:userActivity
    delegate:self];
  }

  // Universal Links 配置檔案, 沒使用的話可以忽略。
  // ios 9.0+
  - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
              options:(NSDictionary<NSString*, id> *)options
  {
    // Triggers a callback event.
    // 觸發回撥事件
    [RCTLinkingManager application:application openURL:url options:options];
    return [WXApi handleOpenURL:url delegate:self];
  }
  // react-native-wechat-lib end`
      }
    },
    create: {
      'android/app/src/main/java/cn/duxapp/wxapi/WXEntryActivity.java': `package ${android.appid}.wxapi;

import android.app.Activity;
import android.os.Bundle;
import com.wechatlib.WeChatLibModule;

public class WXEntryActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    WeChatLibModule.handleIntent(getIntent());
    finish();
  }
}
`,
      'android/app/src/main/java/cn/duxapp/wxapi/WXPayEntryActivity.java': `package ${android.appid}.wxapi;

import android.app.Activity;
import android.os.Bundle;
import com.wechatlib.WeChatLibModule;

public class WXPayEntryActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    WeChatLibModule.handleIntent(getIntent());
    finish();
  }
}
`
    },
    android: {
      xml: {
        'app/src/main/AndroidManifest.xml': {
          tag: {
            queries: {
              child: '<package android:name="com.tencent.mm" />'
            }
          },
          attr: {
            'android:name=".MainApplication"': {
              child: `<activity
                android:name=".wxapi.WXEntryActivity"
                android:label="@string/app_name"
                android:exported="true"
                android:taskAffinity="${android.appid}"
                android:launchMode="singleTask"
              />
              <activity
                android:name=".wxapi.WXPayEntryActivity"
                android:label="@string/app_name"
                android:exported="true"
              />`
            }
          }
        }
      }
    },
    ios: {
      plist: {
        'duxapp/Info.plist': {
          CFBundleURLTypes: [
            {
              CFBundleTypeRole: 'Editor',
              CFBundleURLName: 'weixin',
              CFBundleURLSchemes: [
                option?.wechat?.appid || 'wx'
              ]
            }
          ],
          LSApplicationQueriesSchemes: ['weixin', 'wechat', 'weixinULAPI']
        },
        'duxapp/duxapp.entitlements': {
          'com.apple.developer.associated-domains': [
            `applinks:${option?.wechat?.applinks || 'duxapp.cn'}`
          ]
        }
      }
    }
  }
}

這個檔案匯出了一個函式,這個函式引數中的 config 就是當前專案的RN編譯配置檔案,這個檔案中可以獲取到了包名、版本號等資訊

函式返回了一個物件,這個物件中的每一個key就代表不同的功能,下面一一介紹一下這些key

  • insert 用於將內容插入到指定檔案的指定位置
  • create 用於將檔案建立於指定位置
  • android 其中的xml用來處理合併安卓中的xml檔案的,這是用 xmldom來實現的
  • ios 其中的plist是用來合併ios的plist配置檔案的

關於這個指令碼檔案的詳細內容需閱讀 使用原生模組 瞭解詳情

看了半天,是不是感覺這個模組處理也是挺複雜的,其實我已經封裝了一些常用的原生模組,就像這個微信外掛,你不需要再去實現一遍,你只需要安裝這個微信模組並把他新增到你專案模組的依賴中就能使用了

yarn duxapp app add wechat

然後就像 duxuiExample 這個模組的配置檔案一樣,將 wechat 新增到依賴中,然後重新編譯

{
  "name": "duxuiExample",
  "description": "ui庫示例",
  "version": "1.0.13",
  "dependencies": [
    "duxui",
    "duxcms",
    "amap",
    "echarts",
    "wechat"
  ]
}

還有更多的模組,請前往應用商店檢視 https://www.dux.cn/page/apps

總結

透過上面的說明,你已經基本瞭解了duxapp是如何處理RN端開發的,還有很多的詳細的內容,還需要前往文件檢視http://duxapp.cn/docs/course/rn/start

再結合duxapp提供的ui庫、工具庫、全域性樣式等方法,就能很快的完成你的APP專案了

GitHub:https://github.com/duxapp

相關文章