和原生端通信
通过植入原生应用和原生 UI 组件两篇文档,我们学习了 React Native 和原生组件的互相整合 。在整合的过程中,我们会需要在两个世界间互相通信。有些方法已经在其他的指南中提到了,这篇文章总结了所有可行的技术。
简介
React Native 是从 React 中得到的灵感,因此基本的信息流是类似的。在 React 中信息是单向的。我们维护着组件层次,在其中每个组件都仅依赖于它父组件和自己的状态。通过属性(props)我们将信息从上而下的从父组件传递到子元素。如果一个祖先组件需要自己子孙的状态,推荐的方法是传递一个回调函数给对应的子元素。
React Native 也运用了相同的概念。只要我们完全在框架内构建应用,就可以通过属性和回调函数来调动整个应用。但是,当我们混合 React Native 和原生组件时,我们需要一些特殊的,跨语言的机制来传递信息。
属性
属性是最简单的跨组件通信。因此我们需要一个方法从原生组件传递属性到 React Native 或者从 React Native 到原生组件。
从原生组件传递属性到 React Native
我们使用RCTRootView
将 React Natvie 视图封装到原生组件中。RCTRootView
是一个UIView
容器,承载着 React Native 应用。同时它也提供了一个联通原生端和被托管端的接口。
通过RCTRootView
的初始化函数你可以将任意属性传递给 React Native 应用。参数initialProperties
必须是NSDictionary
的一个实例。这一字典参数会在内部被转化为一个可供 JS 组件调用的 JSON 对象。
NSArray *imageList = @[@"http://foo.com/bar1.png",
@"http://foo.com/bar2.png"];
NSDictionary *props = @{@"images" : imageList};
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"ImageBrowserApp"
initialProperties:props];
import React from 'react';
import {
View,
Image
} from 'react-native';
export default class ImageBrowserApp extends React.Component {
renderImage(imgURI) {
return (
<Image source={{uri: imgURI}} />
);
}
render() {
return (
<View>
{this.props.images.map(this.renderImage)}
</View>
);
}
}
RCTRootView
同样提供了一个可读写的属性appProperties
。在appProperties
设置之后,React Native 应用将会根据新的属性重新渲染。当然,只有在新属性和之前的属性有区别时更新才会被触发。
NSArray *imageList = @[@"http://foo.com/bar3.png",
@"http://foo.com/bar4.png"];
rootView.appProperties = @{@"images" : imageList};
你可以随时更新属性,但是更新必须在主线程中进行,读取则可以在任何线程中进行。
注意: 目前有一个已知问题,如果在 bridge 还没初始化完成前就设置 appProperties,设置可能会无效,具体讨论请见 https://github.com/facebook/react-native/issues/20115
更新属性时并不能做到只更新一部分属性。我们建议你自己封装一个函数来构造属性。
注意: 目前,最顶层的 RN 组件(即 registerComponent 方法中调用的那个)的
componentWillReceiveProps
和componentWillUpdateProps
方法在属性更新后不会触发。但是,你可以通过componentWillMount
访问新的属性值。
从 React Native 传递属性到原生组件
这篇文档详细讨论了暴露原生组件属性的问题。简而言之,在你自定义的原生组件中通过RCT_CUSTOM_VIEW_PROPERTY
宏导出属性,就可以直接在 React Native 中使用,就好像它们是普通的 React Native 组件一样。
属性的限制
跨语言属性的主要缺点是不支持回调方法,因而无法实现自下而上的数据绑定。设想你有一个小的 RN 视图,当一个 JS 动作触发时你想从原生的父视图中移除它。此时你会发现根本做不到,因为信息需要自下而上进行传递。
虽然我们有跨语言回调(参阅这里),但是这些回调函数并不总能满足需求。最主要的问题是它们并不是被设计来当作属性进行传递。这一机制的本意是允许我们从 JS 触发一个原生动作,然后用 JS 处理那个动作的处理结果。
其他的跨语言交互(事件和原生模块)
如上一章所说,使用属性总会有一些限制。有时候属性并不足以满足应用逻辑,因此我们需要更灵活的解决办法。这一章描述了其他的在 React Native 中可用的通信方法。他们可以用来内部通信(在 JS 和 RN 的原生层之间),也可以用作外部通信(在 RN 和纯原生部分之间)。
React Native 允许使用跨语言的函数调用。你可以在 JS 中调用原生代码,也可以在原生代码中调用 JS。在不同端需要用不同的方法来实现相同的目的。在原生代码中我们使用事件机制来调度 JS 中的处理函数,而在 React Native 中我们直接使用原生模块导出的方法。
从原生代码调用 React Natvie 函数(事件)
事件的详细用法在这篇文章中进行了讨论。注意使用事件无法确保执行的时间,因为事件的处理函数是在单独的线程中执行。
事件很强大,它可以不需要引用直接修改 React Native 组件。但是,当你使用时要注意下面这些陷阱:
- 由于事件可以从各种地方产生,它们可能导致混乱的依赖。
- 事件共享相同的命名空间,因此你可能遇到名字冲突。冲突不会在编写代码时被探测到,因此很难排错。
- 如果你使用了同一个 React Native 组件的多个引用,然后想在事件中区分它们,name 你很可能需要在事件中同时传递一些标识(你可以使用原生视图中的
reactTag
作为标识)。
在 React Native 中嵌入原生组件时,通常的做法是用原生组件的 RCTViewManager 作为视图的代理,通过 bridge 向 JS 发送事件。这样可以集中在一处调用相关的事件。