React-Native 與 React-Web 的融合

關於

對於react-native在實際中的應用, facebook官方的說法是react-native是爲多平臺提供共同的開發方式,而不是說一份代碼,多處使用。 而後一份代碼可以多處使用仍是頗有意義的,我所瞭解到的已經在嘗試作這件事情的:javascript

  1. modularise-css-the-react-way
  2. react-style
  3. native-css

現階段你們都是在摸索中,且react-native 還不夠成熟,爲此我也想經過一個實際的例子提早探究一下共享代碼的可行性。css

下面是我以SampleApp作的一個簡單demo, 先奉獻上截圖:html

web 版本:java

react-native版本:node

初步想法

組件

react-native基本上是View套上Text這樣來佈局,爲了作web和native的兼容,咱們得提供繼承版本的View ,針對不一樣的平臺返回不一樣作兼容,咱們將提供:react

  1. Share.View -> View (reac-native = View , web = div)
  2. Share.P + Share.Span -> Text (Text在react-native中分爲塊級別和inline級別因此得用兩個元素來區分)

樣式

咱們知道react-native的樣式是css很小的一個子集,大概支持50種屬性,爲了作到web和native使用一樣地樣式,那麼個人想法是:android

  1. 使用css文件來編寫樣式,經過編譯的方式生產不一樣平臺須要的樣式
  2. 對於web,使用auto-prefixel處理,生產web兼容的css代碼
  3. 對於react-native,生成對應的styles.js
  4. css的寫法用OOCSS的方式

這樣作的另一個緣由是,由於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

  1. 如何區分web和native
  2. js如何對應不一樣的平臺來編譯,由於react-native使用的是本身的依賴管理packager
  3. css如何編譯爲js
  4. 代碼結構應該是怎樣的

問題一 : 如何區分web和native

react-native 裏邊會有window變量嗎?我試了一下,是有的,那window變量裏邊不可能有location,document之類的吧, 藉着這種想法,可用以下方法來區分native和web

javascriptvar isNative = !window.location;

問題二:如何對應不一樣平臺打包

對於react-native,是經過packager來打包的,具體的實現和邏輯能夠隨時查看packager的readme文檔。 那咱們怎麼將適用於native的代碼打包成web的代碼,首先想到的是browserify, webpack。 都是遵循commonJs規範,我的更喜歡前者, 用它來應該足以知足需求。

問題三: css如何編譯爲js

前面提到了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>

Share部分的處理

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;

build打包程序

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);
    }

也嘗試一下由react-native 到react-web的兼容方案

問題

  1. flexbox的寫法在react-native上面咱們會發現, 不用在父元素上聲明display: flex; 在web上必需要作這樣的聲明, 因此咱們須要讓設置了flex:*的元素的父元素display: flex;
  2. flexbox在android上是由不少bug的,因此必需要解決兼容性問題webkit-box

解決方案

1. nested 的style寫法

javascriptstyles = StyleSheet.create({
        mod: {
            flexDirection: 'row',
            item: {
                flex: 1
            }
        }
    });

這樣的寫法有些像less,咱們能夠知道元素的層級關係, 這樣我能夠遍歷這個對象,查找子元素有設置flex的,父元素加上display:flexbox

2. 經過自定義元素

javascriptvar GridSystem = require('GridSystem');
 var {
    Row,
    Grid,
    Grid6,
    Grid4
 } = GridSystem;
 <Row ...>
    <Grid/>
    <Grid/>
 </Row>

經過標籤的方式, 至關於給react-native或者react添加了一個網格系統,同時咱們能夠直接在Row上設置display:flex.

3. 遍歷查找

徹底同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很簡單,實際應用中應該會有不少地方的坑, 好比:

  1. 模塊中依賴只有native纔有的組件
  2. Native模塊的事件處理和web大不相同
  3. 現實環境中的模塊更多,更復雜,如何作模塊的管理

對於write once, run anywhere 這個觀點. 相信不一樣的人會有不一樣的見解,但不管如何,若是兼容成本不大,這樣的兼容技術方案對業務開發是有極大意義的。

ps0: 這裏僅僅作可行性方案的分析,不表明我認同或不認同這種方案。 ps1: 你們若是有更好的方案,求教,求討論。 ps2: 聽說微博關注@sysu_學家不會懷孕

相關文章
相關標籤/搜索