HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

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

评论

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

首届“兴智杯”产业赛收官,文心大模型助推产业创新

飞桨PaddlePaddle

人工智能 深度学习 飞桨 产业赋能

面试官:聊一聊Spring中Bean的作用域

做梦都在改BUG

Java spring bean

ByteHouse技术白皮书正式发布,云数仓核心技术能力首次全面解读(内附下载链接)

字节跳动数据平台

数据仓库 云原生 白皮书 数据存储 企业号 4 月 PK 榜

Spring Boot+Nacos+gRPC,一个区别于 OpenFeign 的微服务通信方案!

江南一点雨

gRPC nacos springboot

2023年最新大厂金三银四必问1200道Java面试题,面面俱到!太全了

采菊东篱下

Java

2023最新1200道JAVA面试题(超全面!超系统!超实用!)早做准备,早上岸!

架构师之道

Java 编程

MarsEdit for Mac 快速方便的博客编辑器

Rose

mac软件下载 MarsEdit下载 博客写作软件

微前端架构:将应用拆分为多个小型模块,实现模块化设计

没有用户名丶

小程序容器

高新技术产业包括哪些?拥有高新企业证书说明什么?

行云管家

高新企业 高新技术 高新

音视频处理MCP:视频版权保护

百度开发者中心

音视频 智能视频 视频版权保护

适用于所有 Mac 的温度监控、风扇控制和诊断:TG Pro

Rose

Mac硬件温度检测 TG Pro for mac 苹果软件资源站 macw软件站

音视频处理MCP:视频添加字幕

百度开发者中心

视频 音视频开发 智能视频

Serverless冷启动:如何让函数计算更快更强?

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

云原生月报丨阿里云云原生月度动态(202303)

阿里巴巴云原生

阿里云 云原生 月报

6步带你用Spring Boot开发出商城高并发秒杀系统

华为云开发者联盟

高并发 开发 华为云 华为云开发者联盟 企业号 4 月 PK 榜

大咖直播专场 | 当人工智能遇到数据库

KaiwuDB

KaiwuDB DB4AI AI4DB

NCH Switch Plus mac版:音频转换工具

Rose

全能音频格式转换 Switch Plus NCH 软件 NCH Switch Plus mac版

生物计算大模型技术在药物研发领域的应用

百度开发者中心

人工智能 文心 ERNIE 生物医药

系统天气再现bug 网友:墨迹天气赶紧上!

Geek_2d6073

新浪顶级架构师保驾护航!国内首本大型分布式架构笔记浴火新生

做梦都在改BUG

Java 架构 分布式

“阿里味”GitHub上新软件架构设计与业务架构融合手册

做梦都在改BUG

Java 架构 架构设计

Alfred 教程:如何在 Mac 之间同步 Alfred 设置?

Rose

Alfred 5 苹果效率工具 Alfred 教程 Mac 之间同步 Alfred

One Switch:Mac上一个集合一键切换系统各项功能的神奇菜单

Rose

Mac软件 苹果软件下载 One Switch Mac资源站

GPT会上网了,ChatGPT插件的原理揭秘

Apifox

人工智能 程序员 OpenAPI openai ChatGPT

火山引擎DataLeap:3小时分享,体系化讲透企业数据治理如何做?

字节跳动数据平台

活动 数据治理 论坛 数据研发 企业号 4 月 PK 榜

MySQL 索引常见问题汇总,一次性梳理

做梦都在改BUG

Java MySQL 数据库 索引

基于ArkUI框架开发-ImageKnife渲染层重构

OpenHarmony开发者

MySQL 驱动中虚引用 GC 耗时优化与源码分析

PPPHUANG

MySQL 性能优化 JVM

2023年郑州市等级保护测评机构名单汇总

行云管家

等保 郑州 等保测评机构

生成式AI已形成全球性“AI再造业务”趋势

百度开发者中心

#人工智能 文心一言 文心一格

一键快速切换工具:One Switch 1.29中文版

真大的脸盆

Mac Mac 软件 切换工具 一键切换

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