如何用 React Native 建立一個iOS APP?(三)

前兩部分,《如何用 React Native 建立一個iOS APP?》,《如何用 React Native 建立一個iOS APP (二)?》中,咱們分別講了用 React Native 來建立 Navigation Bar,Tab Bar 等這些控件,今天在第三節,咱們着重講一下剩下的一些控件。閒話少敘,咱們直入主題!html

#####添加一個ListViewreact

React Native 有一個叫作 ListView 的組件,能夠顯示滾動的行數據,基本上是 ios 項目上的一個術語表視圖。 首先,按照所顯示的修改解構的聲明以包含多個組件,而後就可使用。ios

var {
    Image,
    StyleSheet,
    Text,
    View,
    Component,
    ListView,
    TouchableHighlight
   } = React;

添加如下風格樣式表:git

separator: {
       height: 1,
       backgroundColor: '#dddddd'
   }

添加如下BookList類構造函數:github

constructor(props) {
       super(props);
       this.state = {
           dataSource: new ListView.DataSource({
               rowHasChanged: (row1, row2) => row1 !== row2
           })
       };
   }

而後添加如下功能:json

componentDidMount() {
    var books = FAKE_BOOK_DATA;
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(books)
    });
   }

在構造函數中,咱們建立一個列表視圖。數據源對象,並將其分配給數據源屬性。列表視圖使用的數據源是一個接口,能夠肯定更新了的 UI 改變所在的行。咱們提供一個函數來比較雙行的同一性,它能夠用來決定數據列表的改變。react-native

當組件加載/安裝到用戶界面視圖時 componentDidMount() 便被調用。當這個函數被調用時,咱們能夠從咱們的數據對象中設置數據源屬性。 修改 render() 函數以下圖所示:api

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:

  1. Native的原生控件有更好的體驗;

  2. Native有更好的手勢識別;

  3. Native有更合適的線程模型,儘管Web Worker能夠解決一部分問題,但如圖像解碼、文本渲染仍沒法多線程渲染,這影響了 Web 的流暢性。

「學習一次,寫的任何地方」。僅這一點就可能使其值得學習如何使用框架。

想要了解關於 React Native 更多的內容,你能夠看下面的視頻,也能夠參考文檔。

  1. Introducing React Native.

  2. Deep Dive into React Native.

  3. React Native and Relay: Bringing Modern Web Techniques to Mobile.

以上這些僅供參考,你能夠在[這裏](https://github.com/appcoda/React-Native-Demo-App)下載 Xcode 項目。

OneAPM Mobile Insight ,監控網絡請求及網絡錯誤,提高用戶留存。訪問 OneAPM 官方網站感覺更多應用性能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客 本文轉自 OneAPM 官方博客

相關文章
相關標籤/搜索