最近在淘寶前端團隊(FED)發(fā)現(xiàn)了一個有趣的東西印荔,原理就是把React-Natvie的控件用React的重構(gòu)一片埠啃,然后就可以無縫過度了。不過看了一下有些重要的控件還是沒有的好像refresh這種,所以目前還不考慮仰挣。
原文鏈接:三步將 React Native 項目運行在 Web 瀏覽器上面
React Native 的出現(xiàn)冒冬,讓前端工程師擁有了使用 JavaScript 編寫原生 APP 的能力伸蚯。相比之前的 Web app 來說,對于性能和用戶體驗提升了非常多简烤。
但是 React Native 的代碼只兼容兩個平臺(iOS 和 Android)剂邮,并沒有兼容 Web 端訪問。這里是因為 Facebook 開發(fā)人員認為 Web 端天生兼容性就巨麻煩横侦,而且平臺差異性是注定存在而且也要保留的挥萌,所以 React Native 的目標是Learn once, write anywhere绰姻,而不是Write once, run anywhere。
然而Write once, run anywhere又是一個剛需瑞眼。從產(chǎn)品還是用戶的角度試想一下龙宏,APP 的安裝成本還是很高的,如何讓用戶馬上體驗到你產(chǎn)品的功能再決定是否要安裝伤疙?此外银酗,尤其是重要的產(chǎn)品,除了 APP 客戶端之外徒像,還要有一套兜底的 Web 端以便用戶在某些特殊場景下使用黍特。React Native 可以讓你寫一份代碼跑在兩個平臺,但是你卻還要再寫一份 Web 的一模一樣的應用锯蛀。就顯得十分蛋疼了灭衷。
于是 React web 就出現(xiàn)了。
簡單的一句話描述 React Web 就是:它幫你把 React Native 的組件做了一個 Web 端的實現(xiàn)旁涤,并提供相關(guān)打包工具翔曲,讓你可以直接打包出一份可以跑在 Web 端的代碼。
將 React Native 應用創(chuàng)建一個 Web 版的幾個步驟
為了重點突出轉(zhuǎn)換過程劈愚,這里使用 React Native init 的最簡 Demo 來做實驗(名字叫 Awes 代碼在https://github.com/taobaofed/demo/tree/gh-pages/react-web)瞳遍。React Web 已經(jīng)把 React Native 比較復雜的 UI Explorer Demo 跑起來了,所以只要你的代碼能跑在 iOS 或者 Android 上面菌羽,你基本不用擔心有什么組件上的問題掠械。當然如果有,可以馬上提 Issue 過來注祖,我們有一個小組在支持 React web :)猾蒂。
第一步:安裝 React web 并進行相關(guān)配置
這一步操作主要是安裝react-web包以及相關(guān)依賴,并配置 webpack 打包腳本等肚菠。
為了簡化這一步操作,我們開發(fā)了命令行工具react-web-cli只需要執(zhí)行兩行命令即可蚊逢。同時命令行工具還支持啟動調(diào)試服務(wù)器靴庆、打包等功能,在后面介紹怒医。
安裝 cli 工具:
npminstallreact-web-cli -g
安裝配置 React web 等:
react-web init<當前項目目錄>
執(zhí)行完成之后炉抒,會在你項目目錄下面npm install相關(guān)庫稚叹,并自動創(chuàng)建web/webpack.config.js文件拿诸,里面有一份寫好的配置。此時目錄結(jié)構(gòu)為:
.
├── README.md
├── android/
├── index.android.js
├── index.ios.js
├── ios/
├── package.json
└── web/
└── webpack.config.js
每個項目都需要有一個入口文件塞茅,通常用來引入調(diào)用其他組件并初始化項目亩码,比如index.ios.js表示 iOS 平臺上的該項目的入口文件。為了符合 React Native 的文件命名規(guī)范描沟,我們創(chuàng)建一個index.web.js作為入口文件鞭光,并且需要在 webpack 中指定該文件為入口文件。打開web/webpack.config.js文件惰许,修改config變量:
varconfig = {
paths: {
src: path.join(ROOT_PATH,'.'),
index: path.join(ROOT_PATH,'index.web'),
},
};
然后我們創(chuàng)建index.web.js文件。這個文件其實跟index.ios.js非常像佩伤,只是略有不同。主要區(qū)別在于:iOS 只需要AppRegistry.registerComponent('Awes', () => Awes);即可讓 Xcode 的 Native 代碼接收處理你的 JS 代碼生巡,而 Web 端是需要插入到 DOM 節(jié)點中才可以用结序。因此我們需要在index.web.js最下面添加如下代碼:
AppRegistry.registerComponent('Awes',()=>Awes);
if(Platform.OS =='web') {
var app =document.createElement('div');
document.body.appendChild(app);
AppRegistry.runApplication('Awes', {
rootTag: app
});
}
然后在最上面require部分需要引入Platform組件。這樣配置部分就已經(jīng)處理完成了垃环,執(zhí)行react-web start命令即可啟動調(diào)試服務(wù)器啦返敬!
可以隨便修改試下,跟 React Native 模擬器里面的體驗幾乎一樣涛目。
當你修改開發(fā)完凛澎,并對 Web 端也測試好了,就可以打包發(fā)布了塑煎。react-web-cli工具打包的命令是:
react-web bundle
打包完成后,文件會存放在web/output/目錄下面最铁,可以直接打開index.html(如果 app 有請求操作垮兑,需要起本地服務(wù)器查看)漱挎,再檢查一下就可以發(fā)布了。
好奇的同學看到這里可能會有一些疑問,上面命令行工具的一些命令做了什么事情当犯?為什么 React web 將 React Native 代碼打包出一份用在 Web 端的代碼割疾?React web 安全可靠嗎,里面都是什么東西拓诸?
這里簡單的介紹下 React web 的實現(xiàn)原理和上面步驟實際做的事情麻昼。
React Web 將 React Native 組件做了 Web 端的實現(xiàn)
React 將代碼與平臺環(huán)境分離,多了一層抚芦,這樣開發(fā)者可以在平臺環(huán)境層面做一些處理,使得同樣一份代碼適應更多的平臺環(huán)境等尔崔。
比如react-canvas按照 React 的語法書寫代碼褥民,在平臺環(huán)境層面做一些處理(將你 React 代碼運行并用 canvas 渲染),然后實現(xiàn)特定目標(在移動端提高性能)载弄。
React Native 中撵颊,一份代碼能同時跑在 iOS 和 Android 上面,也是一樣的道理倡勇。React Native 團隊在對應平臺的 Native app 上面做了一些處理,使其可以解析執(zhí)行 React 語法的代碼亲桥。
還有同構(gòu)(isomorphic)的應用固耘,服務(wù)器端使用 React + Node.js 生成 HTML,客戶端使用 React 獲取進行客戶端相關(guān)交互和功能番枚,也是一樣的道理损敷。
為此,React v0.14.x 版本開始路星,專門分成兩個庫react和react-dom诱桂,其實是把對瀏覽器平臺的特殊處理剝離了出來,單獨變成了react-dom庫挥等。
React Native 比較特殊的地方在于,組件最底層的實現(xiàn)是 Native 的實現(xiàn)迁客,所以就不支持span辞槐、div等標簽。而動畫等切威,也是直接調(diào)用 Native 進行界面渲染丙号。所以不支持 Web 端,但是絕大部分組件犬缨,都是可以用 Web 技術(shù)進行模擬實現(xiàn)。動畫可以用 CSS3 刺彩、基礎(chǔ)元素可以用同等 HTML 標簽模擬、布局以及兼容性問題可以用 CSS 來處理嗡害,所以 React web 只需要把 React Native 的組件用 Web 技術(shù)重新實現(xiàn)一遍畦攘,借助 React 這一層,即可實現(xiàn)一份代碼運行在多個平臺上面叹螟。
舉一個非常簡單的例子台盯,Text組件:
React Native 的實現(xiàn)是調(diào)用了很多 React Native 底層的代碼實現(xiàn)的。
對于 Web 端静盅,輸出一行文本使用標簽即可,所以React web 的實現(xiàn)就直接搞一個標簽棚壁,綁一些事件什么的就 OK 了栈虚。
在UI Explorer demo中能跑起來的 React Native 組件,你都可以放心的用曼验。
做出了兼容 Web 端的組件粘姜,那打包的時候豈不是要把所有要打包的組件中的require('react-native')全部更換成require('react-web')?不然怎么用的我的 Web 組件打包豺裆?
強大的 webpack 附帶了alias配置項可以幫你解決這個問題:
resolve:{
alias:{
'react-native':'react-web',
'ReactNativeART':'react-art',},
extensions:['', '.js', '.jsx'],
},
這樣在打包時号显,但凡require('react-native')的地方全都用react-web包替換,而react-web的module.exports與react-native的保持一致即可讓代碼不替換也可以工作蔑歌。
此外配合插件還可以實現(xiàn)另外一種引入方法揽碘,請看下面园匹。
webpack 以及其他的支持 CommonJS 規(guī)范的打包工具劫灶,都會把文件中 require 的所有組件都打包在一起。對于 React Native 來說代碼體積大小無關(guān)緊要累颂,而在 Mobile web 來說凛俱,就要稍微重要一些了料饥。特別是如果你的項目只需要Text組件,但由于require('react-web')結(jié)果把所有的組件全部打包進來了刘莹,就比較傷感。
基于 webpack 插件,還可以用另一種方式引入組件以解決這個問題唯欣,你可以叫它Haste方式搬味。使用這種方式需要加載 webpack 插件haste-resolver-webpack-plugin,默認的 webpack 配置已經(jīng)幫你加載好了碰纬,你可以直接在組件里面這樣用:
varText =require('ReactText');
而不是以前那樣:
var{Text} =require('react-native');
這樣 webpack 打包時悦析,對于前者,只會把那一個組件內(nèi)容打包進來强戴,因此可以減小體積、提升性能媒佣。這是怎么實現(xiàn)的呢陵刹?
加載了插件的 webpack 打包時欢嘿,會先掃描所有組件并讀取組件頭部@providesModule的信息(比如Text 組件的信息)也糊,然后當其他文件中 require 了這個組件名稱,就會自動定位到這個文件進行打包掐隐。同時還可以區(qū)分平臺钞馁,即便是同一個名字,打包時會區(qū)分平臺去打包對應的文件(根據(jù) index.xxx.js 的命名規(guī)則確定文件)探颈。
在 Web 端兼容性是個非常麻煩頭疼的事情训措,React Web 已經(jīng)盡力幫你抹平兼容性問題和代碼差異,盡可能的讓你減少改動就可以創(chuàng)建 Web 版本的應用怀大。但受限于 Web 端的一些固有限制(比如請求跨域)呀闻,不可避免的就會有一些需要你改代碼的地方。
為此屏鳍,可以通過if (Platform.OS == 'web')的方式判斷目標平臺局服,并針對性的做一些平臺兼容性處理。同樣的淫奔,也可以將web替換為ios或者android判斷其他平臺。
在React web 官方文檔上面已經(jīng)列出來了一些平臺差異問題鸭丛,這里就不再贅述了唐责。
歡迎踴躍嘗試,遇到問題可以隨時提 Issue 哦:)