前两部分,《 如何用 React Native 创建一个iOS APP? 》,《 如何用 React Native 创建一个iOS APP (二)? 》中,我们分别讲了用 React Native 来创建 Navigation Bar,Tab Bar 等这些控件,今天在第三节,我们着重讲一下剩下的一些控件。闲话少叙,我们直入主题!
添加一个ListView
React Native 有一个叫做 ListView 的组件,可以显示滚动的行数据,基本上是 ios 项目上的一个术语表视图。首先,按照所显示的修改解构的声明以包含多个组件,然后就可以使用。
var { Image, StyleSheet, Text, View, Component, ListView, TouchableHighlight } = React;
添加以下风格样式表:
separator: { height: 1, backgroundColor: '#dddddd' }
添加以下BookList类构造函数:
constructor(props) { super(props); this.state = { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2 }) }; }
然后添加以下功能:
componentDidMount() { var books = FAKE_BOOK_DATA; this.setState({ dataSource: this.state.dataSource.cloneWithRows(books) }); }
在构造函数中,我们创建一个列表视图。数据源对象,并将其分配给数据源属性。列表视图使用的数据源是一个接口,可以确定更新了的 UI 改变所在的行。我们提供一个函数来比较双行的同一性,它可以用来决定数据列表的改变。
当组件加载/安装到用户界面视图时 componentDidMount() 便被调用。当这个函数被调用时,我们可以从我们的数据对象中设置数据源属性。 修改 render() 函数如下图所示:
render() { return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderBook.bind(this)} style={styles.listView} /> ); }
接下来添加以下书目类函数:
renderBook(book) { return ( <TouchableHighlight> <View> <View style={styles.container}> <Image source={{uri: book.volumeInfo.imageLinks.thumbnail}} style={styles.thumbnail} /> <View style={styles.rightContainer}> <Text style={styles.title}>{book.volumeInfo.title}</Text> <Text style={styles.author}>{book.volumeInfo.authors}</Text> </View> </View> <View style={styles.separator} /> </View> </TouchableHighlight> ); }
以上创建了一个在 render() 中的列表视图组件呈现。这是datasource 属性设置为数据源的值,我们前面定义的函数renderBook() 呈现 ListView 的行。
在 renderBook() 我们使用 TouchableHighlight 组件。这是一个包装器进行观点正确的响应触摸。在低压下,包装视图的透明度降低,使得衬底的颜色显示变暗或视图着色。如果你压在一个列表视图,你将看到突出的颜色,就像我们先前选择一个表视图单元格一样。添加一个空视图组件底部的行分隔符的样式。这种视图将只是一个灰色水平线,就像每一行之间的一个分区。
重新加载应用程序,你应该看到只有一个细胞的表视图。
接下来把真实的数据加载到应用程序。 从文件中删除 FAKE—BOOK—DATA 变量,添加以下数据来代替它。这是我们从数据中加载的 URL。
var REQUEST_URL = 'https://www.googleapis.com/books/v1/volumes?q=subject:fiction';
修改 destructuring 声明。
var { Image, StyleSheet, Text, View, Component, ListView, TouchableHighlight, ActivityIndicatorIOS } = React;
添加以下程序:
listView: { backgroundColor: '#F5FCFF' }, loading: { flex: 1, alignItems: 'center', justifyContent: 'center' }
构造函数修改如图所示。我们将另一个属性添加到组件的状态对象。我们通过这个来判断是否加载视图。
constructor(props) { super(props); this.state = { isLoading: true, dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2 }) }; }
修改 componetDidMount() 函数如图所示,添加如下 fetchData() 函数。fetchData() 调用Googlebooks API 并且用从响应得到的数据设置数据源属性。它也把 isLoading 设置为 true。
componentDidMount() { this.fetchData(); } fetchData() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.items), isLoading: false }); }) .done(); }
按提示修改渲染()函数,添加如下 renderLoading 函数。我们为isLoading 添加一个检查系统,如果它设置为 true,我们就要返回被renderLoadingView() 视图返回来的视图。这将是一个视图显示一个活动指标(转子)与文本“加载书籍...”。加载完成后,你就会看到一个表中的书籍列表。
render() { if (this.state.isLoading) { return this.renderLoadingView(); } return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderBook.bind(this)} style={styles.listView} /> ); } renderLoadingView() { return ( <View style={styles.loading}> <ActivityIndicatorIOS size='large'/> <Text> Loading books... </Text> </View> ); }
重新加载应用程序,应该出现如下所示:
添加 Detail View
如果你点击表中的一个细胞,细胞将突出显示,但并不会有什么反应。我们将添加一个可以显示我们选择这本书的详细信息的细节视图。 将文件添加到项目并命名为 BookDetail.js。把以下内容粘贴到文件中。
'use strict'; var React = require('react-native'); var { StyleSheet, Text, View, Component, Image } = React; var styles = StyleSheet.create({ container: { marginTop: 75, alignItems: 'center' }, image: { width: 107, height: 165, padding: 10 }, description: { padding: 10, fontSize: 15, color: '#656565' } }); class BookDetail extends Component { render() { var book = this.props.book; var imageURI = (typeof book.volumeInfo.imageLinks !== 'undefined') ? book.volumeInfo.imageLinks.thumbnail : ''; var description = (typeof book.volumeInfo.description !== 'undefined') ? book.volumeInfo.description : ''; return ( <View style={styles.container}> <Image style={styles.image} source={{uri: imageURI}} /> <Text style={styles.description}>{description}</Text> </View> ); } } module.exports = BookDetail;
我们将通过上面代码中的大多数所以不用全部浏览。我们没见过的是用道具的使用属性来提取数据。我们将通过道具属性设置传递数据到这个类。在上面,我们得到这个数据并用它来填充视图。 请注意我们在顶部边距设定一个容器。如果你不这样视图将会从屏幕顶端开始,这很可能导致一些元素被导航栏隐藏。
在 BookList.js 中添加以下程序:
var BookDetail = require('./BookDetail');
修改渲染()函数中的 TouchableHightlight 书目类如下图所示:
<TouchableHighlight onPress={() => this.showBookDetail(book)} underlayColor='#dddddd'>
当行被压缩时上述指定一个可能被命名的回调函数。把以下函数粘贴到类函数。这将推动 BookDetail 视图到导航堆栈,设置出现在导航栏中的标题栏。它通过这本书的对象对应于BookDetail类的特定行。
showBookDetail(book) { this.props.navigator.push({ title: book.volumeInfo.title, component: BookDetail, passProps: {book} }); }
重新加载应用程序,这时你应该能够看到所选书的细节。
Searching
既然我们已经完成了特色的主从复合结构的视图选项卡,我们将在搜索选项卡操作以允许用户查询 API 对书籍的选择。 打开 SearchBooks.js 并做如图修改。
use strict'; var React = require('react-native'); var SearchResults = require('./SearchResults'); var { StyleSheet, View, Text, Component, TextInput, TouchableHighlight, ActivityIndicatorIOS } = React; var styles = StyleSheet.create({ container: { marginTop: 65, padding: 10 }, searchInput: { height: 36, marginTop: 10, marginBottom: 10, fontSize: 18, borderWidth: 1, flex: 1, borderRadius: 4, padding: 5 }, button: { height: 36, backgroundColor: '#f39c12', borderRadius: 8, justifyContent: 'center', marginTop: 15 }, buttonText: { fontSize: 18, color: 'white', alignSelf: 'center' }, instructions: { fontSize: 18, alignSelf: 'center', marginBottom: 15 }, fieldLabel: { fontSize: 15, marginTop: 15 }, errorMessage: { fontSize: 15, alignSelf: 'center', marginTop: 15, color: 'red' } }); class SearchBooks extends Component { constructor(props) { super(props); this.state = { bookAuthor: '', bookTitle: '', isLoading: false, errorMessage: '' }; } render() { var spinner = this.state.isLoading ? ( <ActivityIndicatorIOS hidden='true' size='large'/> ) : ( <View/>); return ( <View style={styles.container}> <Text style={styles.instructions}>Search by book title and/or author</Text> <View> <Text style={styles.fieldLabel}>Book Title:</Text> <TextInput style={styles.searchInput} onChange={this.bookTitleInput.bind(this)}/> </View> <View> <Text style={styles.fieldLabel}>Author:</Text> <TextInput style={styles.searchInput} onChange={this.bookAuthorInput.bind(this)}/> </View> <TouchableHighlight style={styles.button} underlayColor='#f1c40f' onPress={this.searchBooks.bind(this)}> <Text style={styles.buttonText}>Search</Text> </TouchableHighlight> {spinner} <Text style={styles.errorMessage}>{this.state.errorMessage}</Text> </View> ); } bookTitleInput(event) { this.setState({ bookTitle: event.nativeEvent.text }); } bookAuthorInput(event) { this.setState({ bookAuthor: event.nativeEvent.text }); } searchBooks() { this.fetchData(); } fetchData() { this.setState({ isLoading: true }); var baseURL = 'https://www.googleapis.com/books/v1/volumes?q='; if (this.state.bookAuthor !== '') { baseURL += encodeURIComponent('inauthor:' + this.state.bookAuthor); } if (this.state.bookTitle !== '') { baseURL += (this.state.bookAuthor === '') ? encodeURIComponent('intitle:' + this.state.bookTitle) : encodeURIComponent('+intitle:' + this.state.bookTitle); } console.log('URL: >>> ' + baseURL); fetch(baseURL) .then((response) => response.json()) .then((responseData) => { this.setState({ isLoading: false}); if (responseData.items) { this.props.navigator.push({ title: 'Search Results', component: SearchResults, passProps: {books: responseData.items} }); } else { this.setState({ errorMessage: 'No results found'}); } }) .catch(error => this.setState({ isLoading: false, errorMessage: error })) .done(); } } module.exports = SearchBooks;
在上面我们在构造函数中设置一些属性:bookAuthor,bookTitle,isLoading 和errorMessage 。很快我们将看到如何使用他们。
在render()方法中,我们检查如果 isLoading 是真的,如果确实是创建一个活动指标,否则,我们就创建了一个空的观点。以后将会用的到。
然后我们创建一个用于插入查询的搜索表单。Texinput 用于输入。我们为每个 Texinput 组件指定一个回调函数时,当用户键入一些文本时将调用该组件的值。命名时,回调函数 bookTileinput() 和bookAuthorinput() 将设置 bookAuthor和bookTlie 的状态属性和用户输入数据。当用户按下搜索按钮时 searchBooks() 就被命名了。
注意 React Native 没有一个按钮组件。相反,我们使用TouchableHighlight 并把它补充在文本周围,然后其造型就像是一个按钮。搜索按钮被按下时,根据输入的数据构造一个 URL。用户可以通过搜索标题或作者来检索,或即通过标题又通过作者来检索。如果返回结果,SearchResults 将被推到导航堆栈否则将显示一条错误消息。我们还将通过 SearchResults 类响应数据。
创建一个名为 SearchResults.js 文件并把以下程序粘贴进去。
'use strict'; var React = require('react-native'); var BookDetail = require('./BookDetail'); var { StyleSheet, View, Text, Component, TouchableHighlight, Image, ListView } = React; var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, title: { fontSize: 20, marginBottom: 8 }, author: { color: '#656565' }, separator: { height: 1, backgroundColor: '#dddddd' }, listView: { backgroundColor: '#F5FCFF' }, cellContainer: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', padding: 10 }, thumbnail: { width: 53, height: 81, marginRight: 10 }, rightContainer: { flex: 1 } }); class SearchResults extends Component { constructor(props) { super(props); var dataSource = new ListView.DataSource( {rowHasChanged: (row1, row2) => row1 !== row2}); this.state = { dataSource: dataSource.cloneWithRows(this.props.books) }; } render() { return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderBook.bind(this)} style={styles.listView} /> ); } renderBook(book) { var imageURI = (typeof book.volumeInfo.imageLinks !== 'undefined') ? book.volumeInfo.imageLinks.thumbnail : ''; return ( <TouchableHighlight onPress={() => this.showBookDetail(book)} underlayColor='#dddddd'> <View> <View style={styles.cellContainer}> <Image source={{uri: imageURI}} style={styles.thumbnail} /> <View style={styles.rightContainer}> <Text style={styles.title}>{book.volumeInfo.title}</Text> <Text style={styles.author}>{book.volumeInfo.authors}</Text> </View> </View> <View style={styles.separator} /> </View> </TouchableHighlight> ); } showBookDetail(book) { this.props.navigator.push({ title: book.volumeInfo.title, component: BookDetail, passProps: {book} }); } } module.exports = SearchResults;
我们已经在以上我们使用的代码中浏览了很多,所以我不会陷入每一个细节。上面得到的数据通过道具属性传递到类并创建一个 ListView 视图的数据填充。
API 中我们注意到一件事是,当你通过作者检索时,一些结果不会记录数据但数据在作者本身。这意味着对于一些行 book,volumelnfo,imageLinks 的描述会有未定义的值。因此我们要做一个检查,表明一个空的图像视图没有是否有图像,如果不做检查应用程序在加载图片时可能会本行奔溃。
我们使用之前创建的相同的 BookDetail 组件来显示每本书的细节。我们应该把上面的检查缺失的数据打包并试图加载 BookDetail 视图与缺失的数据。打开 BookDetail.js,修改 render() 函数如图所示。它用来检查数据传入是否有一个图像和在检查传入数据之前的描绘填充视图。如果我们试图描绘一本没有图片和简介的书,各自的区域将是空白一片。你可能想把一个错误的信息强加给用户,但当它在这里时我们会不理会它。
render() { var book = this.props.book; var imageURI = (typeof book.volumeInfo.imageLinks !== 'undefined') ? book.volumeInfo.imageLinks.thumbnail : ''; var description = (typeof book.volumeInfo.description !== 'undefined') ? book.volumeInfo.description : ''; return ( <View style={styles.container}> <Image style={styles.image} source={{uri: imageURI}} /> <Text style={styles.description}>{description}</Text> </View> ); }
重新加载应用程序,你应该能够搜索一本书。
结论
虽然它仍然是一个工作正在进行中,React Native 看起来很有希望作为另一种选择构建移动应用程序。它开启了大门,对于Web 开发人员来说,让他们能够参与到移动开发的大潮;对于移动开发者,它可以提供一种方法来简化他们的开发流程。
尽管Native开发成本更高,但现阶段 Native 仍然是必须的,因为 Web的用户体验仍无法超越 Native:
Native的原生控件有更好的体验;
Native有更好的手势识别;
Native有更合适的线程模型,尽管Web Worker可以解决一部分问题,但如图像解码、文本渲染仍无法多线程渲染,这影响了 Web 的流畅性。
“学习一次,写的任何地方”。仅这一点就可能使其值得学习如何使用框架。
想要了解关于 React Native 更多的内容,你可以看下面的视频,也可以参考文档。
Introducing React Native.
Deep Dive into React Native.
React Native and Relay: Bringing Modern Web Techniques to Mobile.
以上这些仅供参考,你可以在[这里]( https://github.com/appcoda/React-Native-Demo-App)下载 Xcode 项目。
OneAPM Mobile Insight ,监控网络请求及网络错误,提升用户留存。访问OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问OneAPM 官方技术博客。