使用umi開發(fā)react-native應(yīng)用

動機

很早之前看過這樣一個漫畫三圆,如何用8種編程語言去救公主:

JavaScript: 你是一個喝著咖啡的有品味的騎士。你花了好多時間去選擇支持庫扔茅,設(shè)置節(jié)點和為城堡建造一個框架。當(dāng)你完成了框架的時候,你發(fā)現(xiàn)公主所在的要塞已經(jīng)被廢棄矩距,公主也已經(jīng)搬到了另外一個城堡。

記得似乎是從 nextjs 起怖竭,前端框架就進入了帶編譯時的時代锥债。

自此,開發(fā)者可以迅速投入到業(yè)務(wù)代碼的開發(fā)痊臭,而不用去搭建腳手架哮肚,寫一堆配置和膠水代碼去整合各種框架等等。

筆者在Web端習(xí)慣使用 umi 后广匙,就變得越來越“懶”允趟,什么問題都用這一錘子解決。

當(dāng)工作中涉及到 react-native(后文簡稱:RN)應(yīng)用的內(nèi)容時鸦致,發(fā)現(xiàn) umi 暫時沒有支持RN的打算潮剪。

筆者從Github clone了 umi 的代碼研究學(xué)習(xí)后發(fā)現(xiàn)整個 umi 引擎設(shè)計的非常科學(xué)分唾。

基于 umi 插件化的思想抗碰,很容易就能擴展一些額外的能力用于支持 RN 的開發(fā)。

于是就產(chǎn)生了這個項目:umi-react-native绽乔。

umi 在 RN 中僅用來生成中間代碼(臨時文件)弧蝇,介于編碼構(gòu)建的之間,旨在引入 umi 的開發(fā)姿勢來提升 RN 編程體驗折砸。

下游可以使用:

  • React Native CLI:RN 官方開發(fā)/打包工具看疗;
  • expo:不需要搭建 iOS 和 Android 開發(fā)環(huán)境,工程目錄干凈清爽鞍爱,添加 RN 依賴方便快捷鹃觉;
  • haul:第三方 RN 打包器,使用 webpack睹逃。缺點是不支持:Fast Refresh盗扇、Live Reloading祷肯、Hot Replacement。

目前的版本已經(jīng)支持:

  • 零配置疗隶,添加dva佑笋,@ant-design/react-native... 等依賴后開箱即用;
  • 只需要專注頁面 UI 和業(yè)務(wù)領(lǐng)域模型的實現(xiàn)斑鼻,所有編譯配置蒋纬,框架運行所需 HOC 和 Context Provider 全部由 umi 搞定;
  • 路由方案默認使用 umi 內(nèi)置的react-router坚弱,可選react-navigation蜀备;
  • 啟用dynamicImport配置后,支持拆包荒叶,運行時從本地按需加載 JS bundle 文件碾阁。

實施

下面將詳細介紹umi-react-native的使用方式。

你也可以略過本文直接查看示例工程

當(dāng) RN 工程滿足下列條件時些楣,會進行拆包:

必備

  • RN 工程愁茁;
  • umi 3.0 及以上版本蚕钦。

概覽

NPM 包 簡介
umi-plugin-antd-react-native @ant-design/react-native提供按需加載主題定制鹅很、預(yù)設(shè)嘶居、切換,國際化支持道宅,在expo鏈接字體圖標(biāo)食听。
umi-preset-react-native 基礎(chǔ)包,讓umi具備開發(fā) RN 的能力污茵。需要 react-native 0.44.0 及以上版本(>=0.44.0)
umi-preset-react-navigation 使用react-navigation替換react-router開發(fā)地道的原生應(yīng)用樱报。需要 react-native 0.60.0 及以上版本(>=0.60.0)
umi-renderer-react-navigation 支持以react-navigation的方式來渲染react-router所定義的路由模型。無須單獨安裝該依賴
umi-react-native-multibundle RN Bridge API泞当,為 JS 層提供按需加載 Bundle 文件的能力迹蛤。需要 react-native 0.62.2 及以上版本(>=0.62.2)

安裝

如果沒有 RN 工程,則使用react-native init得到初始工程:

npx react-native init UMIRNExample

在 RN 工程根目錄下使用 yarn 添加umiumi-preset-react-native依賴:

yarn add umi umi-preset-react-native --dev

集成 dva

在 RN 工程根目錄下使用 yarn 添加@umijs/plugin-dva依賴:

yarn add @umijs/plugin-dva --dev

集成 @ant-design/react-native

在 RN 工程目錄下襟士,使用 yarn 安裝@ant-design/react-native:

yarn add @ant-design/react-native && yarn add umi-plugin-antd-react-native --dev

@ant-design/react-native 當(dāng)前(2020/05/14)版本:3.x盗飒。如需使用4.x請按照:安裝 & 使用操作。

集成 react-navigation(可選)

react-navigation可作為 umi 默認react-router替代方案陋桂。

需要 react-native 0.60.0 及以上版本(>=0.60.x)

安裝所有react-navigation的依賴到 RN 工程本地:

yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

RN0.60.0 及以上版本有自動鏈接功能逆趣,Android 會自動搞定這些react-navigation的原生依賴,但對于iOS嗜历,待 yarn 安裝完成后宣渗,還需要進到 ios 目錄抖所,使用 pod 安裝:

cd ios && pod install
image

最后,使用 yarn 安裝umi-preset-react-navigation

yarn add umi-preset-react-navigation --dev

查看詳情:umi-preset-react-navigation痕囱。

配置

All dependencies start with @umijs/preset-田轧、@umijs/plugin-、umi-preset-鞍恢、umi-plugin- will be registered as plugin/plugin-preset.

umi 3.x 后會自動探測傻粘、裝配插件。所以不需要在.umirc.js中配置pluginspresets帮掉。

在 RN 中集成其他umi插件需要開發(fā)者自行斟酌弦悉。

umi插件包括:

與 DOM 無關(guān)的umi插件都是可以使用的警绩,或者說支持服務(wù)端渲染的插件基本也是可以在 RN 運行環(huán)境中使用的。

umi-preset-react-native 擴展配置

umi-preset-react-native會探測用戶工程內(nèi)的依賴盅称,自動為下列工具生成所需的配置文件入口文件

推薦在.gitignore文件末尾后室,追加以下內(nèi)容:

# umi-react-native
tmp
index.js
metro.config.js
babel.config.js
haul.config.js

如果你的 RN 工程只使用一種開發(fā)工具則無需任何配置缩膝。

如果你的 RN 工程安裝了多種開發(fā)工具,則必須通過 umi 配置指定當(dāng)前使用哪一個:

使用expo

// .umirc.js
export default {
  expo: true,
  haul: false,
};

使用haul

// .umirc.js
export default {
  expo: false,
  haul: true,
};

使用React Native CLI:

// .umirc.js
export default {
  expo: false,
  haul: false,
};

Babel 配置

使用extraBabelPluginsextraBabelPresets添加額外的 Babel 配置岸霹。

Metro 配置

添加額外的Metro 配置需要使用環(huán)境變量:UMI_ENV指定要加載的配置文件:metro.${UMI_ENV}.config.js疾层。

比如,執(zhí)行UMI_ENV=dev umi g rn時贡避,會加載metro.dev.config.js文件中的配置痛黎,使用mergeConfigmetro.config.js中的配置進行合并。

使用

開發(fā)

修改package.json文件:

{
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
+   "watch": "umi g rn --dev",
    "test": "jest",
    "lint": "eslint ."
  }
}

啟動 watch 進程刮吧,監(jiān)聽文件變動湖饱,重新生成中間代碼:

yarn watch

接下來,另啟一個終端杀捻,編譯并啟動 Android 應(yīng)用:

yarn android

編譯并啟動 iOS 應(yīng)用:

yarn ios

打包

先使用 umi 生成臨時代碼:

umi g rn

再使用react-native bundle構(gòu)建離線包(offline bundle)井厌。

路由

umi-preset-react-native提供了 2 種可相互替代的路由方案:

使用 umi 內(nèi)置的 react-router

umi內(nèi)置了react-router-domumi-preset-react-native使用alias在編譯時將其替換為:react-router-native致讥。

二者都基于 react-router仅仆,但存在一些差異。

Link組件在 RN 和 DOM 中存在差異

以下是react-router-native Link組件的屬性:

Link.propTypes= {
  onPress: PropTypes.func,
  component: PropTypes.elementType,
  replace: PropTypes.bool,
  to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
};

在 RN 中垢袱,只能這樣使用Link

import React from 'react';
import { Link } from 'umi';
import { List } from '@ant-design/react-native';

const Item = List.Item;

function Index() {
  return (
    <List>
      <Link to="/home" component={Item} arrow="horizontal">
        主頁
      </Link>
      <Link to="/login" component={Item} arrow="horizontal">
        登錄頁
      </Link>
    </List>
  );
}

沒有NavLink組件

react-router-native沒有NavLink組件墓拜,當(dāng)你嘗試引入時會得到undefined

import { NavLink } from 'umi';

typeof NavLink=== 'undefined'; // true;

新增BackButtonAndroidBackButton組件

對于 RN 應(yīng)用,需要在全局 layout中使用BackButton作為根容器:

// layouts/index.js
import React from 'react';
import { SafeAreaView, StatusBar } from 'react-native';
import { BackButton } from 'umi';
const Layout = ({ children }) => {
  return (
    <BackButton>
      {children}
    </BackButton>
  );
};

export default Layout;

這樣做请契,當(dāng)用戶使用Android 系統(tǒng)返回鍵時會返回應(yīng)用的上一個路由咳榜,而不是退出應(yīng)用夏醉。

使用 react-navigation

擴展配置

以下是安裝umi-preset-react-navigation后,擴展的 umi 配置

reactNavigation

theme字段選填贿衍,下面示例中填入的是默認值授舟,等價于不填:

// .umirc.js
export default {
  reactNavigation: {
    // 使用 ant-design 默認配色作為導(dǎo)航條的默認主題
    theme: {
      dark: false,
      colors: {
        primary: '#108ee9',
        background: '#ffffff',
        card: '#ffffff',
        text: '#000000',
        border: '#dddddd',
      },
    },
  },
};

擴展運行時配置

查看 umi 文檔,了解什么是:運行時配置贸辈。

以下是安裝umi-preset-react-navigation后释树,擴展的運行時配置

getReactNavigationInitialState

異步(async)函數(shù),返回的 promise resolve 后的結(jié)果會傳給 react-navigation 作為初始狀態(tài)擎淤。

返回類型:Promise<object | void | undefined>奢啥。

getReactNavigationInitialIndicator

自定義初始化 react-navigation 狀態(tài)過程中的指示器/Loading。通常在實現(xiàn)了上面的getReactNavigationInitialState后才會生效嘴拢。

缺省情況下:

  1. 如果未啟用dynamicImport配置桩盲,則會使用一個內(nèi)置的簡陋 Loading;
  2. 如果啟用dynamicImport配置席吴,則會使用dynamicImport.loading赌结;
    1. 如果未實現(xiàn)自定義的dynamicImport.loadingdynamicImport默認的 Loading 同樣也很簡陋孝冒。
onReactNavigationStateChange

異步(async)函數(shù)柬姚,用于訂閱 react-navigation 狀態(tài)變更通知,在每次路由變動時庄涡,接收最新狀態(tài)量承。

案例:持久化導(dǎo)航狀態(tài)

RN 工程根目錄下app.js文件:

// app.js
import { Linking, Platform, Text } from 'react-native';
/**
 * AsyncStorage 將來會從 react-native 庫中移除。
 * 按照 RN 官方文檔引用:https://github.com/react-native-community/async-storage
 */
import AsyncStorage from '@react-native-community/async-storage';

const PERSISTENCE_KEY = 'MY_NAVIGATION_STATE';

// 返回之前本地持久化保存的狀態(tài)穴店,通常用于需要復(fù)蘇應(yīng)用撕捍、狀態(tài)恢復(fù)的場景。
export async function getReactNavigationInitialState() {
  try {
    const initialUrl = await Linking.getInitialURL();
    if (Platform.OS !== 'web' && initialUrl == null) {
      const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
      if (savedStateString) {
        return JSON.parse(savedStateString);
      }
    }
  } catch (ignored) {}
}

// 自定義返回初始狀態(tài)過程中顯示的Loading泣洞,只有實現(xiàn)了 getReactNavigationInitialState 才會生效忧风。
export function getReactNavigationInitialIndicator() {
  // 下面這個就是內(nèi)置的簡陋Loading:
  return ({ error, isLoading }) => {
    if (__DEV__) {
      if (isLoading) {
        return React.createElement(Text, null, 'Loading...');
      }
      if (error) {
        return React.createElement(
          View,
          null,
          React.createElement(Text, null, error.message),
          React.createElement(Text, null, error.stack),
        );
      }
    }
    return React.createElement(Text, null, 'Loading...');
  };
}

// 訂閱 react-navigation 狀態(tài)變化通知,每次路由變化時斜棚,將導(dǎo)航狀態(tài)持久化保存到手機本地阀蒂。
export async function onReactNavigationStateChange(state) {
  if (state) {
    await AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
  }
}
  • 如果你需要用到@react-native-community/async-storage請按照https://github.com/react-native-community/async-storage安裝;
  • 安裝完成后弟蚀,記得進到 ios 目錄使用 pod 安裝原生依賴:cd ios && pod install && cd -蚤霞,之后記得使用yarn iosyarn android重新編譯,啟動原生 App义钉。

