写点什么

React Native 探索(三):与 react-web 的融合

  • 2015-06-28
  • 本文字数:5442 字

    阅读完需:约 18 分钟

关于

对于 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 来说并不是优选。 后面也做了通过 react-native 的 css 到 web 的 css 的尝试, 那种方案在样式上可以完全基于 react-native 来写,直接兼容 web。

实现思路

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

  1. 如何区分 web 和 native
  2. js 如何对应不同的平台来编译,因为 react-native 使用的是自己的依赖管理 packager
  3. css 如何编译为 js
  4. 代码结构应该是怎样的

问题一 : 如何区分 web 和 native

react-native 里边会有 window 变量吗?我试了一下,是有的,那 window 变量里边不可能有 location,document 之类的吧, 借着这种想法,可用如下方法来区分 native 和 web

复制代码
var 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

复制代码
/**
* 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

复制代码
/**
* for web
*/
var Compo = require('./compo');
React.render(<Compo />, document.getElementById('App'));

样例组件:compo.js

复制代码
// 依赖的公共库,通过它获取兼容的组件
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 的写法还是小驼峰,是的不是横杠,暂时我们还是以这种方式处理
* 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>
<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

复制代码
var 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 打包程序

复制代码
var 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 写法

复制代码
styles = StyleSheet.create({
mod: {
flexDirection: 'row',
item: {
flex: 1
}
}
});

这样的写法有些像 less,我们可以知道元素的层级关系, 这样我可以遍历这个对象,查找子元素有设置 flex 的,父元素加上display:flexbox

2. 通过自定义元素

复制代码
var GridSystem = require('GridSystem');
var {
Row,
Grid,
Grid6,
Grid4
} = GridSystem;
<Row ...>
<Grid/>
<Grid/>
</Row>

通过标签的方式, 相当于给 react-native 或者 react 添加了一个网格系统,同时我们可以直接在 Row 上设置display:flex.

3. 遍历查找

完全同 react-native 原生的写法,直接在 web 中兼容,遍历所有有 flex 样式的节点,直接做兼容。

复制代码
componentDidMount: function() {
var $node = this.getDOMNode();
var $parent = $node.parentNode;
var $docfrag = document.createDocumentFragment();
$docfrag.appendChild($node);
{1}
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');
}
复制代码
.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: 大家如果有更好的方案,求教,求讨论。

2015-06-28 23:0915122

评论

发布
暂无评论
发现更多内容

从一场“众盟科技云滇之播”,我们发现了美食直播的商业与公益价值

脑极体

神经网络之dropout总述

Dreamer

学习

jdk 源码系列之ReentrantLock

sinsy

源码 jdk ReentrantLock 公平锁 非公平锁

JMeter100个线程竟然只模拟出1个并发

dongfanger

软件测试 Jmeter 性能测试 压力测试 测试工具

害怕重构?都怪我太晚和你介绍该如何重构,现在我来了

小Q

Java 学习 程序员 面试 重构

《TED TALKS演讲的力量》阅读笔记

Jesse Xing

读书笔记 演讲 TED

《精进:如何成为一个很厉害的人》阅读心得

Jesse Xing

读书笔记 个人成长 精进

《乌合之众——群体心理研究》读书笔记

Jesse Xing

读书笔记 心理学 乌合之众 群体心理学

接口测试的时候如何生成随机数据进行测试

测试人生路

接口测试

初级工程师职场生存要点

javaadu

程序员 职场成长 开发日志

架构师训练营 - 第七周 - 作业一

行者

Java垃圾回收GC概览

Java JVM GC

金融科技的未来

CECBC

金融

数字货币OTC交易所开发,交易所搭建方案

13530558032

从接口异常说说线上问题排查流程

QiLab

线上排障 指标监控 链路监控 日志监控 排障流程

Android网络性能监控方案

移动研发平台EMAS

android 性能 监控 移动开发 应用

算法有多重要,看字节腾讯等公司面试多重视就行了

小Q

Java 学习 架构 面试 算法

搭建一套ASP.NET Core+Nacos+Spring Cloud Gateway项目

yi念之间

一不小心画了 24 张图剖析计网应用层协议!

苹果看辽宁体育

计算机网络 计算机 协议

京东技术中台Flutter实践之路(二)

京东科技开发者

开源 中台 大前端 Web UI

CDN是什么?

德胜网络-阳

做个别人家的网页

MySQL从删库到跑路

html/css 网页设计

从智慧计算的点、线、面,读懂浪潮AI的进化轨迹

脑极体

面试前不陪女朋友也要看完这套spring源码面试题(附答案)

小Q

Java 学习 编程 架构 面试

18张图,揭开阿里巴巴开发手册强制使用SLF4J作为门面担当的秘密

沉默王二

Java slf4j 日志系统

一款区块链钱包开发需要多少钱?数字资产钱包开发搭建

13530558032

不会这些mysql得面试题,那可能说明你要回炉了

小Q

Java MySQL 数据库 学习 面试

完美!阿里P8都赞不绝口的世界独一份489页SQL优化笔记

Java~~~

Java 数据库 程序员 架构师 SQL优化

魏际刚:精准谋划我国供应链发展新方位

CECBC

供应链 物流

USDT承兑支付平台技术开发,承兑商币支付交易平台搭建

13530558032

音像协呼吁保护音乐版权:短视频平台成为侵权重灾区

石头IT视角

React Native探索(三):与 react-web 的融合_移动_陈学家_InfoQ精选文章