如何用 React Native 創建一個iOS APP?(三)
前兩部分,《 如何用 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 更多的內容,你可以看下面的視頻,也可以參考文檔。
以上這些僅供參考,你可以在[這里]( https://github.com/appcoda/React-Native-Demo-App)下載 Xcode 項目。
OneAPM Mobile Insight ,監控網絡請求及網絡錯誤,提升用戶留存。訪問OneAPM 官方網站感受更多應用性能優化體驗,想閱讀更多技術文章,請訪問OneAPM 官方技術博客。