- 原文地址:How to: React Native Web app. A Happy Struggle.
- 原文做者:Lucas Mórawski
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:weibinzhu
- 校對者:Moonliujk, nanjingboy
你醒來。陽光燦爛,鳥兒在歌唱。沒有戰爭,沒有飢餓,代碼能夠輕易地被原生和 web 環境共享。是否是很贊?但很不幸,僅僅是後者,但願雖已經在地平線上,但仍然有一些事情須要咱們去完成。前端
現在在技術縮寫的海洋裏面,PWA(漸進式 Web 應用程序)是一個重要的三字詞語,可是它仍然有缺點。有不少被迫在開發原生應用之外還要開發 web 版的案例,其中也有不少技術難題。Ian Naylor 寫了一篇很棒的關於這個的文章。node
可是,對於你的電子商務生意,僅僅開發一個原生應用也是一個大錯誤。所以製做一個可以在全部地方工做的軟件彷佛是一個合乎邏輯的操做。你能夠減小工做時間,以及生產、維護的費用。這就是爲何我開始了這個小小的實驗。react
這是一個簡單的用於在線訂餐的電子商務通用應用例子。在此之上,我建立了一個樣板,用於未來的項目以及更深刻的實驗。android
Papu — 一個可用於安卓、iOS、web 的訂餐 APPwebpack
咱們使用 React 來開展咱們的工做,所以咱們應該將應用邏輯與 UI 分離。使用相似 Redux/MobX/other 這樣的狀態管理系統是最好的選擇。這將使得咱們的業務邏輯能在多個平臺之間複用。ios
視圖部分則是另一個難題。爲了構建你的應用的界面,你須要有一套通用的基本模塊。他們須要能同時在 web 與原生環境下使用。不幸的是,web 上有着一套不同的東西。git
<div>這是一個標準的 web 上的容器</div>
複製代碼
而在原生上github
<View>你好!我是 React Native 裏面的一個基礎容器</View>
複製代碼
有些聰明的人想到了如何解決這個問題。我最喜歡的解決方案之一就是由 Nicolas Gallagher 製做的偉大的 React Native Web 庫。不只僅是由於經過它可以讓你在 web 上使用 React Native 組件(不是所有組件!)來解決基本模塊的問題。它還暴露了一些 React Native 的 API,好比 Geolocation,Platform,Animated,AsyncStorage 等。快來 RNW guides 這裏看一些很棒的示例。web
咱們已經知道如何解決基本模塊的問題了,可是咱們仍然要試着將 web 頁與原生的生產環境『粘』在一塊兒。在個人項目中,我使用了 RN 的初始化腳本(沒有展現在這裏),而且對於 web 部分我使用了 create-react-app。首先我經過 create-react-app rnw_web
建立了一個項目,而後經過 react-native init raw_native
建立了另外一個。接着我在一個新的項目文件夾裏面,『科學怪人式』地將他們的 package.json
合併成一個,並在上面運行 yarn. 最終的 package 文件長這樣:json
{
"name": "rnw_boilerplate",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.5.1",
"react-art": "^16.5.1",
"react-dom": "^16.5.1",
"react-native": "0.56.0",
"react-native-web": "^0.9.0",
"react-navigation": "^2.17.0",
"react-router-dom": "^4.3.1",
"react-router-modal": "^1.4.2"
},
"devDependencies": {
"babel-jest": "^23.4.0",
"babel-preset-react-native": "^5",
"jest": "^23.4.1",
"react-scripts": "1.1.5",
"react-test-renderer": "^16.3.1"
},
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"start-ios": "react-native run-ios",
"start-web": "react-scripts start",
"build": "react-scripts build",
"test-web": "react-scripts test --env=jsdom",
"eject-web": "react-scripts eject"
}
}
複製代碼
React Native Web 樣板的 package.json 文件(在這個版本里面沒有導航)
你須要將全部在 web 和 native 目錄裏的源代碼文件複製到新的統一項目目錄中。
須要複製到新項目的文件夾
下一步,咱們將 App.js 與 App.native.js 放到咱們新建立的 src 文件夾中。感謝 webpack 咱們能夠經過文件拓展名來告訴打包器哪些文件用在哪些地方。這對於使用分離的 App 文件相當重要,由於咱們準備使用不一樣的方式進行應用導航。
// App.js - WEB
import React, { Component } from "react";
import { View } from "react-native";
import WebRoutesGenerator from "./NativeWebRouteWrapper/index";
import { ModalContainer } from "react-router-modal";
import HomeScreen from "./HomeScreen";
import TopNav from "./TopNav";
import SecondScreen from "./SecondScreen";
import UserScreen from "./UserScreen";
import DasModalScreen from "./DasModalScreen";
const routeMap = {
Home: {
component: HomeScreen,
path: "/",
exact: true
},
Second: {
component: SecondScreen,
path: "/second"
},
User: {
component: UserScreen,
path: "/user/:name?",
exact: true
},
DasModal: {
component: DasModalScreen,
path: "*/dasmodal",
modal: true
}
};
class App extends Component {
render() {
return (
<View>
<TopNav />
{WebRoutesGenerator({ routeMap })}
<ModalContainer />
</View>
);
}
}
export default App;
複製代碼
給 web 的 App.js. 這裏使用 react-router 進行導航。
// App.js - React Native
import React, { Component } from "react";
import {
createStackNavigator,
createBottomTabNavigator
} from "react-navigation";
import HomeScreen from "./HomeScreen";
import DasModalScreen from "./DasModalScreen";
import SecondScreen from "./SecondScreen";
import UserScreen from "./UserScreen";
const HomeStack = createStackNavigator({
Home: { screen: HomeScreen, navigationOptions: { title: "Home" } }
});
const SecondStack = createStackNavigator({
Second: { screen: SecondScreen, navigationOptions: { title: "Second" } },
User: { screen: UserScreen, navigationOptions: { title: "User" } }
});
const TabNav = createBottomTabNavigator({
Home: HomeStack,
SecondStack: SecondStack
});
const RootStack = createStackNavigator(
{
Main: TabNav,
DasModal: DasModalScreen
},
{
mode: "modal",
headerMode: "none"
}
);
class App extends Component {
render() {
return <RootStack />;
}
}
export default App;
複製代碼
給 React Native 的 App.js. 這裏使用了 react-navigation。
我就是這樣製做了一個簡單的樣板以及給應用構造了一個框架。你能夠經過克隆個人 github 倉庫來試一下我那個乾淨的樣板。
下一步咱們將經過加入路由/導航系統來讓它複雜一些。
除非你的應用只有一個頁面,不然你須要一些導航。如今(2018 年 9 月)只有一種可以在 web 與原生中都能用的方法:React Router。在 web 中這是一個導航方法,但對於 React Native 來講不徹底是。
React Router Native 缺乏頁面過渡動畫,對後退按鈕的支持(安卓),模態框,導航條等等。而其餘的庫則提供這些功能,例如 React Navigation.
我把它用在了個人項目中,可是你能夠用其餘的。因而我把 React Router 用在 web 端,把 React Navigation 用在原生。但這又致使了一個新問題。導航,以及傳參,在這兩個導航庫中有着很大不一樣。
爲了保持在全部地方都有着更多的原生體驗這個 React Native Web 的精神,我經過製做網頁路由並將它們包裹在一個 HOC 裏面來解決這個問題。這樣能暴露出相似 React Navigation 的 API。
這使得咱們無需給兩個『世界』分別製做組件便可實如今頁面之間導航。 第一步是建立一個用於 web 路由的路由 map 對象:
import WebRoutesGenerator from "./NativeWebRouteWrapper"; //用於生成 React Router 路由並將其包裹在一個 HOC 中的自定義函數
import WebRoutesGenerator from "./NativeWebRouteWrapper"; //用於生成 React Router 路由並將其包裹在一個 HOC 中的自定義函數
const routeMap = {
Home: {
screen: HomeScreen,
path: '/',
exact: true
},
Menu: {
screen: MenuScreen,
path: '/menu/sectionIndex?'
}
}
//在 render 方法中
<View>
{WebRoutesGenerator({ routeMap })}
</View>
複製代碼
這個語法與 React Navigation 的 navigator 構造函數的同樣,除了多了一個 React Router 特定的選項。而後,經過個人輔助函數,我建立了一個 react-router
路由。並將其包裹在一個 HOC 中。這回將頁面組件拷貝一份,並在其 props 中添加一個 navigation
屬性。這模擬了 React Navigation 並暴露出一些方法,像是 navigate()
, goBack()
, getParam()
。
經過它的 createStackNavigator
React Navigation 提供了一個選項,讓頁面像一個模態框同樣從底部滑出。爲了在 web 端實現這個,我使用了由 Dave Foley 寫的 React Router Modal 庫。爲了將某個頁面用做模態框,首先你須要在路由 map 中添加一個模態框選項:
const routeMap = {
Modal: {
screen: ModalScreen,
path: '*/modal',
modal: true //路由會用 ModalRoute 組件來渲染這個路由
}
}
複製代碼
此外你還須要添加一個 react-router-modal
庫中的 <ModalContainer />
組件到你的應用中。這是模態框將會被渲染的地方。
感謝咱們自定義的 HOC(暫時稱之爲 NativeWebRouteWrapper,話說這真是一個糟糕的名字),咱們可使用一套跟 React Navigation 中的幾乎同樣的函數來實如今 web 端進行頁面切換:
const { product, navigation } = this.props
<Button
onPress={navigation.navigate('ProductScreen', {id: product.id})}
title={`Go to ${product.name}`}
/>
<Button
onPress={navigation.goBack}
title="Go Back"
/>
複製代碼
在 React Navigation 中,你能夠回到導航棧中的前 n 個頁面。然而在 React Router 中則作不到,由於這裏沒有棧。爲了解決這個問題,你須要引入一個自定義的 pop 函數,以及傳一些參數進去。
import pop from '/NativeWebRouteWrapper/pop'
render() {
const { navigation } = this.props
return (
<Button
onPress={pop({screen: 'FirstScreen', n: 2, navigation})}
title="Go back two screens"
/>
)
}
複製代碼
screen
—— 頁面名字(在 web 端給 React Router 使用的) n
—— 須要返回多少個頁面(給 React Navigation 使用的) navigation
—— 導航對象
若是你想試一下這個想法,我製做了兩個樣板。
第一個只是一個給 web 與原生的通用生產環境。你能夠在這裏找到。
第二個則是第一個的增強版,添加了導航的解決方案。放到了這裏。
另外還有一個基於這個想法的叫作 papu 的 demo 應用。它有不少 bug 以及死衚衕,不過你能夠製做你本身的版本並在你的瀏覽器和手機上查看,感覺一下是怎麼工做的。
咱們真的很須要一個通用的導航庫來使咱們更容易地製做相似項目。讓 React Navigation 也能用在 web 環境會是很讚的事情(事實上今天你就能夠作到,不過這會是一次坎坷的旅途 —— 能夠到這裏瞭解一下)
感謝你花時間閱讀!若是你喜歡這篇文章,但願你能分享出去。這是個人推特 有什麼問題請在下方評論 😃
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。