写点什么

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:0915228

评论

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

Alibaba最新发布的Spring Boot项目实战文档,Github标星78k

Java面试那些事儿

Java Java 面试 java程序员 Java工程师 spring-boot

画一手好的架构图是码农进阶的开始

阿里技术

经验分享 构架

7 天找个 Go 工作,Gopher 要学的条件语句,循环语句 ,第3篇

梦想橡皮擦

Python 爬虫 8月月更

XSKY星辰天合与观测云完成产品兼容性互认证 构建全业务链路的可观测性

观测云

Linux C/C++后台开发高级架构师进阶指南-剑指腾讯T9

C++后台开发

后台开发 后端开发 linux开发 Linux服务器开发 C/C++开发

洞见商业新机,云原生数据库GaussDB让企业决策更科学

华为云开发者联盟

数据库 后端 华为云

开源一夏 | 在STM32L051上使用RT-Thread (一、无线温湿度传感器 之 新建项目)

矜辰所致

开源 RT-Thread 8月月更 STM32L051

什么是知识库,为什么需要它?

Geek_da0866

满足你对 Api 的所有幻想

Liam

Postman API API接口管理 开放api API接口工具

企业实践|基于软件研运一体化DevOps平台的应用解析

云智慧AIOps社区

DevOps 自动化 敏捷开发 研发管理 代码托管

CSDN 加入星策开源社区,携手推动企业智能化转型建设

星策开源社区

企业转型 智能化 CSDN

秒验丨Android客户端集成指南

MobTech袤博科技

android Android Studio Gradle

点赞破百万!字节算法大佬亲撰30W字数据算法笔记:GitHub标星93K

小柴说Java

数据结构 算法 算法题 算法与数据结构 算法面试题

兆骑科创国内外创新创业服务平台,创业大赛,企业落地孵化

兆骑科创凤阁

EMAS Serverless到底有多便利?

hum建应用专家

云计算 Serverless emas

软件测试100天上岸1-测试就是找茬游戏

和牛

测试 8月月更

微信官方kbone,Web端同构的福音

Geek_99967b

小程序

容器化 | 一文搞定镜像构建方式选型

RadonDB

MySQL Docker Kubernetes 镜像 RadonDB

【Java】:程序流程的控制

翼同学

Java 学习 编程语言 分享 8月月更

对话张星亮,洞察本质,SaaS首先是一种商业模式

B Impact

种草 Vue3 中几个好玩的插件和配置

江南一点雨

Java Vue

参与openEuler社区不到1年,我成为了Maintainer……

openEuler

开源 成长 openEuler 开源社区 开发者说

PMP考试经验分享

索隆

项目管理 pmp 考试经验

干货!XDR产品安全检测体系如何更好的落地?

极盾科技

网络安全 安全 信息安全 数据安全 xdr

从0到1打造推荐系统工程实战

Jay Wu

推荐系统

从GitHub火到了InfoQ!共计1658页的《Java岗面试核心MCA版》,拿走不谢

收到请回复

Java 程序员 金九银十 Java面试八股文 常见面试题

兆骑科创创新创业大赛,双创活动,赛事承办,三招三引

兆骑科创凤阁

谁在构建超云?

Kent Yao

超云

如何区分透明LED显示屏种类及应用领域

Dylan

LED显示屏 led显示屏厂家

重磅升级!TDengine3.0正式发布

TDengine

数据库 tdengine 时序数据库

HUAWEI内网最新发布了一份452页网络协议手册,GitHb百万收藏

小柴说Java

Java 网络协议 java程序员 TCP/IP Java工程师

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