从 react 到 react native

之前团队分享的时候讲了这个主题,点这里查看slide(含图片)。

react只是提供了一个VM的框架,react native则在react的基础上,封装了原生的组件,以及热更新的方法。因此,对一个写惯了css样式的前端工程师来说,rn充满了限制,但是跟如同一张白纸可以随意涂鸦的react相比,react native 自带各种官方轮子,在一定程度上十分便利。

本文讨论react和react native的区别,不仅仅是语法上的,其实也是纯web、以webview为基础的混合应用以及rn为基础的混合应用的一些区别。

如何安装

reactcreate-react-app

1
2
3
4
5
npm install -g create-react-app
create-react-app my-app

cd my-app
npm start

react nativereact-native-cli

1
2
3
4
5
npm install -g yarn react-native-cli
react-native init AwesomeProject

cd AwesomeProject
react-native run-android

如何调试

react:热替换,react 的浏览器插件,redux浏览器插件

react native

  1. 摇一摇
  2. 在调试页面chrome F12(支持打断点、查看log,但是不能查看app结构)
  3. 0.43以上版本能下载react-devtools,可以查看app结果和props、styles,基本交互同react浏览器插件。 调试 - React Native 中文网

样式写法

react,一般通过className引用普通css样式,行内则使用驼峰命名法

classnames

1
2
3
render() {
return <span className="menu navigation-menu">Menu</span>
}

Inline Styles | React

1
2
3
4
5
6
7
8
var divStyle = {
color: 'white',
backgroundImage: 'url(' + imgUrl + ')',
WebkitTransition: 'all', // note the capital 'W' here
msTransition: 'all' // 'ms' is the only lowercase vendor prefix
};

ReactDOM.render(<div style={divStyle}>Hello World!</div>, mountNode);

react native:无论是行内还是引用都是驼峰命名,虽然写法跟css大多一样,但不是真的css样式。官方说明

样式布局

react:传统的css布局

1
2
position
display(flex,grid,table...)

相比之下更为灵活

react native

  1. 每个组件自带relative特性,可以声明为absolute
  2. 主要使用flex进行布局

图片引用

react

1
<img className="preview-image" src={source} onClick={this.loadImage.bind(this)}/>

react native

引用网络资源

1
<Image source={{uri: `http://${GEMINI_STORE.config.cdnHostname}/international/shop/v5870/default/resource/images/icons/customize_icon_new.png`}}/>

引用本地资源

如果你在编写一个混合App(一部分UI使用React Native,而另一部分使用平台原生代码),也可以使用已经打包到App中的图片资源(以拖拽的方式放置在Xcode的asset类目中,或是放置在Android的drawable目录里)。注意此时只使用文件名,不带路径也不带后缀

1
2
<Image resizeMode='stretch'
source={{uri: 'icon'}} />

将本地资源一起打包

1
<Image source={require('./img/icon.png')} />

注意:在web中,如果我们不规定图片的尺寸,图片在加载前不占空间,之后按自身大小显示;但是在rn中,为了防止这种情况造成的视图抖动,官方规定需要提前赋予图片尺寸(除非是require方式加载图片),否则图片无法显示。

背景图片

react:css中的背景图片

1
2
3
4
5
6
.container { //以图片中心为基准进行原比例缩放,以覆盖整个背景
background-image: url("../images/bg.webp");
background-position: 50% 50%;
background-size: cover;
background-repeat: no-repeat;
}

可以调整background-positionbackground-sizebackground-repeat等值来达到背景图片的不同自适应效果和位置,非常灵活。

react native:直接用Image组件实现。

1
2
3
4
5
<Image style={styles.bg}
source={{uri: this.bgImg}}
resizeMode={'stretch'}>
<Text style={styles.text}>{'more'}</Text>
</Image>

但是由于Image组件做背景时有各种兼容问题,比如border-radius失效等等。所以我们也可以采用假背景——将图片作为子组件,但是使用absolute定位,高宽与容器相同,从而达到背景图的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<View style={styles.scrollContainer}>
<Image source={{uri: this.bgImg}}
resizeMode={'stretch'}
style={styles.bg} />
<View style={styles.contentWrap}>
{this.renderTitle()}
{dataList.map(this.renderItem.bind(this))}
</View>
</View>


const styles = StyleSheet.create({
scrollContainer: {
width: w,
height: h,
},bg: {
width: w,
height: h,
position: 'absolute',
left: 0,
borderRadius: 2,
},
//...
})

官方也有类似的暂代方案:ImageBackground,在Image组件的兼容问题未解决之前,可以使用这个代替。

动画

react

  1. css动画,处理简单动画和交互
  2. js动画,流程上较复杂的动画
  3. cavas或svg,涉及各种几何变换的超复杂动画

react native

  1. Animated,适用于各种复杂动画,性能很好
  2. LayoutAnimation,用于更新flexbox布局和元素本身高宽变化,使用范围有限但是性能更高-

点击事件

react:任何组件都可以绑定onClick事件

react native:需要用TouchableXX组件接收点击事件,Touchable家族有如下组件——
TouchableOpacity:透明度改变
TouchableHighlight:背景颜色和透明度改变
TouchableNativeFeedback:在安卓上,有涟漪效果
TouchableWithoutFeedback:不显示任何视觉反馈
由于处理点击事件和点击时的交互都会占用js资源,会导致交互样式掉帧。需要在处理事件时添加300ms时延,并在unmount时清除——

1
2
3
4
5
handleClick() {
this.clickTimeout = setTimeout(() => {
// do something
}, 300)
}

跟原生的交互

就不从原理层面上说明了,实际上应该是react native实现了跟webview差不多的解释器

react:基于webview

参考:Android:你要的WebView与 JS 交互方式 都在这里了 - CSDN博客

对于Android调用JS代码的方法有2种:

  1. 通过WebView的loadUrl()
  2. 通过WebView的evaluateJavascript()

对于JS调用Android代码的方法有3种:

  1. 通过WebView的addJavascriptInterface()进行对象映射
  2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
  3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
1
2
3
window.webviewSelected = () => { // 客户端调用js代码,在离开该webview时执行
AHandler.foo();// 通过js调用客户端代码
};

react native
参考:React Native与Android原生通信交互详情 | 江清清的技术专栏
1.RCTDeviceEventEmitter 事件

1
2
3
const { DeviceEventEmitter} = ReactNative;

DeviceEventEmitter.addListener('event_name', this.handleEvent);

2和3在安卓上的实现相似。客户端继承ReactContextBaseJavaModule定义方法,前端引用react-native的NativeModules模块,继而调用客户端定义的module
2.Callback 回调

1
2
3
4
5
6
7
8
import { NativeModules } from 'react-native';
const NativeModule = NativeModules.NativeModule;

NativeModule.foo(
JSON.stringify(data),
() => {// do something},
(err) => { // do something}
);

3.Promise 回调

1
2
3
4
5
import { NativeModules } from 'react-native';
const NativeClientInfo = NativeModules.NativeModule;
NativeModule.foo('xx').then(v => {
bar(v);
});