为了账号安全,请及时绑定邮箱和手机立即绑定

请问该如何在React Native中设计主题机制?

/ 猿问

请问该如何在React Native中设计主题机制?

米琪卡哇伊 2019-08-21 18:14:29

如何在React Native中设计主题机制


查看完整描述

3 回答

?
FFIVE

1:样式变量
web上,我们从css到scss/sass/less,然后我们才有变量可以用。然而在RN里,我们的样式本来就是JS化的,我们可以自由的使用JavaScript的变量以及其它特性来完成样式化:
// theme.js
var globalTextColor = '#000000',
module.exports = {
backgroundColor: '#FFFFFF',
title: {
size: 32,
color: globalTextColor
},
content: {
size: 16,
color: globalTextColor
}
};
// styles.js
var React = require("react-native");
var {StyleSheet} = React;
var theme = require("./theme")
module.exports = StyleSheet.create({
titleText : {
fontWeight: 'bold',
...theme.title
},
container1: {
background: theme.backgroundColor
},
container2: {
background: theme.backgroundColor
},
content: {
...theme.content
}
})
这样做,我们已经可以把一些需要经常修改的样式(如颜色、字体、尺寸等)和一些结构性的样式(譬如flex、position)等拆分开来,并且可以定义一些可以影响到多个样式的变量(如上文的globalTextColor)。对于日常的样式迭代,这已经可以满足一部分需求了,我们还可以根据自己的需要进行一些定制的预计算。而且这应当是最易理解的方式。不过这并不能实现用户切换主题的需求。
2:在组件上使用多个样式
类似ReactJS中我们使用classnames插件,ReactNative天然就支持使用数组提供多个样式的方式:
<View style={[styles.base, styles.background]} />
所以我们也可以在组件里分开引用默认样式和主题化的样式:注意这里theme返回的不是上文中的一个纯对象,而是另一个stylesheet
var React = require('React');
var styles = require('./styles');
var theme = require('../theme');
class Article extends React.Component {
render(){
return (
<View style={[style.container, theme.container]}>
<Text style={[styles.title, theme.title]}>
{this.props.title}
</Text>
<Text style={[styles.content, theme.content]}>
{this.props.content}
</Text>
</View>
);
}
};
3. 附加样式
首先,当我们实现一个通用组件的时候,应当让它可以在默认的style的基础上允许附加style(也即:允许外部进一步指定style属性):
var styleSheet = StyleSheet.create({
container: {
flex: 1
}
})
class Article extends React.Component{
render(){
var {
title, content,
style, titleStyle, contentStyle,
...others
} = this.props;
if (Array.isArray(style)){
style = [styleSheet.container, ...style];
} else {
style = [styleSheet.container, style];
}
//或者合并成这样一句: style=[styleSheet.container].concat(style);
return (
<View style={style} {...others}>
<Title style={titleStyle}>
{title}
</Title>
<Content style={contentStyle}>
{content}
</Content>
</View>
)
}
}
// Now you can use in this way:
<Article style={theme.article} title="Title" content="Foo" />
如果所有组件代码都支持附加样式,我们就比较方便通过props来传递额外的样式,这样,我们就可以引入一个新的黑科技:
4. 叠加默认属性黑魔法: 使用增强组件(EnhancedComponent)模式
如果我们可以给一个组件叠加一个默认样式,就可以让我们的许多工作简单的多(譬如上文的Title和Content,可以在Text的基础上直接叠加默认属性和样式得到)
export var Title = enhanceComponent(Text, {
style: styleSheet
});
export var Content = enhanceComponent(Text, {
style: {
fontSize: 16,
}
});
实际上,这个enhanceComponent很容易实现:
function enhanceComponent(Component, props){
var {style, ...others} = props;
return class extends React.Component {
render(){
var newProps = {...props};
for (var k : this.props){
if (/[sS]tyle$/.test(k) && newProps[k]) {
// merge style
newProps[k] = [].concat(newProps[k], this.props[k]);
} else if (k != 'children') {
// assign normal prop.
newProps[k] = this.props[k];
}
}
return (
<Component {...newProps}>
{this.props.children}
</Component>
)
}
};
}
所有名字以style或Style结尾的的属性被当做样式处理(处理类似titleStyle的情况)
5. 主题化组件
在3和4的基础上,结合ES6的一部分语法,我们应当可以以一种反向的方法来提供主题:提供一套主题化的组件。在此之前,我们的Article还需要做一些修改来适应:
var styleSheet = StyleSheet.create({
container: {
flex: 1
}
})
class Article extends React.Component{
render(){
var {
title, content, style,
createTitle, createContent
...others
} = this.props;
if (Array.isArray(style)){
style = [styleSheet.container, ...style];
} else {
style = [styleSheet.container, style];
}
//或者合并成这样一句: style=[styleSheet.container].concat(style);
return (
<View style={style} {...others}>
{createTitle ? createTitle(title) : <Title>
{title}
</Title>}
{createContent ? createContent(content) : <Title>
{content}
</Title>}
</View>
)
}
}
// Now you can use in this way:
<Article title="Title" content="Foo" createTitle={title=><Title style={theme.title}>{title}</Title>}/>
上面这个修改增加了createTitle和createContent函数,而取消了titleStyle和contentStyle(不够通用),实际上这也接近大部分内置库或第三方库所提供自定义化组件的方式(譬如为ListView提供ScrollBar)。
然后,我们可以编写这样一个themedComponents.js
// themedComponents.js
var themes = require("./themes");
import Article, {Title, Content} from './components/Article';
export var ThemedTitle = enhanceComponent(Title, {
style: themes.title
});
export var ThemedContent = enhanceComponent(Text, {
style: themes.content
});
export var ThemedArticle = enhanceComponent(Article, {
style: themes.article,
createTitle: title=>(<ThemedTitle>title</ThemedTitle>),
createContent: content=>(<ThemedContent>title</ThemedContent>),
});



查看完整回答
反对 回复 2019-08-24
?
慕的地2183247

用过 React 会知道,React 的核心概念是「DOM Representation」,在开发者和 DOM 中间构建一个中间件,然后通过高效的算法来 diff 两次 Virtual DOM 渲染的差异,然后在最小范围内更新 DOM,在大部分情况下——注意是大部分不是所有——这种做法都是足够高效的,但是对于精细的需求、动画控制等——比如在移动设备上做一个跟随 touchmove 的元素,还要各种 transition 等等——场景 React 会显得力不从心,或者很笨拙。


但是抛开这些太过复杂的需求,React 是有能力满足大部分的业务场景的。


再说 React Native,这几天不停看见媒体用「Web 开发要 XXX」一类的题目来发稿,真是吐槽无力。React Native 根本都不算 Web 开发好不好——Webview 都没了还 Web 个 bird 啊


React Native 继承了 React 在 JavaScript 的扩展语法 JSX 中直接以声明式的方式来描述 UI 结构的机制,并实现了一个 CSS 的子集,这把「DOM Representation」的概念外扩成了「UI Representation」,由于不是操作真实 UI,就可以放到非 UI 线程来进行 render——所有做客户端 UI 开发的都应该知道 UI 线程是永远的痛,无论你怎么 render,render 的多低效,这些 render 都不会直接影响 UI,而要借由 React Native 来将改变更新回 UI 线程。


由于目前没有任何示例代码,也看不到更细节的实现机制介绍,所以以下部分为猜测。如果 React Native 沿袭 React 的机制,就会同样是把两次 render 的 diff 结果算出来,然后把 diff 结果传递回主线程,在最小范围内更新 UI。


所以,核心是以下三点:

  1. 在非 UI 线程渲染 UI Representation

  2. 2. 高效的 diff 算法保证 UI update 的高效

  3. 3. 没错,由于中间件的机制,React 很有可能成为一个跨的统一 UI 解决方案,可以理解为 UI 开发的虚拟机?

声明式 UI 开发,简单快捷,必然大有作为。精细控制无力,复杂应用场景无法很好满足,必然受限。



查看完整回答
反对 回复 2019-08-24
?
宝慕林4294392

React native充分利用了Facebook的现有轮子,是一个很优秀的集成作品,并且我相信这个团队对前端的了解很深刻,否则不可能让Native code「退居二线」。


对应到前端开发,整个系统结构是这样:

JSX vs HTML

CSS-layout vs css

ECMAScript 6 vs ECMAScript 5

React native View vs DOM