擴展路由屬性

查看 umi 文檔昧绣,了解什么是:擴展路由屬性

案例:單獨為某個頁面設(shè)置導(dǎo)航條

使用擴展路由屬性定制頂部導(dǎo)航條:

import React from 'react';
import { Text } from 'react-native';
import { Button } from '@ant-design/react-native';

function HomePage({ navigation }) {
  // 處理導(dǎo)航條右側(cè)按鈕點擊事件
  function onHeaderRightPress() {
    // do something...
  }

  // 設(shè)置導(dǎo)航條右側(cè)按鈕
  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button type="primary" size="small" onPress={onHeaderRightPress}>
          彈窗
        </Button>
      ),
    });
  }, [navigation]);

  return <Text>Home Page</Text>;
}

// 擴展路由屬性:
HomePage.title = 'Home Page';
HomePage.headerTintColor = '#000000';
HomePage.headerTitleStyle = {
  fontWeight: 'bold',
};
HomePage.headerStyle = {
  backgroundColor: '#ffffff',
};
// headerRight 也可以寫在這里:
// HomePage.headerRight = () => (
//  <Button type="primary" size="small">
//    彈窗
//  </Button>
// );

export default HomePage;

如果頁面的title屬性未設(shè)置捶闸,則使用.umirc.js中的全局title夜畴。

頁面間跳轉(zhuǎn)

查看 umi 文檔:頁面間跳轉(zhuǎn)拖刃,姿勢保持不變。

使用聲明式Link組件時需要注意贪绘,在 RN 中 與 DOM 存在較大差異:

import React from 'react';
import { Link } from 'umi';
import { List } from '@ant-design/react-native';

const Item = List.Item;

function Index() {
  return (
    <List>
      <Link to="/home" component={Item} arrow="horizontal">
        主頁
      </Link>
      <Link to="/login" component={Item} arrow="horizontal">
        登錄頁
      </Link>
    </List>
  );
}

使用命令式跳轉(zhuǎn)頁面時兑牡,只能使用history的 API,umi-preset-react-navigation目前還不支持使用react-navigation提供的navigation來跳轉(zhuǎn)税灌,只能做導(dǎo)航條設(shè)置之類的操作均函。

頁面間傳遞/接收參數(shù)

IndexPage點擊Link,攜帶query參數(shù)路由到HomePage:

import React from 'react';
import { Link } from 'umi';
import { List } from '@ant-design/react-native';

const Item = List.Item;

export default function IndexPage() {
  return (
    <List>
      <Link to="/home?name=bar" component={Item} arrow="horizontal">
        主頁
      </Link>
    </List>
  );
}
export default function HomePage({ route }) {
  console.log(route); // route 屬性字段查看下面

  // ...
}

route屬性示例:

{ "key": "/home-WnnfQomYXFls0kS0v0lxo", "name": "/home", "params": { "name": "bar" } }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菱涤,一起剝皮案震驚了整個濱河市苞也,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粘秆,老刑警劉巖如迟,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異攻走,居然都是意外死亡殷勘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門昔搂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劳吠,“玉大人,你說我怎么就攤上這事巩趁。” “怎么了淳附?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵议慰,是天一觀的道長。 經(jīng)常有香客問我奴曙,道長别凹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任洽糟,我火速辦了婚禮炉菲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坤溃。我一直安慰自己拍霜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布薪介。 她就那樣靜靜地躺著祠饺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汁政。 梳的紋絲不亂的頭發(fā)上道偷,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天缀旁,我揣著相機與錄音,去河邊找鬼勺鸦。 笑死并巍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的换途。 我是一名探鬼主播懊渡,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼怀跛!你這毒婦竟也來了距贷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吻谋,失蹤者是張志新(化名)和其女友劉穎忠蝗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漓拾,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡阁最,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了速种。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡示血,死狀恐怖难审,靈堂內(nèi)的尸體忽然破棺而出麸拄,到底是詐尸還是另有隱情拢切,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布豺撑,位于F島的核電站,受9級特大地震影響陆错,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绳慎,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一已脓、第九天 我趴在偏房一處隱蔽的房頂上張望厕宗。 院中可真熱鬧媳瞪,春花似錦厕鹃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至审胚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背临谱。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工抄课, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像迎变,于是被迫代替她去往敵國和親充尉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354