React Native (一) 入門實踐

上週末開始接觸react native,版本爲0.37,邊學邊看寫了個demo,語法使用es6/7和jsx。準備分享一下這個過程。之前沒有native開發和react的使用經驗,不對之處煩請指出。筆者認爲從Vue過度到React應該是非常容易的,畢竟都是面向組件的,一通百通,只是每個框架的特性可能有所不同。本文的目的在於希望對在web開發有一定基礎,又想涉及app開發的同學有幫助,共同進步。

 

一、環境安裝

首先是開發環境安裝,我是在win7/8 64位環境下安裝的安卓環境。模擬器用的是android studio自帶模擬器(android emulator),安卓API 24(7.0),因爲我沒有mac -.-。文中組件的使用也會以安卓的爲主。具體的安裝流程官網或中文網都有講解,我也是按照那個流程走的。

這裏說下安裝和運行過程中經常出現但教程又沒有提到的問題:

1.gradle-2.4-all.zip無法安裝引發的錯誤,可以參考這裏

2.環境安裝完成,app成功構建,但是更改代碼react packager沒有監聽到文件變動並重新打包bundle,導致reload後無法更新app的問題。只有重新構建app並重啓react packager才能解決。但這樣太麻煩了,解決辦法可以參考這裏。我按照這個方式修改以後,環境表現就正常了。

3.安卓模擬器經常崩潰。

第一個問題與是否使用科學上網工具以及安卓環境配置和SDK有關係。第二個問題在win7環境下遇到過,修改數值後正常了,win8正常。第三個問題無解。

rn在windows下的安卓開發環境的坑還是比較多的。

4.這是一個比較完整的參考

二、demo app的功能和項目結構

首先看看這個demo的流程:

流程描述:

第一個場景是登錄界面,輸入賬號和密碼以後登錄。登錄後進入第二個場景,這是一個tabview容器,包含了三個tab:搜圖書列表、電影排行列表和一個關於界面。列表視圖支持上啦加載更多和下拉刷新,返回頂部以及跳轉列表項的詳情。關於界面裏放了個靜態的 swiper、說明以及一個登出的按鈕,會返回到登錄頁。

 

 

 

說明:1.登錄頁做的是假的,後期可以加上session驗證。2.搜圖書和電影Top250排行都直接調用的豆瓣開放接口

 

項目結構如下:

目錄描述:

common -  公用工具(公用方法以及豆瓣接口Model的封裝)

components - 全局公用組件(和業務無關)

images - 公用組件和業務視圖組件的圖片

views - 業務視圖組件和業務公用組件(按照場景分文件夾)

views/MainView - 根組件(渲染了一個Navigator來控制整個App的場景跳轉)

index.android.js - 入口文件(註冊根組件,runApplication的前奏)

package.json - rn和第三方相關依賴

 

下面開始對每個場景進行拆分介紹。

 

三、入口文件和根組件

index.android.js這個文件按照官方文檔的寫法就可以,需要注意的是registerComponent方法傳入的項目名一定要和命令行工具中執行react-native init xxx初始化命令時候輸入的項目名稱保持一致。

 

import React, {Component} from 'react';
import {AppRegistry} from 'react-native';

import MainView from './views/MainView';

AppRegistry.registerComponent('rndemo', () => MainView);

 

MainView.js作爲根組件主要渲染了一個導航器來控制App場景跳轉,所有業務視圖組件都在它的控制下。

import React, {Component} from 'react';
import {View, Navigator} from 'react-native';

import LoginView from './login/LoginView';

export default class MainView extends Component {
    render() {
        return (
            <Navigator
                initialRoute={{name: 'LoginView', component: LoginView}}
                configureScene={(route) => Navigator.SceneConfigs.PushFromRight}
                renderScene={(route, navigator) => <route.component {...route.params} navigator={navigator} />}
            />
        );
    }
}

這個導航器類似於路由棧,是一種棧式結構,出棧和入棧的配合就能實現最基本的界面跳轉,也提供有更高級的方法。

