對於react-native在實際中的應用, facebook官方的說法是react-native是爲多平臺提供共同的開發方式,而不是說一份代碼,多處使用。 而後一份代碼可以多處使用仍是頗有意義的,我所瞭解到的已經在嘗試作這件事情的:javascript
現階段你們都是在摸索中,且react-native 還不夠成熟,爲此我也想經過一個實際的例子提早探究一下共享代碼的可行性。css
下面是我以SampleApp作的一個簡單demo, 先奉獻上截圖:html
web 版本:java
react-native版本:node
react-native基本上是View套上Text這樣來佈局,爲了作web和native的兼容,咱們得提供繼承版本的View ,針對不一樣的平臺返回不一樣作兼容,咱們將提供:react
咱們知道react-native的樣式是css很小的一個子集,大概支持50種屬性,爲了作到web和native使用一樣地樣式,那麼個人想法是:android
這樣作的另一個緣由是,由於css是全集,react-native是子集,全集到子集能夠經過刪減來處理,可是若是想經過子集到全集就會很麻煩(react-style就是經過react-native來生成css)。 且這樣作還有不少好處,例如咱們能夠支持react-native裏邊不支持的css寫法,例如padding: a b c d;
這種寫法很容易獲得兼容。webpack
其實這裏,不管react-native仍是react-web都支持
style={}
這樣的寫法. 上面例子中的web截圖實際上是沒有引用css的,但inline樣式對於web來講並非優選。ios
首先大概整理一下咱們須要解決的問題:git
react-native 裏邊會有window變量嗎?我試了一下,是有的,那window變量裏邊不可能有location,document之類的吧, 藉着這種想法,可用以下方法來區分native和web
javascriptvar isNative = !window.location;
對於react-native,是經過packager來打包的,具體的實現和邏輯能夠隨時查看packager的readme文檔。 那咱們怎麼將適用於native的代碼打包成web的代碼,首先想到的是browserify
, webpack
。 都是遵循commonJs規範,我的更喜歡前者, 用它來應該足以知足需求。
前面提到了native-css
, 能夠用它來幫助咱們完成打包。
web和native的代碼都寫在同一個地方,如何作區分呢? 這個問題固然最好就是不作區分,或者就像女生的衣服,指望是越少越好,但永遠不可能木有(猥瑣了:-】)。
我設想中的一個最簡模型的目錄結構,web和ios有不一樣的入口,web和ios有單獨的目錄, 組件共享, 以下:
├── compo.js // 咱們會使用到得公共組件 ├── styles.css // compo的樣式文件 ├── index.web.js // web 入口 ├── index.ios.js // ios 入口 ├── shared.js // 作兼容的共享變量文件 ├── ios // ios 目錄 └── web // web 目錄 ├── index.html // web 頁面 ├── index.web.js // 打包事後的js └── react.js // react.js依賴
好像很複雜的樣子, 其實相對於本來的SampleApp,只是多了index.web.js
, web目錄
, shared
三者。 而後style經過style.css來描述。
咱們已經整理了具體的實現思路,下面是我就會直接吐出個人實現代碼, 重點的地方都會在源碼裏邊有註釋
ios入口:index.ios.js
javascript/** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var React = require('react-native'); var Compo = require('./compo'); React.AppRegistry.registerComponent('ShareCodeProject', () => Compo);
web入口:index.web.js
javascript/** * for web */ var Compo = require('./compo'); React.render(<Compo />, document.getElementById('App'));
樣例組件:compo.js
javascript// 依賴的公共庫,經過它獲取兼容的組件 var Share = require('./shared'); // styles是style.css build事後生成的style.js var styles = require('./styles'); var React = Share.React; var { View, P, Span } = Share; var Compo = React.createClass({ render: function() { return ( <View style={styles.container}> <P style={styles.welcome}> Welcome to React Native! </P> <P style={styles.instructions}> To get started, edit index.ios.js </P> <P style={styles.instructions}> Press Cmd+R to reload,{'\n'} Cmd+Control+Z for dev menu </P> </View> ); } }); module.exports = Compo;
組件樣式: style.css
css/** * 你們可能發現了css的寫法仍是小駝峯,是的不是橫槓,暫時咱們仍是以這種方式處理 * native-css 目測不支持橫槓,(本身重寫native-css相對來講是比較容易的,徹底能夠作到css兼容到react-native的css子集) */ .container { flex: 1; justifyContent: center; alignItems: center; backgroundColor: #F5FCFF; } .welcome { fontSize: 20; textAlign: center; margin: 10; } .instructions { textAlign: center; color: #333333; marginBottom: 5; }
index.html
html<!DOCTYPE html> <html> <head> <title>Hello React!</title> <script src="./react.js"></script> <!-- No need for JSXTransformer! --> </head> <body> <div id="App"></div> <script src="./index.web.js"></script> </body> </html>
shared.js
javascriptvar Share = {}; var React = require('react-native'); var isNative = !window.location; /** * 判斷是web的時候,從新賦值React */ if (window.React) { React = window.React; } Share.React = React; /** * 作底層的兼容, 固然這裏只是作了一個最簡demo,具體實現的時候可能會對props作各類兼容處理 */ if (!isNative) { Share.View = React.createClass({ render: function() { return <div {...this.props}/> } }); Share.P = React.createClass({ render: function() { return <p {...this.props}/> } }); Share.Span = React.createClass({ render: function() { return <span {...this.props}/> } }); } else { // alert('isNative') Share.View = React.View; Share.P = React.Text; Share.Span = React.Text; Share.Text = React.Text; } module.exports = Share;
javascriptvar fs = require('fs'); var nativeCSS = require('native-css'), var cssObject = nativeCSS.convert('./styles.css'); toStyleJs(cssObject, './styles.js'); buildWebReact(); /** * native-css獲取到得是一個對象,須要將cssObject轉化爲js代碼 */ function toStyleJs(cssObject, name) { console.log('build styles.js \n'); var tab = ' '; var str = ''; str += '/* build header */\n'; str += 'var styles = {\n'; for(var key in cssObject) { var rules = cssObject[key]; str += tab + key + ': {\n'; for(var attr in rules) { var rule = rules[attr]; str += tab + tab + attr + ': ' + format(rule) + ',\n' } str += tab + '},\n' } str += '};\n' str += 'module.exports = styles;\n' fs.writeFile(name, str) function format(rule) { if (!isNaN(rule - 0)) { return rule; } return '"' + rule + '"'; } } /** * 構造web使用的react */ function buildWebReact() { console.log('build web bundle'); var browserify = require('browserify'); var b = browserify(); b.add('./index.web.js'); // 添加es6支持 b.transform('reactify', {'es6': true}); // ignore掉react-native b.ignore('react-native') var wstream = fs.createWriteStream('./web/index.web.js'); b.bundle().pipe(wstream); }
display: flex;
在web上必需要作這樣的聲明, 因此咱們須要讓設置了flex:*
的元素的父元素display: flex;
。webkit-box
javascriptstyles = StyleSheet.create({ mod: { flexDirection: 'row', item: { flex: 1 } } });
這樣的寫法有些像less,咱們能夠知道元素的層級關係, 這樣我能夠遍歷這個對象,查找子元素有設置flex的,父元素加上display:flexbox
。
javascriptvar GridSystem = require('GridSystem'); var { Row, Grid, Grid6, Grid4 } = GridSystem; <Row ...> <Grid/> <Grid/> </Row>
經過標籤的方式, 至關於給react-native或者react添加了一個網格系統,同時咱們能夠直接在Row上設置display:flex
.
徹底同react-native原生的寫法,直接在web中兼容,遍歷全部有flex樣式的節點,直接作兼容。
javascriptcomponentDidMount: function() { var $node = this.getDOMNode(); var $parent = $node.parentNode; var $docfrag = document.createDocumentFragment(); $docfrag.appendChild($node); var treeWalker = document.createTreeWalker($node, NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } }, false); while(treeWalker.nextNode()) { var node = treeWalker.currentNode; if (node.style.flex) { flexChild(node); flexParent(node.parentNode); } }; $parent.appendChild($docfrag); } function flexChild(node) { if (node.__flexchild__) { return; } node.__flexchild__ = true; var flexGrow = node.style.flexGrow; addStyle(node, ` -webkit-box-flex: ${flexGrow}; -webkit-flex: ${flexGrow}; -ms-flex: ${flexGrow}; flex: ${flexGrow}; `); node.classList.add('mui-flex-cell'); } function flexParent(node) { if (node.__flexparentd__) { return; } node.__flexparentd__ = true; node.classList.add('mui-flex'); }
css.mui-flex { display: -webkit-box!important; display: -webkit-flex!important; display: -ms-flexbox!important; display: flex!important; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; -webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; } .mui-flex-cell { -webkit-flex-basis: 0; -ms-flex-preferred-size: 0; flex-basis: 0; max-width: 100%; display: block; position: relative; }
這個demo很簡單,實際應用中應該會有不少地方的坑, 好比:
對於write once, run anywhere
這個觀點. 相信不一樣的人會有不一樣的見解,但不管如何,若是兼容成本不大,這樣的兼容技術方案對業務開發是有極大意義的。
ps0: 這裏僅僅作可行性方案的分析,不表明我認同或不認同這種方案。 ps1: 你們若是有更好的方案,求教,求討論。 ps2: 聽說微博關注@sysu_學家不會懷孕