React-Native 与 React-Web 的融合

关于

对于react-native在实际中的应用, facebook官方的说法是react-native是为多平台提供共同的开发方式,而不是说一份代码,多处使用。 然后一份代码能够多处使用还是很有意义的,我所了解到的已经在尝试做这件事情的:

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

现阶段大家都是在摸索中,且react-native 还不够成熟,为此我也想通过一个实际的例子提前探究一下共享代码的可行性。

下面是我以SampleApp做的一个简单demo, 先奉献上截图:

web 版本:

react-native版本:

初步想法

组件

react-native基本上是View套上Text这样来布局,为了做web和native的兼容,我们得提供继承版本的View ,针对不同的平台返回不同做兼容,我们将提供:

  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使用同样地样式,那么我的想法是:

  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; 这种写法很容易得到兼容。

其实这里,无论react-native还是react-web都支持style={}这样的写法. 上面例子中的web截图其实是没有引用css的,但inline样式对于web来说并不是优选。

实现思路

首先大概整理一下我们需要解决的问题:

  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_学家不会怀孕

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom