博客 / 詳情

返回

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

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對應的文件位置

這個文件夾內容看起來是這樣的

目錄結構

那麼你又會發現,好像這些文件的結構,以及如何生成這些文件,又是一個頭疼的問題,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

user avatar xiaohaiqianduan 頭像 bd_68bd40c5df395 頭像
2 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.