无需编译,我在第一次编译了ipa装好以后,就再也没更新过app,只要更新云端的js代码,reload一下,整个界面就全变了。

多数布局代码都是JSX,所有Native组件都是标签化的,这对于前端程序员来说,降低了不少学习成本,也大大减少了代码量。不信你可以看看JSX编译后的代码。

复用React系统,也减少了一定学习和开发成本,更重要的是利用了React里面的分层和diff机制。js层传给Native层的是一个diff后的json,然后由Native将这个数据映射成真正的布局视图。

css-layout也是点睛之笔,前端可以继续用熟悉的类css方式来编写布局,通过这个工具转换成constrain布局。

系统只有js-objc的单向调用,就是把原生UI组件的方法通过javascritcore或者webview(低版本iOS)映射到js中来,整个调用过程是异步的,这样的设计令React native可以让js运行在桌面chrome中,通过websocket连接Native code和桌面chrome,极大地方便了调试。对其中的机制Bang的一篇文章写得很详细,我就不拾人牙慧了:React Native通信机制详解 laquo;  bang’s blog 。但这样设计也会带来一些问题,后面说。

点按操作也被抽象成了一组组件(TouchableXXX),这种抽象方式是我在之前做类似工作中没有想到的。facebook还列出Native为什么和web「手感」不同的原因:实时的点按反馈和取消能力。React Native 这套相应机制设计得很完善,能像Native code那样控制整个点按操作的所有过程。


Debug相当方便!修改了js以后,通过内建的nodejs watcher编译成bundle,在模拟器里面按cmd+r就可以看到效果。而且按cmd+d,可以打开一个chrome窗口,所有的js都移到了chrome里面运行,所以什么断点单步打调用栈,都不在话下。


上面的既是特点也是优点,下面说说缺点,或者应该说:「仍然遗留的问题」,在我看来,这个方案已经超越了Hybird方案。

系统仍然(不得不)依赖原生组件暴露出来的组件和方法。举两个例子,ScrollView这个组件,在Native层是有大量事件的,scrollViewWillBeginDragging, scrollViewWillEndDragging,scrollViewDidEndDragging等等,这些事件在现有的版本都没有暴露,基本上做不了组件联动效果。另外,这个版本中有大量组件是iOS only的:ActivityIndicatorIOS、DatePickerIOS、NavigatorIOS、PickerIOS、SliderIOS、SwitchIOS、TabBarIOS、AlertIOS、AppStateIOS、LinkingIOS、PushNotificationIOS、StatusBarIOS、VibrationIOS,反过来看,剩余的都是一些抽象程度极强的基本组件。这样,用户必须在不同的下写两套代码,而且所有能力仍然强烈依赖 React native 开发人员暴露的接口。

由于最外层是React,初次学习成本高,不像往常的Hybird方案,只要多学几个JS API就可以开始干活了。当然,React的确让后续开发变得简单了一些,这么一套外来的(基于iOS)、残缺不全的(css-layout)在React的包装下,的确显得不那么面目可憎了。

另外,React Native仍然很不完善。文档还不全,我基本上是看着他的示例代码完成的demo,集成到已有app的文档也是今天才出来。按照官方的说法,Android版本要到半年后才发布:Blog | React ,届时整个系统设计可能还会有很大的变化。


PS,在使用Tabbar的时候,我惊喜的发现他们居然用了iconfont方案,我现在手头的项目中也有同样的实现,不过API怎么设计一直很头疼。结果,我发现他是这么写的:

<TabBarItemIOS

name="blueTab"

icon={_ix_DEPRECATED('favorites')}

.>


在 _ix_DEPRECATED 的定义处,有一句注释: // TODO(nicklockwood): How can this fit our require system?


以上。


下面是一周前,在React native还没开源的时候,通过反解ipa的一些分析过程,有兴趣的可以看看。


------------------------简单粗暴的分割线--------------------


背景和调研手段

React Native还没开源,最近和组里兄弟「反编译」了Facebook Group(这个应用是用React Native实现的)的ipa代码,出来几百个JS文件,格式化一下,花了几天时间读了一下源码,对React Native的内部核心机制算是有了一个基本了解。


React Native的核心实现:


