React Native for Android 实践 — 实现知乎日报客户端
React Native for Android 的发布,对一个 Android 开发者来说还是有相当的吸引力的。通过前面这篇博客:React Native for Android 入门老虎好不容易入了门了,然后想找一个简单的项目,来练练手。一方面来熟悉一下 RN(React Native,后面都做此简写),另一方面来验证使用 RN 来实现一个相对完整的项目的可行性。 平时用的最多的客户端之一:知乎日报,这个 APP 相对简单,而且也找到了有人分析的知乎日报 API 分析。就选择它了:实现一个 React Native 版的知乎日报客户端,目标是尽量实现官方客户端一致的效果。 这篇文章主要讲使用 RN 来实现知乎日报客户端的可行性和实现方法。整个项目开源在 GitHub 上:ZhiHuDaily-React-Native,欢迎 Star 和 PR。 基本概念这里以我的个人理解,快速过一下 React Native 中一些基本概念。如果和官方的理解有些偏差,还请指出。 1. 组件React Native 主要是通过 Virtual Dom 来实现显示页面或者页面中的模块。可以通过 var MyCustomComponent = React.createClass({ ... }); // 然后就可以这样使用 <MyCustomComponent /> 到底什么是一个组件?我的理解就是页面上的一个逻辑单元。组件可以小到一个按钮,也可以大到整个页面,组件嵌套组合,就成了各种复杂的界面了。 2. 组件生命周期类似于 Android 中的一个 View,它也有自己的生命周期,有自己的状态。React 组件的数据保存在自己内部的
具体要写自己的页面的话,要从哪里入手呢?我们这里就要来看一下 var MyCustomComponent = React.createClass({ // 这里返回一个对象,设置组件的初始化状态, // 后面就可以通过 this.state 来获得这个对象 getInitialState: function() { return { key1: data1,key2: data2,... }; },// 这里一般做一些和界面显示无关的初始化操作 componentWillMount: function() { },// 这里一般做加载数据的操作 componentDidMount: function() { },// 这是最重要的函数,用来绘制界面, // 所有的自定义组件,这个函数是必须提供的 render: function() { return( <View> ... </View> ); },}); 一个自定义组件基本上就是上面那样定义了。只有 3. 组件的数据绘制界面部分,一般情况下会根据组件的状态 render: function() { return( <Text>{this.state.key1}</Text> ); } 这里就是直接把状态中的 另外,React 组件中最重要的一个概念就是 如果在代码中直接修改 this.setState({key1: 'Hello world!'}); 界面上的 组件中还有一种数据:属性(Property),这种数据可以通过 <View style={{flex: 1}}> 这里的 那么属性(props)和状态(state)两种数据有什么区别呢?一般属性表示静态的数据,组件创建后,就基本不变的内容,状态是动态数据。 4. React Native 布局关于 React Native 的布局,实用的是 FlexBox 实现,类似网页的 CSS 布局方法,具体可以参考官方推荐的A Complete Guide to Flexbox和官方文档Flexbox。关于布局说起原理比较简单,但是要很灵活的写出你想要的样式,还是需要慢慢积累经验。 另外,值得一提的是,React Native 中的样式长度单位,是逻辑单位,概念和 Android 中的 以上就是 React Native 的基本逻辑,有了这些概念,我们就可以开始写 APP 了。 APP 开发实践我们要实现的知乎日报的 APP 的主页面是一个文章列表,左边可以滑动出来抽屉,账号信息和显示主题列表。选择主题列表,可以在列表页更新对应主题的文章列表。点击文章列表进入文章详情。还有评论,点赞,登录等功能初期并不计划做。 1. 抽屉的实现庆幸的是,官方提供了DrawerLayoutAndroid组件,这个组件其实就是对 Android 中的 render: function() { ... return ( <DrawerLayoutAndroid ref={(drawer) => { this.drawer = drawer; }} drawerWidth={Dimensions.get('window').width - DRAWER_WIDTH_LEFT} keyboardDismissMode="on-drag" drawerPosition={DrawerLayoutAndroid.positions.Left} renderNavigationView={this._renderNavigationView}> <View style={styles.container}> ... {content} </View> </DrawerLayoutAndroid> ); } 其中 2. 主页文章列表文章列表在 Android 可以用 getInitialState: function() { var ds = new ListView.DataSource({rowHasChanged: (r1,r2) => r1 !== r2}); return { dataSource: ds.cloneWithRows(['row 1','row 2']),}; },render: function() { return ( <ListView dataSource={this.state.dataSource} renderRow={(rowData) => <Text>{rowData}</Text>} /> ); }, 这是一个最简的 ListView 使用的例子。其实,React Native 提供的 ListView 比原生还要强大一些,提供了列表的 因为知乎日报的文章列表是按照日期分 Section 的。具体的使用方法在官方的例子UIExplorer中有例子。问项目中可以参考这个文件:ListScreen.js。 3. 详情页的实现知乎日报的文章详情页是使用一个 public class ReactWebViewManager extends SimpleViewManager<WebView> { public static final String REACT_CLASS = "RCTWebView"; @UIProp(UIProp.Type.STRING) public static final String PROP_URL = "url"; @UIProp(UIProp.Type.STRING) public static final String PROP_HTML = "html"; @UIProp(UIProp.Type.STRING) public static final String PROP_CSS = "css"; @Override public String getName() { return REACT_CLASS; } @Override protected WebView createViewInstance(ThemedReactContext reactContext) { return new WebView(reactContext); } @Override public void updateView(final WebView webView,CatalystStylesDiffMap props) { super.updateView(webView,props); if (props.hasKey(PROP_URL)) { webView.loadUrl(props.getString(PROP_URL)); } if (props.hasKey(PROP_HTML)) { String html = props.getString(PROP_HTML); if (props.hasKey(PROP_CSS)) { String css = props.getString(PROP_CSS); html = "<link rel="stylesheet" type="text/css" href="" + css + "" />" + html; } webView.loadData(html,"text/html; charset=utf-8","UTF-8"); } } } 这里我导出了一个简单的 WebView,并暴露了 在 JS 端,需要做对应的封装: class ObservableWebView extends React.Component { ... render() { return <RCTWebView {...this.props} onChange={this._onChange} />; } } ObservableWebView.propTypes = { url: PropTypes.string,html: PropTypes.string,css: PropTypes.string,onScrollChange: PropTypes.func,}; var RCTWebView = requireNativeComponent('RCTWebView',ObservableWebView,{ nativeOnly: {onChange: true} }); module.exports = ObservableWebView; 然后就可以在 React 中使用了,如下: var MyWebView = require('./WebView'); render: function() { return ( <View style={styles.container}> <MyWebView style={styles.content} html={this.state.detail.body} css={this.state.detail.css[0]}/> </View> ); } 这样就能直接显示了网页内容,挺出乎我意料的简单。 还有一个细节,官方客户端,随着 WebView 的滑动,头部的 Image 也跟着往上收起来。这里我们就要监听 WebView 的滑动事件,然后来设置头部的 Image 的跟随移动。还好,官方文档也提供了一个可以方便从 Native 往 React 传递事件的方法:Events。跟着文档来,实现了一个 // ObservableWebView.java public class ObservableWebView extends WebView { ... @Override protected void onScrollChanged(final int l,final int t,final int oldl,final int oldt) { super.onScrollChanged(l,t,oldl,oldt); WritableMap event = Arguments.createMap(); event.putInt("ScrollX",l); event.putInt("ScrollY",t); ReactContext reactContext = (ReactContext)getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( getId(),"topChange",event); } } 这里在 class ObservableWebView extends React.Component { constructor() { super(); this._onChange = this._onChange.bind(this); } _onChange(event: Event) { if (!this.props.onScrollChange) { return; } this.props.onScrollChange(event.nativeEvent.ScrollY); } render() { return <RCTWebView {...this.props} onChange={this._onChange} />; } } 详情可以参考:WebView.js。 这时,我们就可以在 React 组件中的 onWebViewScroll: function(event) { // 这里移动头部的 Image },render: function() { return ( <View style={styles.container}> <MyWebView ... onScrollChange={this.onWebViewScroll}/> <Image ref={REF_HEADER} source={{uri: this.state.detail.image}} style={styles.headerImage} /> {toolbar} </View> ); }, 这里的写起来也很简单。关键看一下 React Native 提供了Direct Manipulation,也就是直接操作组件,这种方式不会触发重绘,效率会高很多。 onWebViewScroll: function(event) { // 像素转为 React 中的大小单元 var scrollY = -event / PIXELRATIO; var nativeProps = precomputeStyle({transform: [{translateY: scrollY}]}); // 直接操作组件的属性 this.refs[REF_HEADER].setNativeProps(nativeProps); }, 到这里,实现这个 React Native 版的知乎日报客户端所涉及的技术点,基本都讲完了。还有很多细节请参考源码:ZhiHuDaily-React-Native,欢迎一起交流,和发 pull request 来一起完善这个项目。 总结这篇文章几百字就写完了,看起来实现这个客户端并不复杂。其实,这里有远超过我想象的坑,后面我应该还会写一篇文章,来总结这个项目中遇到的坑。总体来说,React Native for Android 作为初期的版本,实现一个简单 APP 已经可行。但是它并不完善,如果想用在实际项目中,还需要慎重考量。 最后,大家可以关注这个项目:ZhiHuDaily-React-Native。希望能对开始关注 React Native 的同学有些帮助。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |