React Web的目的及意義非常明確: 讓React Native代碼跑在Web上讓一套代碼運行在各個移動終端,對前端及業務來說,這是開發效率中一個質的提升。在項目初期,我們也曾向React團隊諮詢過類似的問題,他們團隊的核心同學@vjeux 也認為這是非常酷的事情,也是他們未來想做的事情。也許在發布React Native for Android的時候,也會發布React Web也說不定。 (YY一下)
技術架構
基於React Native的適配方案,有幾個:
1.制定一個Bridge標準,RN與RW 各自用最優的方式實現這套標準。
比如基於Flex佈局,我們實現一套一致的Flex Component, <Flex> 、<Cell> 等。
2.完全向RN看齊,RW實現RN的所有能實現的API。
在討論中,最終選擇了後者。
因為React Web的理念,讓React Native代碼跑在Web端,那麼就決定了RW只是一個構建及打包工具,脫離RN,RW的實現則沒有太大的意義,那麼整體的技術方向就非常明確了: 實現RN一致的Style、Component及API,最終通過構建工具編譯成web版本。
示例
下面我們來看一下React Web項目的創建過程:
第一步:安裝React web 並進行相關配置
這一步操作主要是安裝react-web 包以及相關依賴,並配置webpack 打包腳本等。
為了簡化這一步操作,我們開發了命令行工具react-web-cli 只需要執行兩行命令即可。同時命令行工具還支持啟動調試服務器、打包等功能,在後面介紹。
安裝cli 工具:
npm install react-web-cli -g
安裝配置React web 等:
react-web init <當前項目目錄>
執行完成之後,會在你項目目錄下面npm install 相關庫,並自動創建web/webpack.config.js 文件,裡面有一份寫好的配置。此時目錄結構為:
.├── README.md├── android/├── index.android.js├── index.ios.js├── ios/├── package.json└── web/ └── webpack.config.js
第二步:添加入口文件並進行相關配置
每個項目都需要有一個入口文件,通常用來引入調用其他組件並初始化項目,比如index.ios.js 表示iOS 平台上的該項目的入口文件。為了符合React Native 的文件命名規範,我們創建一個index.web.js 作為入口文件,並且需要在webpack 中指定該文件為入口文件。打開web/webpack.config.js 文件,修改config 變量:
var config = { paths: { src: path.join(ROOT_PATH, '.'), index: path.join(ROOT_PATH, 'index.web'), },};然後我們創建index.web.js 文件。這個文件其實跟index.ios.js 非常像,只是略有不同。主要區別在於:iOS 只需要AppRegistry.registerComponent('Awes', () => Awes); 即可讓Xcode 的Native 代碼接收處理你的JS 代碼,而Web 端是需要插入到DOM 節點中才可以用。因此我們需要在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 組件。這樣配置部分就已經處理完成了,執行react-web start 命令即可啟動調試服務器啦!
可以隨便修改試下,跟React Native 模擬器裡面的體驗幾乎一樣。
第三步:測試並打包Web 版本代碼
當你修改開發完,並對Web 端也測試好了,就可以打包發布了。 react-web-cli 工具打包的命令是:
react-web bundle
打包完成後,文件會存放在web/output/ 目錄下面,可以直接打開index.html (如果app 有請求操作,需要起本地服務器查看),再檢查一下就可以發布了。
這個過程中發生了什麼?
好奇的同學看到這裡可能會有一些疑問,上面命令行工具的一些命令做了什麼事情?為什麼React web 將React Native 代碼打包出一份用在Web 端的代碼? React web 安全可靠嗎,裡面都是什麼東西?
這裡簡單的介紹下React web 的實現原理和上面步驟實際做的事情。
React Web 將React Native 組件做了Web 端的實現
React 將代碼與平台環境分離,多了一層,這樣開發者可以在平台環境層面做一些處理,使得同樣一份代碼適應更多的平台環境等。
比如react-canvas 按照React 的語法書寫代碼,在平台環境層面做一些處理(將你React 代碼運行並用canvas 渲染),然後實現特定目標(在移動端提高性能)。
React Native 中,一份代碼能同時跑在iOS 和Android 上面,也是一樣的道理。 React Native 團隊在對應平台的Native app 上面做了一些處理,使其可以解析執行React 語法的代碼。
還有同構(isomorphic)的應用,服務器端使用React + Node.js 生成HTML,客戶端使用React 獲取進行客戶端相關交互和功能,也是一樣的道理。
為此, React v0.14.x 版本開始,專門分成兩個庫react 和react-dom ,其實是把對瀏覽器平台的特殊處理剝離了出來,單獨變成了react-dom 庫。
React Native 比較特殊的地方在於,組件最底層的實現是Native 的實現,所以就不支持span、div 等標籤。而動畫等,也是直接調用Native 進行界面渲染。所以不支持Web 端,但是絕大部分組件,都是可以用Web 技術進行模擬實現。動畫可以用CSS3 、基礎元素可以用同等HTML 標籤模擬、佈局以及兼容性問題可以用CSS 來處理,所以React web 只需要把React Native 的組件用Web 技術重新實現一遍,借助React 這一層,即可實現一份代碼運行在多個平台上面。
舉一個非常簡單的例子,Text 組件:
React Native 的實現是調用了很多React Native 底層的代碼實現的。
對於Web 端,輸出一行文本使用<span> 標籤即可,所以React web 的實現就直接搞一個<span> 標籤,綁一些事件什麼的就OK 了。
在UI Explorer demo 中能跑起來的React Native 組件,你都可以放心的用。
webpack 幫你切換打包目標
做出了兼容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 的保持一致即可讓代碼不替換也可以工作。
此外配合插件還可以實現另外一種引入方法,請看下面。
通過Haste 方法引入組件以提高性能
webpack 以及其他的支持CommonJS 規範的打包工具,都會把文件中require 的所有組件都打包在一起。對於React Native 來說代碼體積大小無關緊要,而在Mobile web 來說,就要稍微重要一些了。特別是如果你的項目只需要Text 組件,但由於require('react-web') 結果把所有的組件全部打包進來了,就比較傷感。
基於webpack 插件,還可以用另一種方式引入組件以解決這個問題,你可以叫它Haste 方式。使用這種方式需要加載webpack 插件haste-resolver-webpack-plugin,默認的webpack 配置已經幫你加載好了,你可以直接在組件裡面這樣用:
var Text = require('ReactText');而不是以前那樣:
var {Text} = require('react-native');這樣webpack 打包時,對於前者,只會把那一個組件內容打包進來,因此可以減小體積、提升性能。這是怎麼實現的呢?
加載了插件的webpack 打包時,會先掃描所有組件並讀取組件頭部@providesModule 的信息(比如Text 組件的信息),然後當其他文件中require 了這個組件名稱,就會自動定位到這個文件進行打包。同時還可以區分平台,即便是同一個名字,打包時會區分平台去打包對應的文件(根據index.xxx.js 的命名規則確定文件)。