## **環境架設注意事項** 1. Node 版本需高於 16 2. 需安裝 Java Development Kit [JDK] 11 ( 相容版本 ) 3. 需安装 Android Studio 4. 必需安裝配置 Android 13 (Tiramisu) SDK 5. 環境變數設置 【[架設環境參考文章](https://reurl.cc/GAYeox)】 **==資源:==** 1. [線上操作](https://snack.expo.dev/) 2. [RN開發的APP案例](https://reactnative.dev/showcase) 3. [RN Cli 文件](https://github.com/react-native-community/cli/blob/main/docs/commands.md) 4. [Expo 官方文件](https://docs.expo.dev/) **==開發環境:==** * [支援的裝置 android、ios、tv、windows、mac和其他](https://reactnative.dev/docs/out-of-tree-platforms) * Debug https://reactnative.dev/docs/react-devtools https://reactnative.dev/docs/debugging **==IOS:==** IOS環境建置需安裝較多套件,但整體流程算是順利,只須注意使用rbenv安裝Ruby時會遇到安裝失敗問題,解決辦法可參考[docs文件](https://docs.google.com/document/d/1Zx2ZI_M1SYFyMQi8DRW-uFRL4lMjSy1gL_7ZFCLD14Y/edit) ## 評估項目 ### :pushpin: 熱更新 ### Microsoft React Native Codepush 在程式引用微軟Codepush套件後,即可透過微軟平台來推送管理App熱更新。平台包含視覺化的UI後台、Cli指令碼。**實際測試熱更新正常運作**,使用上沒有太大問題。惟須注意**IOS APP需使用Apple環境**,且RN推出新版本後,微軟也可能需要推出對應版本來支援該版本RN的熱更新機制,**估計時間落差至少3-6個月**。 **==參考資料==** [CodePush 官方文件/建置步驟](https://learn.microsoft.com/zh-tw/appcenter/distribution/codepush/) [App Center CodePush 更新發佈步驟](https://learn.microsoft.com/en-us/appcenter/distribution/codepush/cli#releasing-updates-react-native) [CodePush Q&A](https://microsoft.github.io/code-push/faq/index.html) [App center CLI 文件](https://learn.microsoft.com/en-us/appcenter/distribution/codepush/cli#releasing-updates-general) [CodePush 參考文章](https://todoit.tech/rn/devops/hotfix.html#%E9%85%8D%E7%BD%AE-rn-%E5%B7%A5%E7%A8%8B) **==費用==** **免費使用**。也可付費以取得額外功能(線上編譯、線上實機測試) * [無限app數量、無限推送更新次數、無限codepush使用](https://learn.microsoft.com/zh-tw/appcenter/general/pricing) * [所有人都可以使用 Visual Studio App Center](https://visualstudio.microsoft.com/zh-hant/app-center/pricing/) **==App Center 常用指令==** * 列出所有app: `appcenter apps list` * 查看某個app的所有deployment list: `appcenter codepush deployment list -a <ownerName>/<appName>` * 發布react-native app deployment: `appcenter codepush release-react -a <ownerName>/<appName> -d <deploymentName> -t <targetBinaryVersion>` * 修改已經relase出去的更新的metadata `appcenter codepush patch -a <ownerName>/<appName> <deploymentName> <existing-release-label>` * 取消某次更新推送(實際上是再發布一版前個版本的更新): `appcenter codepush rollback -a <ownerName>/<appName> <deploymentName>` **==開發時注意事項==** 需要特別注意使用的RN版本需要是微軟Codepush支援的。其餘設定(如APP更新檢查時機、更新時間點)已有完整設定可直接參考文件。 1. [測試更新不能使用debug mode的App](https://learn.microsoft.com/en-us/appcenter/distribution/codepush/rn-deployment#android) :::info CodePush updates should be tested in modes other than Debug mode. In Debug mode, React Native app always downloads JS bundle generated by packager, so JS bundle downloaded by CodePush does not apply ::: 2. 預設 codepush 會在 app 每次 starup 時檢查更新 :::info * By default, CodePush will check for updates on every app start * 如果更新是 madatory,該更新會立即被安裝 ::: 3. 官方建議推送更新流程 :::info We recommend that all users take advantage of the automatically created Staging and Production environments, and do all releases directly to Staging, and then promote from Staging to Production after the appropriate testing ::: 4. 推送上去的更新無法刪除,有出錯必須使用rollback **==限制==** 1. 有支援的RN版本限制,須按裝對應版本 :::warning [對應版本對照參考資料](https://github.com/microsoft/react-native-code-push/tree/master#supported-react-native-platforms) * [查看 releases 選擇支援的版本](https://github.com/microsoft/react-native-code-push/releases) * 安裝指定版本:npx react-native@0.71.0 init npx_react_native2 --version 0.71.0 ::: 2. codpush熱更新要支援最新版本的RN,會有至少3-6個月的時間差 3. 要同時支援 ios、android,官方建議要建立分開的 codepush application :::warning * Note, if you are targeting both platforms it is recommended to create separate CodePush applications for each platform ::: 4. IOS需要有Apple開發環境 **==其他==** **AppCenter** 1. 建立AppCenter/登入 `Distribue` > `Codepush` > `建立deployement>照著步驟設定App環境` 2. App center按下建立deployement按鈕後會建立2個deployments(production、staging)。 :::warning 要如何有效使用這2個deployment經參考: https://learn.microsoft.com/en-us/appcenter/distribution/codepush/rn-deployment#multi-deployment-testing ::: **==其他功能==** * Email通知功能 推送更新後、編譯後(成功或失敗)、當機事件 * Webhook支援 推送更新後、編譯後(成功或失敗)、當機事件 * 線上後台錯誤回報列表 Diagnostics/Issues,可用於查看當機/錯誤報告 * 後台分析功能 * 主要用於了解應用程式使用者和使用應用程式時的行為 * Analytics裡面的分析包含任何安裝本app的裝置(不管是否有安裝過推送的更新) **==實測==** * 檢查更新頻率 * ON_APP_START: App啟動時檢查更新 * ON_APP_RESUME: App從背景回到前景時檢查更新 (**實測如果前面任一更新有指定強制更新且尚未裝過該更新,則安裝第一次打開會強制檢查一次**) * MANUAL: 不會主動檢查更新,需手動檢查 * 安裝更新時點 * ON_NEXT_RESTART: APP下次開啟時安裝更新 (實測正常) * ON_NEXT_RESUME: APP下次回到前景時安裝更新(**實測如果前面任一更新有指定強制更新且尚未裝過該更新,則安裝第一次打開會強制安裝**) * IMMEDIATE: 立即安裝更新 * ON_NEXT_SUSPEND: APP下次進入背景時安裝更新 ### Expo (需用 expo cli 開發) [Expo Updates 官方文件](https://docs.expo.dev/versions/latest/sdk/updates/) [react-native 热更新expo-update使用心得](https://blog.csdn.net/gg_ios/article/details/109206546) [React Native EXPO打包.apk入门](https://www.cnblogs.com/sunxun/p/17261458.html) [bare React Native 使用 expo-update 文件](https://github.com/expo/expo/tree/main/packages/expo-updates#installation-in-bare-react-native-projects) [OTA Update 參考步驟](https://pagepro.co/blog/ota-updates-with-expo/) 1. https://expo.dev 上創建一個新應用程序 2. 建立完成後跟著指令建置環境 `npm install – global eas-cli && \ eas init – id <id_of_your_project>` 3. 新增 expo-updates 插件 `npx expo install expo-updates` 4. 依照順序下指令新增配置 `eas build:configure` `eas update:configure` 5. 用 `eas build` 依照指令操作看要 build 安卓 還是 ios 版本 6. 到 https://expo.dev 選擇專案 > Builds > Download 7. 如果下載下來的檔案為 .aab 需轉換成 .apks 檔案才能在模擬器上安裝運行 8. [可用 bundletool 轉檔](https://github.com/google/bundletool/releases) 9. 或是 [設置 build 出來的檔案為 apk ](https://docs.expo.dev/build-reference/apk/) 10. 測試熱更新 [熱更新相關指令文檔](https://docs.expo.dev/eas-update/eas-cli/) [更新步驟官方文檔](https://docs.expo.dev/eas-update/getting-started/) 11. `eas update --branch preview --message "Updating the app"` :::danger 雖然 OTA 更新可能是將更新傳遞給您的應用程式的最佳方式,但它們並非適用於所有情況。 首先,如果您對原生程式碼進行任何更改,OTA 更新將無法運作。請記住,OTA 更新僅更新 JS 捆綁程式碼,而不是原生程式碼。推送具有原生更改的 OTA 更新可能會導致應用程式對最終使用者出現問題,因為 JS 程式碼可能引用不存在的原生模組。 為確保您的使用者始終收到其原生應用程式版本的正確更新,您可以使用位於 "app.json" 檔案中的 "expo.runtimeVersion" 關鍵字。在對原生程式碼進行任何更改並推送更新之前,請務必更新其值。 其次,您不應該使用 OTA 實現新功能,因為這樣的更改會省略 Apple 和 Google 的驗證。OTA 更新通常用於發布後續將包含在相應應用程式商店的基本應用程式中的錯誤修復。 ::: ### :pushpin: react native 版本選型 #### ==Expo 開發:== **優點:** 1. 加速開發流程: 官方的文件友善清楚,基本上很多情境都能依照官方文件指示的流程開發就可以了,前端開發時會以寫 js 為主,不太需要特別去配置原生開發環境,開發上完全不需要Android Studio、Xcode 2. 熱更新: Expo 有提供內建的熱更新功能可以快速部署更新,可以完全不需要使用App Store來辦到熱更新(OTA Update) 3. 預建元件: Expo 提供大量預建的元件和模組,可以加快開發速度,並且有利於跨平台開發 Ex. 開啟相機、相簿、麥克風、推播、地圖 ...等等 基本上常見應用的需求都可以 cover **缺點:** 1. 功能限制: Expo 提供的內建功能相對較簡單,如果需要更複雜的功能或原生模組支援,可能還是會需要使用 RN 的原生 CLI 進行開發 2. 依賴 Expo server: 使用 Expo CLI 開發,會依賴於 Expo 的 sever,如果 Expo server 掛掉或維修時可能會導致 app 也受到影響 3. 大小問題: Expo 本身就比較大因此開發出來的 App 的大小會相對較大,會影響到使用者下載安裝 App 時需要較長的時間和佔較多的儲存空間 #### ==RN 預設 cli 開發:== **優點:** 1. 原生模組支援: RN 預設的 CLI 提供較完整的原生模組支援,允許直接在原生程式碼中寫功能模組 2. 自定義性: 直接使用 RN 的原生 CLI 可以更彈性撰寫、配置原生程式碼 **缺點:** 1. 環境設置複雜: RN 的原生 CLI 需手動配置原生開發環境,包括 Android Studio 和 Xcode 的設置,這些步驟較繁雜,先前在測試開發時也因此開發過程不時出現 bug 或卡住的狀況 2. 更新管理: 由於需要手動處理原生程式碼,當 RN 更新時,可能需要更新和適配原生程式碼,這部分會相對耗時耗力 ### :pushpin: 比較明顯的缺點 1. 雙平台開發還是會有需要分開判斷和使用不同 api 的狀況 2. ios、android是2種風格 3. 框架版本升級困難和除錯不易 4. 無人維護的套件以及過分仰賴第三方函式庫 [參考文章 - 社群認為React Native有三大痛點,升級難、除錯不易、效能不佳](https://www.ithome.com.tw/news/155617) --- ## 實際業務需求 ### ==App生命週期事件== * 可偵測的事件 * [app在前景、app在背景、app在背景跟前景間的中間狀態(僅IOS)](https://reactnative.dev/docs/appstate) * 沒有支援的事件 * [app安裝事件/app解除安裝事件](https://appsamurai.com/blog/8-tools-to-track-android-and-ios-app-uninstalls/) 延伸閱讀(IOS) - [混淆系列:App 的生命週期「App Life Cycle」](https://medium.com/%E5%9C%A8%E7%A8%8B%E5%BC%8F%E8%88%87%E6%97%85%E8%A1%8C%E7%9A%84%E8%B7%AF%E4%B8%8A/%E6%B7%B7%E6%B7%86%E7%B3%BB%E5%88%97-app-%E7%9A%84%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F-app-life-cycle-6ef9c88e9737) 延伸閱讀 (Android) `Android Activity` 生命週期補充: ![image alt](https://developer.android.com/images/activity_lifecycle.png) [官方文件 - 了解 Activity 生命周期](https://developer.android.com/guide/components/activities/activity-lifecycle?hl=zh-cn) [官方文件 - Activity](https://developer.android.com/reference/android/app/Activity) [Activity生命週期與Intent介紹](http://www.aaronlife.com/v1/teaching/android_activity&intent.html) ### ==App Links== 1. Android:有3種,分別為Deep links、Web links、Android App Links :::warning 延伸閱讀: [淺談App Link與Deep Link實作](https://medium.com/%E5%B7%A5%E7%A8%8B%E5%B8%AB%E6%B1%82%E7%94%9F%E6%8C%87%E5%8D%97-sofware-engineer-survival-guide/%E6%B7%BA%E8%AB%87app-link%E8%88%87deep-link%E5%AF%A6%E4%BD%9C-995734a11889) ::: 2. IOS:有2種,分別為Custom URL Schemes、Universal Links [相關套件文件 - Deep linking](https://reactnavigation.org/docs/deep-linking/) #### :iphone: Android ##### ==Android - Deep Links== > Deep Link 是一種 intent 過濾器,它允許用戶在App中指定點擊連結後的特定活動 會將使用者直接導向 app。惟如果沒安裝 app,預設沒有 fallback,需自行處理 fallback 邏輯。 測試範例: * [下載apk並安裝App](https://drive.google.com/file/d/1F_ONlSWogPaBUxa-xY39WhTzPCacJf8p/view?usp=sharing) * [打開本頁,並點擊該網址](https://halgatewood.com/deeplink/?link=app%3A%2F%2Fdeeplink) ##### ==Android - Web Links== 一樣是 deep link 但使用 http 或 https 作為 scheme,差異在會直接導向網頁。評估下來本方式用處不大。 ##### ==Android - App Links== > App Link 是基於你的網站網址的深層鏈接,該網址已經過驗證。因此,如果點擊會立即打開 App 不顯示彈跳視窗。 可當作是deep links的進化版,會將app打開、app沒安裝則會fallback至該網址。此方式需在該網站放上**驗證資料(.well-know/assetlinks.json)**。 而驗證的部分則由app本身負責,安裝後app會自行判斷是否有link需要進行驗證並自動完成驗證(Android 6之後) :::warning 只要點了連結之後不是直接跳到app,就代表沒有成功啟用: * 沒打開app * [打開了一個視窗讓你選瀏覽器或是app](https://developer.android.com/static/training/app-links/images/app-disambiguation_2x.png),這也是失敗 ::: 測試範例: * [下載apk並安裝App](https://drive.google.com/file/d/1F_ONlSWogPaBUxa-xY39WhTzPCacJf8p/view?usp=sharing) * [打開本頁,並點擊該網址](https://halgatewood.com/deeplink/?link=https%3A%2F%2Freact-native-deep-link-test.vercel.app) #### :iphone: IOS ##### ==IOS - Custom URL Schemes== 依照你指定的路徑,快速跳轉到 App 內的某個頁面 > 由于苹果选择使用沙盒机制来保障用户的隐私和安全,APP 只能访问自己沙盒数据,但同时也阻碍了应用间合理的信息共享。因此苹果提供了一个可以在APP之间跳转的方法: **URL Scheme**。 > 如果你的 APP 需要其他 APP 访问某些功能或者数据,那么你需要在你的 APP 定义一个相应的 URL Scheme。 > **当别的 APP 使用 URL Scheme 进行访问时,系统会根据 URL Scheme 进行匹配,从而来拉起对应的 APP。** > [參考文章 - URL Scheme 的運用](https://www.appcoda.com.tw/intermediate-swift-tips/url-schemes.html) [參考文章 - iOS 唤起APP之URL Scheme](https://www.cnblogs.com/guoshaobin/p/11163984.html) ##### ==IOS - Universal Links== Universal Link 是能夠方便的通過傳統 HTTPS 連結來啟動 APP 的功能,可以使用相同的網址打開網址和 APP。當用戶點擊連結可以跳轉到你的網站並獲得無縫重定向到對應的 APP,且不需要通過 Safari瀏覽器。如果你的應用不支持的話,則會在 Safari 中打開該連結。 **※ 通過打開 https 連結来直接啟動 App 的功能只有在手機有安裝該 App 時才能使用** [參考文章 - iOS 喚起APP之Universal Link(通用鏈接)](https://www.cnblogs.com/guoshaobin/p/11164000.html) [參考文張 - 趣谈 iOS Universal Link](https://juejin.cn/post/7041091910626181157) ### ==推播功能== 1. 依賴第三方平台通知 2. 依賴觸發條件 靠手機套件通知 ### 參考資料 * [Push Notifications and Web Sockets in React-native](https://curiosum.com/blog/push-notifications-and-web-sockets-in-react-native#understanding-web-sockets-in-react-native) ### 第三方平台通知 #### ==Firebase Cloud Messaging ( FCM )== 若直接用 Firebase 推播的優點為 操作簡易、介面也很簡易、免費 缺點為無法特別編輯推播的樣式 > Firebase Cloud Messaging (FCM) 是一种跨平台消息传递解决方案,可供您可靠地传递消息,且无需任何费用。 > > 使用 FCM,您可以通知客户端应用有新的电子邮件或其他数据有待同步。您可以发送通知消息进行用户再互动并留住他们。在即时通讯等使用情形中,一条消息可将最多 4000 字节的载荷传送至客户端应用 [官方原文連結](https://firebase.google.com/docs/cloud-messaging?hl=zh-cn) ==※ 所有 Android 應用都需要 Firebase Cloud Messaging 憑證才能在應用中接收推送通知== [FCM 官方文件](https://firebase.google.com/docs/cloud-messaging?hl=zh-cn) [步驟參考文件](https://thumbb13555.pixnet.net/blog/post/333378541-notification) API 相關文件: [Firebase Cloud Messaging HTTP protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref) [參考文章 - Firebase Cloud Messaging important REST API’s](https://selvaganesh93.medium.com/firebase-cloud-messaging-important-rest-apis-be79260022b5) [Method: projects.messages.send](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send) [Firebase Admin SDK for PHP - Cloud Messaging](https://firebase-php.readthedocs.io/en/stable/cloud-messaging.html) **==注意事項==** 因 **安卓接收推播都需要經過 FCM 的憑證** 所以這個步驟會是必須的 SDK 設置步驟要注意目前 Cli 專案設置的 `build.gradle` 是使用舊的 `buildscript` 語法管理插件 官方文件: `根级(项目级)Gradle 文件 (<project>/build.gradle):` ```javascript= plugins { // ... // Add the dependency for the Google services Gradle plugin id 'com.google.gms.google-services' version '4.3.15' apply false } ``` buildscript 語法: ```javascript= // 直接寫在 dependencies classpath dependencies { classpath ('com.google.gms:google-services:4.3.15') classpath('com.android.tools.build:gradle:7.4.1') classpath('com.facebook.react:react-native-gradle-plugin') } ``` 官方文件: `模块(应用级)Gradle 文件 (<project>/<app-module>/build.gradle):` ```javascript= plugins { id 'com.android.application' // Add the Google services Gradle plugin id 'com.google.gms.google-services' ... } ``` `android/app/build.gradle` ```javascript= // 直接在最上方 apply plugin apply plugin: "com.android.application" apply plugin: "com.google.gms.google-services" apply plugin: "com.facebook.react" import com.android.build.OutputFile ``` ### ==OneSignal== RN官方建議使用的推播套件,功能齊全並有UI後台可查看統計資料 [Expo SDK setup 文件](https://documentation.onesignal.com/docs/react-native-expo-sdk-setup) [React Native SDK setup 文件](https://documentation.onesignal.com/docs/react-native-sdk-setup) [reat-native-onesignal Github](https://github.com/OneSignal/react-native-onesignal) [Pricing](https://onesignal.com/pricing) API 文件: [Client SDK Reference](https://documentation.onesignal.com/docs/sdk-reference) [PHP Client SDK](https://documentation.onesignal.com/docs/php-client-sdk) [Server REST API Reference](https://documentation.onesignal.com/reference/create-notification) * 測試可以順利發送推播也可編輯使用 in-app 訊息 * 後台操作容易,推播內容可客製化的部分蠻多的( Ex. 圖片、聲音、字體顏色、連結、按鈕... etc ) * ==目前免費版發送推播**移動裝置**都是**免費**的==,詳細可查看 [Pricing](https://onesignal.com/pricing) 連結 ### ==Expo== [Expo Notifications 官方文件](https://docs.expo.dev/versions/latest/sdk/notifications/) * 測試後可順利發送推播 * 推播樣式可調整 icon 以及字體顏色 (僅適用於安卓) * 可自定義通知聲音 (僅當使用EAS Build) > 推送通知服務的費用 通過 Expo 的經典推送通知服務發送通知不需要任何費用。 > > 發送通知的限制 每個項目每秒可發送的通知數上限為 600 個。如果超過此速率,後續請求將失敗,直到速率再次低於每秒 600 個。 [官方原文](https://docs.expo.dev/push-notifications/faq/) API 相關文件: [Send notifications with Expo's Push API](https://docs.expo.dev/push-notifications/sending-notifications/) [laravel-expo-notifier](https://github.com/YieldStudio/laravel-expo-notifier) **補充事項** 1. 若以 Expo cli 開發,預設不會有 android / ios 文件夾 :::success [官方文檔參考](https://docs.expo.dev/workflow/customizing/) 生成 ios 文件夾:`npx expo run:ios` 生成 android 文件夾:`npx expo run:android` ::: 2. 用 Expo Go 測試推播需先註冊登入 Expo 帳號 :::success [官方文件參考](https://docs.expo.dev/more/expo-cli/#authentication) 1. 登入指令 `npx expo login` ::: ### 依賴觸發條件 靠手機套件通知 除了顯示推播視窗外,也須有機制來判斷何時觸發推播。由於[Web Sockets仰賴app開啟狀態](https://github.com/tradle/react-native-udp/issues/92),因此這邊使用背景程序套件來測試 **==發送本地推播套件==** 由App本身透過套件觸發推播視窗出現 * [notifee](https://notifee.app/react-native/docs/overview) 文件完整,實測可正常發送推播 **==背景程序相關套件==** 在App退到背景後,繼續執行App所需要的服務。實際測試了解相關套件後,要在背景執行程式會有不同系統/裝置間支援程度不一致的問題發生: * [Android不同版本/廠商可能會有背景執行被取消的限制](https://notifee.app/react-native/docs/android/background-restrictions) * [IOS背景執行超過時間會被取消](https://notifee.app/react-native/docs/android/background-restrictions) 因此**使用第三方發送推播仍然是較可靠的選擇** * [expo-background-fetch](https://docs.expo.dev/versions/latest/sdk/background-fetch/) 官方維護頻率高,文件清楚完整,無須修改原生程式。最快只能每分鐘執行一次程式 * [react-native-background-timer](https://github.com/ocetnik/react-native-background-timer) 須修改原生程式,不同裝置/系統間的支援度問題(IOS會在一段時間後停止該程式執行) * [react-native-background-actions](https://github.com/Rapsssito/react-native-background-actions) 維護頻率低,不同裝置/系統間的支援度有疑慮,文件較不清楚 ### ==自訂彈窗通知== ### 官方方法 可由 Alert 組件或是 Modal 組件達到功能目的 Alert 組件適合快速顯示簡單的警告訊息,而 Modal 組件則適合顯示更複雜的彈出式視窗,並允許用戶與其中的元件進行互動。 可以根據應用程式的需求來選擇使用哪種組件來實現彈出式視窗的功能。 :bell: **Alert 組件** [RN 官方 Alert 文件 (警告/通知)](https://reactnative.dev/docs/alert) 使用方式: 通過 Alert.alert() 方法來快速創建和顯示一個警告視窗,不需要額外的元件包裝。 `詳細方式可參考附註的官方文件` **注意 / 補充 事項** 1. 只有 iOS 系统支持在提示框中加入文本框 2. IOS 可以設置任意數量的按鈕,但安卓最多只能指定三個按鈕 [IOS 詳細設定文件 - AlertButtonStyle ](https://reactnative.dev/docs/alert#alertbuttonstyle-ios) 3. 安卓的三個按鈕分別為中性按鈕、消極按鈕和積極按鈕的概念 :::info 如果只有一個按鈕,會是 "肯定" 按鈕(例如: "確定") 兩個按鈕分別表示 "否定"、"肯定"(例如: "取消"、"確定") 三個按鈕分別表示 "中立"、"消極"、"積極"(例如: "稍後"、"取消"、"確定") ::: :speech_balloon: **Modal 組件** Modal 組件是一種用於在應用程式中顯示彈出式視窗的元件。它可以用來顯示一個簡單的對話框、警告視窗、選擇器或自定義內容,使用的範圍廣、自定義樣式也較彈性。 [RN 官方 Modal 文件 (Dialog / 自定義彈窗)](https://reactnative.dev/docs/modal) [實作參考文章 - react-native系列(10)组件篇:Modal模态框实现弹窗效果](https://blog.csdn.net/zeping891103/article/details/88071186) 使用方式: Modal 組件需要在應用程式中手動創建並控制顯示與隱藏,通常需要包裝要顯示的內容在 <Modal> 元件中,並使用 visible 屬性控制顯示狀態。 `詳細方式可參考附註的官方文件` ### 插件/套件方法 ==**Alert:**== * [5 Awesome Alerts for React Native, works with iOS and Android](https://morioh.com/a/fce1794ec7af/5-awesome-alerts-for-react-native-works-with-ios-and-android) 也可推播套件搭配使用: * [12 Awesome React Native Notifications Libraries You Should Know About](https://morioh.com/a/e9d2ef60291e/12-awesome-react-native-notifications-libraries-you-should-know-about) ==**Modal**== * [18 React Native Popup Component Libraries you Should Know](https://morioh.com/a/dd6e5a37ab7c/18-react-native-popup-component-libraries-you-should-know) * [10 Best Modal Components For React \& React Native Apps (2023 Update)](https://reactscript.com/best-modal-component/) --- ## RN 套件 [套件目錄網址](https://reactnative.directory/) **注意事項** 因 RN 非常依賴套件,在選擇套件時須先注意該套件是否還是有持續在維護,避免後續遇到版本更替 or 更新導致套件掛掉的問題 ### UI 庫 列出的均為星數高、維護頻率高的 UI 庫 * [RNUILib (react-native-ui-lib)](https://github.com/wix/react-native-ui-lib) 元件很齊全,像是常用的按鈕、跑馬燈、輪播、日期選擇器、選擇器(selector)、數字Input、抽屜選單、Dialog... 等等 * [react-native-paper](https://github.com/callstack/react-native-paper) 此 UI 庫將風格統一為 Material Design 規範的風格和主題,讓元件的風格有一致性 基本常用的原件幾乎都有 React Native Paper 的另一個突出特點是其可定制的外觀。開發人員可以輕鬆調整組件的樣式,以滿足其應用程序的特定需求,同時仍然保持一致的外觀和感覺。 * [NativeBase](https://github.com/GeekyAnts/nativebase) 跨平台元件,除了行動裝置外 web 元件也都能共用,基本常用的原件幾乎都有 今年(2023)推出 [gluestack-ui](https://ui.gluestack.io/) 並轉推廣該 UI 庫,[[詳細文章](https://nativebase.io/blogs/road-ahead-with-gluestack-ui)],目的為改善解決NativeBase v3的性能和可維護性問題,目前看起來下載量不多、但的確是很頻繁在維護,目前也還是 Beta 階段,是否要使用則可在觀望看看。[詳細介紹](https://docs.nativebase.io/?utm_source=HomePage&utm_medium=Hero_Fold&utm_campaign=NativeBase_3) * [react-native-elements](https://reactnativeelements.com/) 跨平台元件,除了行動裝置外 web 元件也都能共用,文件很清楚甚至還有提供一個 playground 讓你直接去調整元件樣式直接使用,元件算齊全,常用的都有。 ### 路由 / 頁面切換 [react-navigation](https://reactnavigation.org/docs/getting-started/) **重點使用功能**: * [路由設置 / 頁面切換](https://reactnavigation.org/docs/navigating) * [路由參數傳遞](https://reactnavigation.org/docs/params) * [Header](https://reactnavigation.org/docs/headers) [Native Stack Navigator](https://reactnavigation.org/docs/native-stack-navigator) * [Bottom Tabs Navigator](https://reactnavigation.org/docs/bottom-tab-navigator) :::warning 1. 不能直接和 `Stack.Navigator` 共用一個 `NavigationContainer` 2. 基本上可以取代 `Stack.Navigator` 使用 3. 使用 `Tab.Navitator` 不能直接使用 `navigation.push` 以及 `navigation.popToTop` 功能 * 若要使用需以 [StackActions reference](https://reactnavigation.org/docs/stack-actions/) 方式使用 * 若要用 `push` 的方式切換頁面必須在 `Stack.Navigator` 中註冊的 `Stack.Screen` 才可以順利被切換渲染 4. 若要同時使用 `Tab.Navigator` 和 `Stack.Navigator` 參考官方文件 - [Nesting navigators](https://reactnavigation.org/docs/nesting-navigators/) ::: * [Drawer](https://reactnavigation.org/docs/drawer-navigator) [Drawar 安裝說明 & 流程](https://reactnavigation.org/docs/drawer-navigator/#installation) * [Tab View(類似NavTabs)](https://reactnavigation.org/docs/tab-view/) ### 輪播 * [Swiper FlatList component](https://github.com/gusgard/react-native-swiper-flatlist) ### Action Sheet(可用於替代下拉、dropdown) * [@expo/react-native-action-sheet](https://github.com/expo/react-native-action-sheet) ### Icon * [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons#icon-component) ### SVG 解決RN預設無法使用Svg作為圖檔顯示的問題 * [react-native-svg](https://github.com/software-mansion/react-native-svg) * [react-native-svg-transformer](https://github.com/kristerkari/react-native-svg-transformer) ### 跑馬燈 * [react-native-marquee](https://github.com/kyo504/react-native-marquee) 很陽春,但上次更新時間較近期 * [react-native-text-ticker](https://github.com/deanhet/react-native-text-ticker) 下載量和星數最高,跑馬燈的效果較滑順且功能較齊全,但上次更新時間較久遠 * [react-native-auto-scrolling](https://github.com/homielab/react-native-auto-scroll) 雖然下載量和星數最低,功能也較基本,但畫面順暢也保有一定的彈性,上次更新時間也較近 ### 動畫套件 * [react-native-animatable](https://github.com/oblador/react-native-animatable) ## 串接資料 ### React Native 內置 Fetch API 基本用法參考 [官方文件](https://reactnative.dev/docs/network) RN 官方提供和 Web 標準一致的 Fetch API 供開發者發送和接收網絡請求 適合用於簡單的發送接收需求,但若遇到較複雜的判斷或需設置攔截器就還是建議使用 Axios ### Axios 使用起來跟一般網頁版沒什麼差別 [Using Axios with React Native to manage API requests](https://blog.logrocket.com/using-axios-with-react-native-manage-api-requests/) ### Apisauce [Apisouse Github](https://github.com/infinitered/apisauce) > Axios + standardized errors + request/response transforms. Apisauce是一個輕量級、快速的HTTP客戶端庫,使用 Axios 並更好地管理 API 的響應。 它以Axios為基礎構建,並添加了定制標準錯誤格式和請求/響應轉換器的風格。 ### 狀態管理庫 當專案 APP 相對簡單,並只需在幾個組件之間共享簡單的狀態時,Context API 是一個輕量級和方便的解決方案。 但若是專案 APP 的資料結構相對複雜,需要處理大量狀態邏輯和異步操作時會建議使用 Redux。 [參考文章 - React context API vs Redux](https://levelup.gitconnected.com/react-context-api-vs-redux-99d46b8ff2eb) #### Context API [參考文章 - React Native + Context API|在 React Native 中使用 useContext!](https://molly1024.medium.com/react-native-context-api-%E5%9C%A8-react-native-%E4%B8%AD%E4%BD%BF%E7%94%A8-usecontext-f7add40f961f) #### Redux [Redux 官方文件](https://redux.js.org/introduction/getting-started#create-a-react-redux-app) [react-native中使用redux](https://juejin.cn/post/7049545179258159135) [React Native - 整理資料流,使用 Redux 吧!](https://ithelp.ithome.com.tw/articles/10274241) **使用 Redux 可搭配 `redux toolkit`** > Redux Toolkit 官方的推薦使用的Redux工具,用於提高 Redux 開發。 它包含幾個實用功能,可簡化最常見的 Redux 用例,包括存儲設置、定義 reducer、不可變更新邏輯,甚至無需手動編寫任何動作創建者或動作類型即可一次創建整個狀態Slice。 它還包括最廣泛使用的 Redux 插件,例如用於異步邏輯的 Redux Thunk 和用於編寫選擇器函數的 Reselect,以便您可以立即使用它們。 引用自: [Redux 深入淺出 - [ Day 12 ] Redux Toolkit 簡介](https://ithelp.ithome.com.tw/articles/10288668) [Redux Toolkit 官網連結](https://redux-toolkit.js.org/) [Day16-Redux 篇-認識 Redux Toolkit](https://ithelp.ithome.com.tw/articles/10275089) [Using Redux Toolkit in React Native: Getting started and usage guide](https://hybridheroes.de/blog/2021-01-08-redux-toolkit-react-native/) ### 多語系 * [react-i18next](https://github.com/i18next/react-i18next) [官方文件](https://react.i18next.com/) [官方文件 - API](i18next.com/overview/api) [官方應用範例 - reactnative-expo](https://github.com/i18next/react-i18next/tree/master/example/v9.x.x/reactnative-expo) #### 基本設置應用 1. 安裝 npm: `npm install react-i18next i18next --save` yarn: `yarn add react-i18next i18next` 2. 目錄結構 ``` ├── src │ ├── locale │ │ ├── lang │ │ │ ├── cn.json │ │ │ ├── en.json │ │ │index.js │ App.js ``` 3. `src/locale/lang/index.js` ```javascript= import {initReactI18next} from 'react-i18next'; import i18n from 'i18next'; import en from './lang/en.json'; import cn from './lang/cn.json'; i18n.use(initReactI18next).init( { compatibilityJSON: 'v3', // 附註補充添加的第一種解法 resources: { en: {translation: en}, cn: {translation: cn}, // 有新的語言就加在這邊 }, lng: 'en', // 預設語言 fallbackLng: 'en', // 默認/備用語言,當設置不存在語言時會套用 interpolation: { escapeValue: false, // React already safely escapes all strings }, }, err => { // 錯誤處理 if (err) { throw err; } i18n.setLocalLanguage = function (val) { // 設置項目文本的語言 this.changeLanguage(val); // 可以接著處理別的套件語言切換 }; // 初始化 i18n.setLocalLanguage(i18n.language); }, ); export default i18n; ``` 4. 在 App.js / App.ts 載入 ```javascript= import './src/locale/index'; ``` 5. 在頁面中使用 ```javascript= import {useTranslation} from 'react-i18next'; function App(): JSX.Element { const {t, i18n} = useTranslation(); return ( <View> <Text>{t('hello')}</Text> <Text>{t('welcome')}</Text> <Button title="Switch Language" onPress={() => i18n.changeLanguage(i18n.language === 'en' ? 'cn' : 'en') } /> </View> ); } ``` ==**附註補充:**== 若出現下列錯誤: > i18next::pluralResolver: Your environment seems not to be Intl API compatible, use an Intl.PluralRules polyfill 解法有兩種,參考: [stackoverflow](https://stackoverflow.com/questions/70493788/i18nextpluralresolver-your-environment-seems-not-to-be-intl-api-compatible-u) --- #### 其他常用 - **[Interpolation (插值)](https://www.i18next.com/translation-function/interpolation)** 翻譯文本: ```javascript= // 用雙大括號包住變數 { "key": "{{what}} is {{how}}" } ``` 應用: ```javascript= <Text>{t('test', {what: 'Apple', how: '蘋果'})}</Text> // 畫面輸出為 Apple is 蘋果 ``` - **Formatting** 此功能應用很廣泛,像是可以改變翻譯變數的大小寫、數字依照位數加逗號、小數點幾位數補零、格式化時間... 等等功能,詳細使用方法可以參考 [官方文件](https://www.i18next.com/translation-function/formatting) ### 表單 * [react-hook-form](https://www.react-hook-form.com/) [官方簡中文檔](https://reacthookform.caitouyun.com/zh/) 星數高、維護頻率高、文件完整還有影片教學 支援 ios、安卓、expo go、web React-Hook-Form 支持對每個字段設置獨立的校驗規則, 且同時支持預置的 HTML 校驗規則和第三方庫的 schema 校驗。 :::warning React-Hook-Form 預置了以下的校驗規則,這些規則與現有的 HTML 格式驗證標準一致。 - required - min - max - minLength - maxLength - pattern - validate ::: 基本使用範例: ```javascript= import React from 'react'; import {Text, View, TextInput, Button, StyleSheet} from 'react-native'; import {useForm, Controller} from 'react-hook-form'; function App(): JSX.Element { const { control, handleSubmit, formState: {errors}, } = useForm({ defaultValues: { firstName: '', lastName: '', }, }); const onSubmit = (data: any) => console.log(data); return ( <View style={styles.container}> <Controller control={control} // 校驗規則 rules={{ required: true, }} render={({field: {onChange, onBlur, value}}) => ( <TextInput placeholder="First name" onBlur={onBlur} onChangeText={onChange} value={value} style={styles.field} /> )} name="firstName" /> {errors.firstName && <Text style={styles.error}>This is required.</Text>} <Controller control={control} rules={{ maxLength: 100, }} render={({field: {onChange, onBlur, value}}) => ( <TextInput placeholder="Last name" onBlur={onBlur} onChangeText={onChange} value={value} style={styles.field} /> )} name="lastName" /> <View style={styles.button}> <Button title="Submit" onPress={handleSubmit(onSubmit)} color="#f194ff" /> </View> </View> ); } ``` #### 驗證工具 * yup react-hook-form 依賴包 `npm install @hookform/resolvers yup` 使用範例: ```javascript= import React from 'react'; import {Text, View, TextInput, Button, StyleSheet} from 'react-native'; import {useForm, Controller} from 'react-hook-form'; import {yupResolver} from '@hookform/resolvers/yup'; import * as yup from 'yup'; const schema = yup.object().shape({ firstName: yup .string() // 驗證錯誤要出現的文字放在括號裡 .required('This is required.'), age: yup .number() .typeError('Please enter a valid number') .positive('Please enter an integer') .integer() .required('This is required.'), }); function App(): JSX.Element { const { control, handleSubmit, formState: {errors}, } = useForm({ // yup resolver 驗證的 schema 放這裡 resolver: yupResolver(schema), }); const onSubmit = (data: any) => console.log(data); return ( <View style={styles.container}> <Controller control={control} render={({field: {onChange, onBlur, value}}) => ( <TextInput placeholder="First name" onBlur={onBlur} onChangeText={onChange} value={value} style={styles.field} /> )} name="firstName" /> {errors.firstName && ( // 錯誤提示文字 <Text style={styles.error}>{errors.firstName.message}</Text> )} <Controller control={control} render={({field: {onChange, onBlur, value}}) => ( <TextInput placeholder="age" onBlur={onBlur} onChangeText={onChange} value={value !== undefined ? String(value) : ''} keyboardType="numeric" style={styles.field} maxLength={3} /> )} name="age" /> {errors.age && <Text style={styles.error}>{errors.age.message}</Text>} <View style={styles.button}> <Button title="Submit" onPress={handleSubmit(onSubmit)} color="#f194ff" /> </View> </View> ); } const styles = StyleSheet.create({ error: { color: 'red', }, field: { border: 1, borderColor: '#faf', borderRadius: 4, backgroundColor: 'white', marginTop: 10, marginBottom: 10, }, container: { flex: 1, justifyContent: 'center', padding: 8, backgroundColor: '#0e101c', }, button: { marginTop: 20, marginBottom: 20, }, }); export default App; ``` #### 搭配 UI 庫使用 ```javascript= // 範例使用 react-native-paper import {TextInput} from 'react-native-paper'; <Controller control={control} // 把 ui 庫的 component 放到 render 渲染 render={({field: {onChange, onBlur, value}}) => ( <TextInput label="First name" onBlur={onBlur} onChangeText={onChange} value={value} style={styles.field} /> )} name="firstName" /> {errors.firstName && ( <Text style={styles.error}>{errors.firstName.message}</Text> )} /> ``` --- ## :memo: 其他補充 ### 雙平台開發 **注意事項:** 1. [React Native 跨平台裝置判斷](https://ithelp.ithome.com.tw/articles/10267731) [官方文件 - Platform-Specific Code](https://reactnative.dev/docs/platform-specific-code) ```javascript= import { Platform } from 'react-native'; //使 用 Platform.OS 來判斷裝置 const styles = StyleSheet.create({ height: Platform.OS === 'ios' ? 200 : 100 // 於 StyleSheet.create 運用 Platform.select const styles = StyleSheet.create({ container: { flex: 1, ...Platform.select({ ios: { backgroundColor: 'red' }, android: { backgroundColor: 'green' }, default: { backgroundColor: 'blue' } }) } }); }); ``` 2. 樣式,元件預設樣式 ios 和 安卓的會有落差需要注意 3. 預設的 Button 元件內容只能塞純文字,若 Button 需要用到圖片、icon 的話,可以使用 Pressable 包裝,或是有區塊需要能觸發點選 Event 也可用 Pressable 包裝 [官方參考文件](https://reactnative.dev/docs/pressable#example) ```javascript= // 官方範例 <Pressable onPress={onPressFunction}> <Text>I'm pressable!</Text> </Pressable> ``` ### CLI 補充 #### ignite CLI 測試時也有試著搭配 ignite 開發 雖然會建立專案基本的架構以及常用工具的配置 但 ignite 非常大包外,它的資料夾結構、官方文件都蠻雜的不太容易閱讀 因此後續就不列入評估中 #### Expo CLI 因 Expo 版本更新很快,需要注意==版本相容==的問題 開發時遇到剛更新到 49.0 因套件版本不相容導致無法順利開發 後續降版本至 `48.0.18` 才能順利開發 ## :bookmark_tabs: 討論項目 1. 是否要使用 EXPO 開發? 2. 目前預計會需要的功能 ? (例如: 開啟相機、指紋... etc)