先简单说几点,详细的等回头更新。


  1. React Native里面没有webview,这货不是Hybrid app,里面执行JS是用的

  2. JavascriptCore。

  3. 2. 再说React Native的核心,iOS Native code提供了十来个最基本核心的类(RCTDeviceEventEmitter、RCTRenderingPerf等)、或组件(RCTView、RCTTextField、RCTTextView、RCTModalFullscreenView等),然后由React Native的JS部分,组成二十来个基本组件(Popover、Listview等),交由上层的业务方来使用(THGroupView)。

  4. 3. 就如他们在宣传时所说,他们实现了一套类似css的子集,用来解决样式问题,相当复杂和强大,靠这个才能将Native的核心组件组成JS层的基本组件再组成业务端的业务组件,应该是采用facebook/css-layout · GitHub的C语言版本实现的(在ppt中我们看到了类似flex-direction: column一类的代码,这个正是css-layout支持的语法)。

  5. 4. 在React Native中,写JS的工程师解决的是「将基本组件拼装成可用的React组件」的问题,写Native Code的工程师解决的是「提供核心组件,提供足够的扩展性、灵活性和性能」的问题。

React Native的设计考虑:


ReactJS对React Native有着直接的影响(我没在生产环境中用过React,只看过代码用过Angular,如果有误请指出)


ReactJS里面有这样的设计:

  1. ReactJS 的大工厂入口createElement返回的不是某个实体DOM对象,而只是一个数组

  2. 2. 通过源码中 ui/browser/ 目录中的代码,将这个数组转换成DOM

  3. 3. 底层的渲染核心是可以更换的

另外,Facebook自己有JSX,css-layout等开源项目,基于这些,如果要做一个用 JS来开发Native app的东西,很自然就想到了一套最有效率的搞法:


  1. 将 ui/browser 里面的代码替换成一套 Native 的桥接JS(实际上,iOS版是通过

  2. injectGenericComponentClass方法,将核心组件的方法注入到JS里面 ),就直接复用React的MVVM,自动将数据映射到Native了

  3. 2. Native code里面实现三组核心API,一组提供核心组件的API(create、update、delete),一组事件方法(ReactJS里面的EventEmitter ),一组对css进行解析(css-layout)以及返回Style的ComputedStyle(React Native里面叫meatureStyle)。

这样,用上了ReactJS本身的所有核心功能和设计思路,Native的开发也足够简单。


那,React Native是什么看


其实这东西从Native开发来说,相当于重新发明了一个浏览器渲染引擎并且套一个React的壳,从Web开发角度来说,就是把原来React的后端换成了Native code来实现,就跟Flipboard最近搞的React Canvas 一样: Flipboard · GitHubreact-canvas


React Native的优势和劣势::


优势相对Hybird app或者Webapp:

  1. 不用Webview,彻底摆脱了Webview让人不爽的交互和性能问题

  2. 2. 有较强的扩展性,这是因为Native端提供的是基本控件,JS可以自由组合使用

  3. 3. 可以直接使用Native原生的「」动画(在FB Group这个app里面,面板滑出带一点果冻弹动,面板基于某个点展开这种动画随处可见,这种动画用Native code来做小菜一碟,但是用Web来做就难上加难)。

优势相对于Native app:

  1. 可以通过更新远端JS,直接更新app,不过这快成为各家大型Native app的标配了…

劣势:

  1. 扩展性仍然远远不如web,也远远不如直接写Native code(这个不用废话解释了吧)

  2. 2. 从Native到Web,要做很多概念转换,势必造成双方都要妥协。比如web要用一套CSS的阉割版,Native通过css-layout拿到最终样式再转换成native原生的表达方式(比如iOS的Constraint\origin\Center等属性),再比如动画。另外,若Android和iOS都要做相同的封装,概念转换就更复杂了。

更新1:添加了React对React Native的影响。

更新2:基本确定其使用了 css-layout,添加了对React Native的总结

更新3: React native已经开源了: React Native,只有iOS版。我写了几个demo,简单看了看objc代码并和开源前的我们的一些结论(见后文)交叉验证。简单地从前端工程师和系统整体角度说一下React native的特点和优劣吧。

更新4: 补充了几条优势和与前端开发的对照



查看完整回答
反对 回复 2019-08-24

添加回答

回复

举报

0/150
提交
取消
意见反馈 邀请有奖 帮助中心 APP下载
官方微信