initialRoute要求指定默認顯示的組件,這裏import了登錄視圖組件,並指定爲導航器的默認組件。confgureScene是導航器手勢控制和動畫配置,renderScene會渲染當前導航棧中被壓入或者指定跳轉的組件。

需要注意的是 <route.component {...route.params} navigator={navigator} /> 這裏, {...route.params} 這是一個es6延展操作語法,能夠進行不定參數接收、數組和對象的拆分等。能夠進行批量賦值,可以將params對象的所有key->value結構轉換成不同的prop。

比如:route.params值爲 {a: 123,b: (a) => a + 1,c: true} ,最後的結果相當於 <route.component a={123} b={(a) => a + 1} c={true} navigator={navigator} /> 

 

雖然是語法範疇,但經常會用到,還是介紹一下。route.params可以用來給要跳轉到的視圖組件傳遞參數。如果數據過爲複雜還是需要專門的數據流(狀態)管理工具。這個demo因爲數據簡單,props傳參已足夠使用,也就沒有用上redux,後面的各類組件也不會用到。有興趣的話可以到別處瞭解一下redux。我在前段時間的vue.js組件化開發實踐中對flux思路下的vuex和redux有一定介紹。vuex是專門針對vue的一個定製版,泛用性沒有redux高,但和vue組件契合度高。redux這方面正好相反,但思想基本相同。

 

四、登錄

登陸頁面很簡單,主要是一些佈局:

 

 jsx結構:

<ScrollView>
    <Image style={sty.back} source={require('../../images/login/banner_2.png')}/>
    <View style={[sty.back, sty.mask]}></View>
    <View style={sty.loginView}>
        <View style={sty.bannerWrap}>
            <Image style={sty.bg} source={require('../../images/login/banner_1.png')}/>
            <View style={sty.logoTextWrap}>
                <Animated.Text style={[sty.logoText, {opacity: this.state.fadeAnim}]}>Demo</Animated.Text>
            </View>
            <View style={sty.copyRightWrap}>
                <Text style={sty.copyRightText}>©2016</Text>
            </View>
        </View>
        <View style={sty.inputWrap}>
            <Text style={sty.inputTitle}>SIGN IN</Text>
            <TextInput 
                {/* ... 賬號 */}/>
            <TextInput 
                {/* ... 密碼 */}/>
            <Animated.View style={{opacity: this.state.fadeAnim}}>
                <TouchableOpacity
                    style={sty.loginBtn}
                    onPress={this.login.bind(this)}
                >
                    <Text style={sty.loginBtnText}>登錄</Text>
                </TouchableOpacity>
            </Animated.View>
        </View>
        <View style={sty.footer}>
            <Image style={sty.footerLogo} source={require('../../images/login/react_native_logo.png')} />
            <Text style={sty.footerText}>Powered by React-Native</Text>
        </View>
    </View>
</ScrollView>
const sty = StyleSheet.create({
    back: {
        position: 'absolute',
        top: 0,
        bottom: 0,
        left: 0,
        right: 0,
        height: height - 24
    },
    mask: {
        backgroundColor: '#2B75DE',
        opacity: 0.2
    },
    loginView: {
        height: height - 24
    },
    bannerWrap: {

    },
    bg: {
        width: 375 * refRat,
        height: 235 * refRat
    },
    logoTextWrap: {
        position: 'absolute',
        bottom: 90
    },
    logoText: {
        width: 375 * refRat,
        textAlign: 'center',
        fontSize: 40,
        color: '#fff'
    },
    copyRightWrap: {
        position: 'absolute',
        bottom: 0,
        paddingTop: 6,
        paddingBottom: 6,
        width: 375 * refRat,
        borderTopWidth: onePx,
        borderTopColor: '#fff',
        opacity: 0.5
    },
    copyRightText: {
        textAlign: 'center',
        fontSize: 12,
        color: '#fff'
    },
    inputWrap: {
        alignItems: 'center'
    },
    inputTitle: {
        paddingTop: 5,
        paddingBottom: 20,
        textAlign: 'center',
        fontSize: 12,
        color: '#fff',
        opacity: 0.5
    },
    input: {
        width: 230 * refRat,
        textAlign: 'center',
        color: '#fff',
        borderBottomWidth: onePx,
        borderBottomColor: '#fff',
    },
    loginBtn: {
        marginTop: 30,
        padding: 10,
        width: 90 * refRat,
        alignItems: 'center',
        borderRadius: 20,
        backgroundColor: '#FF8161'
    },
    loginBtnText: {
        color: '#fff'
    },
    footer: {
        position: 'absolute',
        bottom: 0,
        width: 375 * refRat,
        alignItems: 'center'
    },
    footerLogo: {
        width: 20 * refRat,
        height: 20 * refRat
    },
    footerText: {
        marginTop: 5,
        textAlign: 'center',
        fontSize: 12,
        color: '#fff'
    }
});

banner的文字和登錄按鈕有一個簡單的淡入效果,這是通過Animated實現的,官方文檔有更多的介紹。

登錄以後跳轉到TabView,使用的事導航器的restTo方法:

navigator.resetTo({
       name: 'TabView',
       component: TabView
});

 它會跳轉到新的場景,並重置整個路由棧。也就是說不能再直接通過pop出棧返回到登錄頁,必須指定跳轉纔行。這樣可以避免在TabView進行了右滑等手勢操作,無意間又回到了登錄界面。

 

五、TabView

這是在github上找的一個第三方組件:react-native-tab-view,需要0.36以上版本支持。提供了頂部和底部tabbar,以及左右滑動來切換tab的容器,也支持點擊tabbar按鈕切換tab。tabbar點擊自帶有水波擴散你動畫,類似於css3 scale實現的那種水波效果。

 

 

// 導入rn相關
// ...
// 導入需要的組件 // ...
export default class TabView extends Component { constructor (props){ super(props); const {navigator} = props; this.state = { index: 0, routes: [ {key: '1',title: '搜圖書'}, {key: '2',title: '電影排行'}, {key: '3',title: '關於'} ], routeMap: { 1: <BookListView navigator={navigator} />, 2: <MovieListView navigator={navigator} />, 3: <AboutView navigator={navigator} /> } }; } handleChangeTab (index){ this.setState({index}); } renderFooter (props){ return <TabBar tabStyle={{backgroundColor: '#6C6A6A'}} {...props} />; } renderScene ({route, navigator}){ return this.state.routeMap[route.key] || null; } render (){ return ( <TabViewAnimated style={sty.container} navigationState={this.state} renderScene={this.renderScene.bind(this)} renderFooter={this.renderFooter} onRequestChangeTab={this.handleChangeTab.bind(this)} /> ); } } const sty = StyleSheet.create({
// 樣式 // ... });

導入搜圖書、電影排行和關於這三個視圖組件,簡單配置便可運行。具體可查看github上的介紹和示例代碼。

 

六、搜圖書

 

 

從最直觀的列表開始介紹。列表採用ListView實現,ListView繼承自ScrollView,擴展了一些功能,列表需要一個DataSource管理數據,我們取回的數據都必須用它改造一遍:

 

// 各種導入
// ...

const ds = new ListView.DataSource({ // 更新策略(列表某項是否需要重新渲染):新舊值不相等 rowHasChanged: (v1, v2) => v1 !== v2 });

rowsHadChanged定義了怎麼判斷列表項是否有改變。虛擬DOMdiff算法是react的很巧妙的地方。新render出的虛擬DOM視圖樹會和當前的樹進行比較,和得到不同地方,以類似於打補丁的方式打到當前的樹上,也稱之爲'和解'過程。這就意味着只有真正改變了的地方纔需要重新繪製,在有很多元素的時候能大幅提升渲染性能。

 

下面是BookListView類的結構:

 

// ...

export default class BookListView extends Component { constructor (props){ super(props); this.state = { showLoading: true, scrollY: 0, q: '紅樓夢', start: 0, noMore : false, isLoading: false, data: [], dataSource: ds.cloneWithRows([]) }; } async setListData (data){ await this.setState({ data: data, dataSource: ds.cloneWithRows(data) }); } async componentWillMount (){ let data = await this.getListData(); await this.setListData(data); this.setState({showLoading: false}); } async getListData (){ this.setState({isLoading: true}); let {q, start} = this.state; let data = await searchBook({q, start, count}); await this.setState({ start: start + count, isLoading: false }); return data.books; } async listRefresh (){ await this.setState({start: 0,noMore : false}); let data = await this.getListData(); this.setListData(data); if (!data.length) ToastAndroid.show("沒有數據", ToastAndroid.SHORT); } renderFooter (){ if (this.state.isLoading || this.state.data.length < 1) return null; if (this.state.data.length < count) return <ListGetMore />; return <ListGetMore isLoadAll={true}/>; } async onEnd (){ if (this.state.isLoading || this.state.noMore) return; let data = await this.getListData(); if (data.length < count) this.setState({noMore: true}); let newList = this.state.data.concat(data); this.setListData(newList); } search (){ let {q} = this.state; if (!q) return ToastAndroid.show("請輸入書名", ToastAndroid.SHORT); this.listRefresh(); } toDetail (data){ const {navigator} = this.props; navigator.push({ name: 'BookDetailView', component: BookDetailView, params: {data} }); } onScroll (e){ let scrollY = e.nativeEvent.contentOffset.y; this.setState({scrollY}); } render (){ return ( <View> <View style={sty.searchWrap}> <TextInput style={sty.searchInput} ref={(SearchInput) => {_SearchInput = SearchInput;}} value={'' + this.state.q} placeholder={'輸入書名'} autoCorrect={false} clearButtonMode={'while-editing'} underlineColorAndroid={'transparent'} autoCapitalize={'none'} onChangeText={(q) => this.setState({q})} /> <TouchableOpacity style={sty.searchBtn} onPress={() => { _SearchInput.blur(); _ListView.scrollTo({y: 0, animated: false}); this.search(); }} > <Text style={sty.searchBtnText}>搜索</Text> </TouchableOpacity> </View> <ListView style={sty.listWrap} ref={(ListView) => {_ListView = ListView;}} enableEmptySections={true} automaticallyAdjustContentInsets={false} dataSource={this.state.dataSource} renderRow={(rowData) => <BookListItem {...rowData} toDetail={this.toDetail.bind(this)}></BookListItem>} renderFooter={this.renderFooter.bind(this)} onEndReached={this.onEnd.bind(this)} onEndReachedThreshold={50} onScroll={this.onScroll.bind(this)} scrollEventThrottle={5} refreshControl={ <RefreshControl onRefresh={this.listRefresh.bind(this)} refreshing={this.state.isLoading} colors={['#ff0000', '#00ff00', '#0000ff']} enabled={true} /> } /> {this.state.scrollY > (height - 30 - 40 * refRat) ? <ListToTop listView={_ListView}/> : null} {this.state.showLoading ? <ActivityIndicator style={sty.loading} size={"large"} /> : null} </View> ); } }

 

在佈局上ListView位於整個頂部搜索欄的下方,樣式爲flex佈局,其內部子組件將按列排布。具體的屬性和配置,以及dataSource數據集等在文檔均有說明。

這裏需要介紹下組件調用別的組件的方法:

BookListView作爲導出的組件,它是由類中的state和方法,以及render方法返回的一個各種組件組成的複合組件,在實例中,我們點擊了搜索按鈕,輸入框失去了焦點,讓列表返回頂部,並觸發了列表的搜索更新。都是通過refs這個屬性來實現的。

 <TextInput 
    // ...
    ref={(SearchInput) => {_SearchInput = SearchInput;}}
 <TouchableOpacity
// ... style={sty.searchBtn} onPress={() => { _SearchInput.blur(); _ListView.scrollTo({y: 0, animated: false}); this.search(); }} >

// ...
<ListView
   style={sty.listWrap}
   ref={(ListView) => {_ListView = ListView;}}

// ...

可以看到ListView這個組件類配置了一個ref屬性,值爲一個函數,入參即爲這個ListView類在運行的時候實例化出的ListView對象,然後賦值給了_ListView這個變量,在點擊搜索按鈕的時候我們調用了_ListView的scrollTo方法來返回頂部,然後調用了BookListView類最後實例出的對象的search方法,也就是 this.search(); 。基於箭頭函數的特性,this是在寫的時候決定,因此它一定是指向BookListView對象的。如果是這樣調用:  <TouchableOpacity onPress={this.search.bind(this)} >  ,就需要這個this通過es5的bind方法傳入進去,強制要求search方法被調用的時候其內部this一定指向BookListView實例化出來的那個對象,因爲這個search方法內部可能需要用到這個對象的屬性和方法。如果不用bind方法來強行指定上下文環境,this指向的會是TouchableOpacity類實例化出的那個對象。這也是屬於語法範疇。

ref屬性可以不一定賦予一個函數作爲值,一個字符串也是可行的。比如: ref={'ListView'}  ,然後可以通過  this.refs['ListView']  取到ListView這個對象,即可調用它的方法。當然,在使用時this一定要保證是指向BookListView對象的。 

this的指向如果弄錯,如果遇到這類報錯,可以從這點開始排查,會經常出現’undefined is not a function‘這類報錯。

 

基於React流程的setState方法是異步的(不受React控制的流程除外),這個一定要記住,如果在setState後直接獲取state,值有可能還沒有改變,要想保證改變,請使用es7的async/await特性,讓異步操作用同步的方式來書寫,其他異步方式也能解決。

列表的下拉刷新是通過配置refreshControl來實現的。

回到頂部按鈕的顯示與否是通過監聽列表滾動的Y軸偏移來判斷的,列表每次滾動會調用onScroll這個回調,從事件中獲取到偏移,通過偏移量來決定按鈕是否顯示。由數據來驅動視圖



// ...
onScroll (e){ let scrollY = e.nativeEvent.contentOffset.y; this.setState({scrollY}); }
// ...
<ListView
// ...
onScroll={this.onScroll.bind(this)}
scrollEventThrottle={5}
// ...
{this.state.scrollY > (height - 30 - 40 * refRat) ? <ListToTop listView={_ListView}/> : null}

注意:

1.jsx裏不能食用if else 等,只支持一個語句,所以有判斷的地方必須使用三元表達式。

2.scrollEventThrottle是節流控制,類似於jquery的debounce-throttle插件,可以避免每一次的scroll都執行onScroll回調帶來的性能問題,畢竟我們一秒鐘的滾動時間會觸發很多很多次onScroll事件。

上拉加載更多是通過滾動到底部的檢測來觸發事件。官方文檔中都有配置介紹。

 

列表項BookListItem是封裝的一個業務組件,通過傳入props來提供渲染需要的數據。很簡單佈局的一個組件,這裏不再詳細說。

跳轉圖書詳情視圖BookDetailView是通過push壓棧的方式進行的,之所以沒有使用resetTo方法,是因爲希望進入詳情以後能通過pop出棧便能返回上一個視圖:

// ...
toDetail (data){ const {navigator} = this.props; navigator.push({ name: 'BookDetailView', component: BookDetailView, params: {data}    });
}
// ...

豆瓣開放接口的簡單封裝,很簡單,就2個接口,哈哈。使用了rn環境自帶的fetch:

// common/model.js

import {ToastAndroid} from 'react-native'; const _fetch = (url, param) => { let qstring = ''; for (let key in param) qstring += key + '=' + param[key] + '&'; url += '?' + qstring; return fetch(url); } const handle = async (url, param = {}) => { try { let response = await _fetch(url, param); let res = await response.json(); return res; } catch (error){ ToastAndroid.show('網絡請求錯誤:' + error, ToastAndroid.LONG); return {books: [],subjects: []}; } } // 豆瓣開放API url const domain = 'https://api.douban.com'; const douban = { searchBook : domain + '/v2/book/search' , movieTop250 : domain + '/v2/movie/top250' }; /** * 搜索圖書 * @param {q 查詢關鍵字 tag 查詢標籤 start 本次偏移 count 本次條數} * @return {start 本次偏移 count 本次條數 total 總條數 books[] 圖書集合} */ export const searchBook = param => handle(douban.searchBook, param);/** * 電影Top250排行 * @param {start 本次偏移 count 本次條數} * @return {start 本次偏移 count 本次條數 total 總條數 total 總條數 subjects[] 電影集合} */ export const movieTop250 = param => handle(douban.movieTop250, param);

 

七、電影排行

這個視圖的列表相關組件以及詳情組件與搜圖書視圖基本是一致的,只是少了搜索而已。

優化點:其實這兩個視圖的列表組件可以提取出公用的地方來抽象一次,封裝爲一個具有基本功能的公用List業務組件。搜圖書列表和電影排行列表都可以繼承自它,按需重寫和擴展其他方法即可。

因爲列表和詳情基本與搜圖書界面的功能基本一致,這裏就只介紹一下webview內嵌豆瓣h5這裏。從豆瓣取回的電影數據,有一個叫'alt'的字段存放了這個電影url,通過webview加載這個url,即可訪問豆瓣的web頁面:

<View>
    <View style={sty.header}>
        <TouchableOpacity
            style={sty.backBtn}
            onPress={this.back.bind(this)}
        >
            <Text style={sty.backBtnText}>{'<'}</Text>
        </TouchableOpacity>
        <Text numberOfLines={1} style={sty.headerText}>{this.props.title}</Text>
        {this.state.canGoBack  ? 
            <TouchableOpacity
                style={sty.rightBtn}
                onPress={this.directBack.bind(this)}
            >
                <Text style={sty.rightBtnText}>{'關閉'}</Text>
            </TouchableOpacity> :
            <Text style={sty.rightBtn}></Text>}
    </View>
    <WebView
        style={sty.webView}
        ref={'webview'}
        automaticallyAdjustContentInsets={false}
        source={{uri: this.props.url}}
        javaScriptEnabled={true}
        domStorageEnabled={true}
        decelerationRate="normal"
        startInLoadingState={true}
        renderLoading={() => <ActivityIndicator style={sty.loading} size={"large"} />}
        onNavigationStateChange={this.onNavigationStateChange.bind(this)}
        onError={this.loadError.bind(this)}
    />
    <Dialog
        ref={'dialog'}
        content={'刷新嗎?'}
        cancelAction={this.directBack.bind(this)}
        okAction={this.reloadWebView.bind(this)}
    />
</View>

頭部有三個元素:左邊的後退按鈕,中間的標題,右邊的直接關閉按鈕。

後端按鈕可以控制webview的後退,只要webview的history還沒有back到底,否則將直接通過整個app的導航組件回到電影詳情界面:

async back (){
    if (this.state.canGoBack){
        this.refs['webview'].goBack();
    } else {
        this.directBack();
    }
}
directBack (){
    const {navigator} = this.props;
    navigator.pop();
}

 

webview的每次history變化都會觸發onNavigationStateChange事件,然後回調這個方法:

async onNavigationStateChange (navState){
    var {canGoBack, canGoForward, title, loading} = navState;
    await this.setState({
        canGoBack,
     title: loading ? '' : title }); }

傳入navState狀態對象,我們可以取到canGoBack和canGoForward這兩個布爾值,它們表示了當前webview的history狀況,能否前進和後退。如果canGoBack爲true,我們通過調用webview的back方法,可以實現history.back的功能。navState.loading爲false表示加載完成,這時我們可以取到web頁面的title作爲header的title。

並且我們在state裏維護了canGoBack這個狀態值,當他爲true的時候,會顯示右側的關閉按鈕,點擊這個按鈕,可以直接回退到電影詳情界面。好處在於:當我們在webview中點擊web頁面的連接前進了很多次之後,不想再不停的點擊後退按鈕,不論history有多少層都可以直接退回到上個場景:

 

 

 

這個虛擬機裏面請求外網數據很緩慢,加載頁面更慢...

 

八、關於

放了一個swiper組件,是一個第三方的組件:Github。下面放了一個登出按鈕,點擊以後彈出確任的對話框。點擊確定,通過導航器的resetTo方法直接跳轉到登錄界面,並重置掉路由棧。

功能比較簡單就不做過多介紹。

 

 

九、調試

ctrl+m或者打開菜單,點擊'Debug JS Remotely',可以開啓遠程調試:

 

 

 

在js代碼裏console打出的信息都會在Console tab展示出來,報錯和警報也會有。還Sources還可以打斷點等。但是我開啓遠程調試後,有些時候非常卡,但幀數並不低。

 

 除了菜單裏的'Toggle Inspector'可以簡易的查看一下元素以外,還可以安裝react-devtools,下載地址:Github。也可以在chrome應用商店搜索安裝(需要科學上網工具)。

這個調試插件我安裝好以後,並沒有使用起。即便在擴展管理裏勾選了'允許訪文件地址',在開啓遠程調試以後並沒有探測到rn工程,但是訪問豆瓣h5等使用react-js構建的站點時,可以嗅探到,並在chrome開發者工具裏成功喚起了react-devtools的tab。Git上查看了issue,發現很多人也有這個問題,重新安裝插件也沒法解決...可能和chrome版本有關係,太高版本可能會出這個問題。

 

十、公共組件

這個demo抽象和封裝了一些公共組件,但是沒有提取完,還有優化點。這裏介紹一下components目錄下的Dialog對話框:

export default class Dialog extends Component {
    constructor (props){
        super(props);

        this.state = {
            show: false
        };
    }

    async showDialog (){
        await this.setState({show: true});
    }

    async hideDialog (){
        await this.setState({show: false});
    }

    render (){
        return (
            <Modal
                visible={this.state.show}
                animationType='slide'
                transparent={true}
                onShow={() => {}}
                onRequestClose={() => {}}
            >  
                <View style={sty.modalStyle}>
                    <View style={sty.subView}>
                        <Text style={sty.titleText}>{this.props.title || '提示'}</Text>
                        <Text style={sty.contentText}>{this.props.content || '確定嗎?'}</Text>  
                        <View style={sty.horizontalLine}></View>
                        <View style={sty.buttonView}>  
                            <TouchableHighlight
                                underlayColor='transparent'
                                style={sty.buttonStyle}
                                onPress={() => {
                                    this.props.cancelAction && this.props.cancelAction();
                                    this.hideDialog.bind(this)();
                                }}
                            >
                                <Text style={sty.buttonText}>取消</Text>
                            </TouchableHighlight>  
                            <View style={sty.verticalLine}></View>
                            <TouchableHighlight
                                underlayColor='transparent'
                                style={sty.buttonStyle}
                                onPress={() => {
                                    this.props.okAction && this.props.okAction();
                                    this.hideDialog.bind(this)();
                                }}
                            >
                                <Text style={sty.buttonText}>確定</Text>  
                            </TouchableHighlight>  
                        </View>  
                    </View>  
                </View>  
            </Modal>
        );
    }
}

 

十一、總結

1. windows下的安裝環境的坑確實很多,而且這還只跑是在模擬器上,如果真機測試的話,不同機型、廠商應該會有適配的問題出現。mac下的表現應該要好得多,畢竟大家都說ios纔是親兒子。相信安卓方面以後還會不斷的優化。如果需要一套代碼同時跑安卓和ios兩個平臺,底層一定需要做組件封裝,來屏蔽平臺的差異。業務開發的時候,就不太需要考慮平臺差異了。

2. 調試的提示信息有時候不太明確,需要挨着排查代碼。

3. 佈局和樣式需要適應。

4. 組件使用上的限制文檔沒有明確提出,很多時候都是用到那裏,那樣寫了,才發現不對。

5. html現在都講究結構和樣式分離,結構和邏輯分離。jsx又把我們拉回了以前的時代。

6. rn的生態圈還是很好的。

7. 其他 ...

 

以上希望對學習react native的同學有所幫助。不對的地方也請指出。

 

最後分享一個github上找到的一個不錯的react native系列文章,包含作者原創和翻譯的各種資料,原理、構架設計、性能優化、離線打包、增量更新都有介紹,入門rn以後可以看看,一定會有幫助的,可以基於此重構你的demo。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章