1. 程式人生 > >React-Native 之 專案實戰(一)

React-Native 之 專案實戰(一)

前言

  • 本文有配套視訊,可以酌情觀看。
  • 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯絡我。
  • 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關。
  • 如文中內容對您造成不便,煩請聯絡 [email protected] 處理,謝謝。
  • 轉載麻煩註明出處,謝謝。
  • 原始碼託管到 github 上,需要原始碼的 點我下載,喜歡的話記得 Star,謝謝!

ES5轉ES6

專案簡介

  • 先來看下我們仿照的這款APP的效果:

最終效果圖.gif

  • 從上圖中,我們可以看出複雜度並不大,但是時間關係我們儘量將所有的模組都做完,並完善細節。

譯註:

  • 建議開啟 視訊 配合 文字 學習,以免有某些細節文中沒有提到,但以文中內容為準(根據反饋進行相應更新)

  • 之所以選擇這款APP,和我個人的愛好有關,當然關鍵還是因為這個APP整體並不複雜,包含了市面上常見APP的樣式,並且很順利地就獲取到所有請求引數和圖片資源,很適合我們體驗 React-native 大致的開發流程。

專案分析

  • 在開發APP前,產品經理大致會進行需求的分析,然後開會討論開發過程中需要使用到的技術、會遇到的難點、分配相應任務、傾聽開發人員意見並進行相應的修改,最終確定整體原型圖、開發流程、技術、週期等等,當然其中還有UI的介入,我們沒有產品經理,UI也有現成的,所以大致給大家劃分以下幾塊:

    • 需求分析:這款APP主要是通過抓取各大電商平臺的 商品優惠資訊 進行篩選、分類並最終展現給使用者,使使用者可以方便、快捷、實時的獲取高質量的優惠資訊。

    • 開發模型:我們這邊類似基於 原型模型 開發。

    • 使用的技術:React-Native

    • 功能模組:主要分為 首頁、海淘模組、小時風雲榜 三大模組等其它附屬模組(酌情增加)。

    • 整體架構:

      • 主體:由 TabBar 作為主體框架,以 首頁、海淘模組、小時風雲榜 為整體模組,根據 原型圖 的效果選擇相應的跳轉方式
      • 資料展示:根據 原型圖 選擇相應的資料展示方式
    • 命名規則:參考 編碼規範文件(不同公司之間都有差異,具體看公司提供的文件,這邊先遵守下面提到的規則即可)

  • 測試:MDZZ,誰測試→→!

工程環境配置

  • 所有需要用到的資源點選下載

  • 首先,來配置 iOS 端。

  • 將壓縮包內的 Images.xcassets 資料夾直接替換掉我們iOS工程中的 Images.xcassets 資料夾。
  • 這時候我們可以看到所有圖片資源已經成功匯入到iOS工程中,接著我們點選工程檔案進行一些必要的配置。
  • General —— App Icons and Launch Images —— 修改 Launch Images SourceImages.xcassets 資料夾內的 LaunchImage ,清除 Launch Screen File 內容。
  • General —— Deployment Info —— Device Orientation —— 只保留 Portrait 選項。
  • 開啟 info.plist 檔案,找到 Bundle name 選項,將其內容修改為 逛丟學習
  • 開啟 info.plist 檔案,找到 App Transport Security Settings 選項,給其新增 Allow Arbitrary Loads 選項並設定內容為 YES (如果使用 IPV6標準 可以忽略這一步)
  • OK,至此 iOS 端配置完畢。

iOS配置後效果.gif

  • 接著,來配置 Android 端。
  • 將壓縮包內的 drawable-xxhdpi 資料夾複製貼上到 GD/android/app/src/main/res/ 中。
  • 設定 APP圖示 進入 GD/android/app/sec/ 開啟 AndroidManifest 檔案,修改 android:icon 項,如下:

        <applicatio>
            android:icon="@drawable/icon"
        </application>
    
  • 設定 APP名稱 進入 GD/android/app/src/main/res/values/ 中,開啟 strings.xml 檔案,做如下修改:

        <resources>
            <string name="app_name">逛丟學習</string>
        </resources>
  • OK,至此 Android 配置完畢。

Android配置後效果.gif

目錄結構與命名規則

  • 為了方便理解,我們這邊先不按照常規的React-native開發結構進行開發,後續章節再慢慢轉變
  • 這邊我們將檔案分為 main(入口)、home(首頁)、ht(海淘)、hourList(小時風雲榜) 4大部分,將相關的檔案放入對應的資料夾,避免開發中頻繁切換文件給新手帶來煩躁感
  • 命名規則:
    • 資料夾命名方式我們就跟著 React-Native 預設的方式,採用 小寫 + 下劃線 進行命名
    • 檔案命名方式我們採用 字首(大寫) + 模組名稱(帕斯卡) 的方式進行命名
    • 函式、常量、變數等使用 駝峰命名規則

目錄結構:

譯註:

  • 駝峰命名規則:首字母小寫,後續單詞以大寫開頭,詳情點選 駝峰命名 檢視閱讀。
  • 帕斯卡命名規則:和駝峰類似,只不過將首字母改為大寫,詳情點選 帕斯卡命名 檢視閱讀。
  • 下劃線命名規則:就是使用下劃線分割單詞。

第三方框架

  • 這邊來講下在 React-Native 中怎麼匯入第三方框架

  • 首先,第三方框架肯定是要到 GitHub 找嘍。

  • 在搜尋框內搜尋 react-native-tab-navigator
  • 在下面的 說明 中告訴我們了,使用終端 —— 進到工程的主目錄下 —— 複製命令列()—— 回車 —— 等待下載完成就匯入到工程中了。
  • 到此,第三方框架匯入完成,使用在下面會提到。

主體框架搭建

  • 上面提到使用 TabBar 作為主體框架,但是官方只提供了iOS端的 TabBarIOS ,時間原因為了加快開發進度,並且順帶講解 第三方框架使用 所以我們使用 <react-native-tab-navigator>進行開發

  • 既然要使用框架,肯定要先引入框架檔案。

   // 引用第三方框架
   import TabNavigator from 'react-native-tab-navigator';
  • 根據 使用說明 文件可以看出,使用方法和官方的 TabBarIOS 類似(不清楚的麻煩參考React Native 之 TabBarIOS和TabBarIOS.Item使用),所以我們把 三大模組 新增進TabBar,並且各個模組都是以 Navigator 的形式存在。
    export default class GD extends Component {

    // ES6
    // 構造
      constructor(props) {
        super(props);
        // 初始狀態
        this.state = {
            selectedTab:'home',
        };
      }

    // 返回TabBar的Item
    renderTabBarItem(title, selectedTab, image, selectedImage, component) {
        return(
            <TabNavigator.Item
                selected={this.state.selectedTab === selectedTab}
                title={title}
                selectedTitleStyle={{color:'black'}}
                renderIcon={() => <Image source={{uri:image}} style={styles.tabbarIconStyle} />}
                renderSelectedIcon={() => <Image source={{uri:selectedImage}} style={styles.tabbarIconStyle} />}
                onPress={() => this.setState({ selectedTab: selectedTab })}>
                    // 新增導航功能
                <Navigator
                        // 設定路由
                    initialRoute={{
                        name:selectedTab,
                        component:component
                    }}

                    renderScene={(route, navigator) => {
                        let Component = route.component;
                        return <Component {...route.params} navigator={navigator} />
                    }}
                />
            </TabNavigator.Item>
        );
    }

    render() {
        return (
            <TabNavigator>
                {/* 首頁 */}
                {this.renderTabBarItem("首頁", 'home', 'tabbar_home_30x30', 'tabbar_home_selected_30x30', Home)}
                {/* 海淘 */}
                {this.renderTabBarItem("海淘", 'ht', 'tabbar_abroad_30x30', 'tabbar_abroad_selected_30x30', HT)}
                {/* 小時風雲榜 */}
                {this.renderTabBarItem("小時風雲榜", 'hourlist', 'tabbar_rank_30x30', 'tabbar_rank_selected_30x30', HourList)}
            </TabNavigator>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    tabbarIconStyle: {
        width:Platform.OS === 'ios' ? 30 : 25,
        height:Platform.OS === 'ios' ? 30 : 25,
    }
});
  • 至此,主體框架搭建完畢。

主體框架效果.gif

自定義導航欄樣式

  • 從效果圖中可以看出,導航欄的樣式都差不多,因為我們前面已經設定了 Navigator ,這邊的話我們還需要自定義 Navigator 的樣式,可以看到所有的 Navigator 樣式都是相近的,所以這邊我們就抽出來,讓所有的 Navigator 共用一個元件就可以了。

  • 那麼首先我們在 main 資料夾中建立 GDCommunalNavBar 檔案並初始化一下里面基本的內容

  • 接著,我們來看下首頁的導航欄,首頁導航欄分別有左中右三個按鈕,左邊為半小時熱門,中間為點選下拉顯示支援篩選的平臺的列表,右邊則是商品搜尋,通常 Navigator 也只有這3個元件,為了使用者高度地自定義,這邊我們只在 currencyNavBar 中設定3個元件的佈局,然後提供介面,獲取外部傳入的值,並在內部判斷是否需要建立相應的元件。


    export default class GDCommunalNavBar extends Component {

    static propTypes = {
        leftItem:PropTypes.func,
        titleItem:PropTypes.func,
        rightItem:PropTypes.func,
    };

    // 左邊
    renderLeftItem() {
        if (this.props.leftItem === undefined) return;
        return this.props.leftItem();
    }

    // 中間
    renderTitleItem() {
        if (this.props.titleItem === undefined) return;
        return this.props.titleItem();
    }

    // 右邊
    renderRightItem() {
        if (this.props.rightItem === undefined) return;
        return this.props.rightItem();
    }

    render() {
        return (
            <View style={styles.container}>
                {/* 左邊 */}
                <View>
                    {this.renderLeftItem()}
                </View>
                {/* 中間 */}
                <View>
                    {this.renderTitleItem()}
                </View>
                {/* 右邊 */}
                <View>
                    {this.renderRightItem()}
                </View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        width:width,
        height:Platform.OS === 'ios' ? 64 : 44,
        backgroundColor:'white',
        flexDirection:'row',
        justifyContent:'space-between',
        alignItems:'center',
        borderBottomWidth:0.5,
        borderBottomColor:'gray',
        paddingTop:Platform.OS === 'ios' ? 15 : 0,
    },
});
  • 這邊我們就已經完成了 Navigator 的樣式,我們到首頁來用一下,看好不好用,使用這邊就不說了(1.引用外部檔案;2.

首頁半小時熱門

  • 這邊我們就先從 半小時熱門 開始,像這樣的資料展示,我們肯定是優先選擇 ListView ,其中,cell 的樣式分解如下:

cell樣式.png

  • 我們先將資料請求下來,確定正確獲取到資料後,再來定義 cell 的樣式。

  • 接下來我們來自定義一下 cell 樣式


    export default class GDCommunalNavBar extends Component {

    static propTypes = {
        image:PropTypes.string,
        title:PropTypes.string,
    };

    render() {
        return (
            <View style={styles.container}>
                {/* 左邊圖片 */}
                <Image source={{uri:this.props.image}} style={styles.imageStyle} />
                {/* 中間的文中 */}
                <View>
                    <Text numberOfLines={3} style={styles.titleStyle}>{this.props.title}</Text>
                </View>
                {/* 右邊的箭頭 */}
                <Image source={{uri:'icon_cell_rightArrow'}} style={styles.arrowStyle} />
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flexDirection:'row',
        alignItems:'center',
        justifyContent:'space-between',
        backgroundColor:'white',
        height:100,
        width:width,
        borderBottomWidth:0.5,
        borderBottomColor:'gray',
        marginLeft:15

    },

    imageStyle: {
        width:70,
        height:70,
    },
    titleStyle: {
        width:width * 0.65,
    },
    arrowStyle: {
        width:10,
        height:10,
        marginRight:30
    }
});
  • 好了,到這裡 cell 樣式也定義完成並且效果是一樣的。

    export default class GDHalfHourHot extends Component {

    // 構造
      constructor(props) {
        super(props);
        // 初始狀態
        this.state = {
            dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}),
        };
        // 繫結
        this.fetchData = this.fetchData.bind(this);
      }

    // 網路請求
    fetchData() {
        fetch('http://guangdiu.com/api/gethots.php')
            .then((response) => response.json())
            .then((responseData) => {
                this.setState({
                    dataSource: this.state.dataSource.cloneWithRows(responseData.data)
                });
            })
            .done()
    }

    popToHome() {
        this.props.navigator.pop();
    }

    // 返回中間按鈕
    renderTitleItem() {
        return(
            <Text style={styles.navbarTitleItemStyle}>近半小時熱門</Text>
        );
    }

    // 返回右邊按鈕
    renderRightItem() {
        return(
            <TouchableOpacity
                onPress={()=>{this.popToHome()}}
            >
                <Text style={styles.navbarRightItemStyle}>關閉</Text>
            </TouchableOpacity>
        );
    }

    // 返回每一行cell的樣式
    renderRow(rowData) {
        return(
            <CommunalHotCell
                image={rowData.image}
                title={rowData.title}
            />
        );
    }

    componentDidMount() {
        this.fetchData();
    }

    render() {
        return (
            <View style={styles.container}>
                {/* 導航欄樣式 */}
                <CommunalNavBar
                    titleItem = {() => this.renderTitleItem()}
                    rightItem = {() => this.renderRightItem()}
                />

                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this.renderRow}
                    showsHorizontalScrollIndicator={false}
                    style={styles.listViewStyle}
                />
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex:1,
        alignItems: 'center',
    },

    navbarTitleItemStyle: {
        fontSize:17,
        color:'black',
        marginLeft:50
    },
    navbarRightItemStyle: {
        fontSize:17,
        color:'rgba(123,178,114,1.0)',
        marginRight:15
    },

    listViewStyle: {
        width:width,
    }
});

近半小時熱門.gif

  • 從效果圖中可以看出,我們還少了上面的提示標題,這邊很簡單,我們也來快速完成一些
    {/* 頂部提示 */}
    <View style={styles.headerPromptStyle}>
        <Text>根據每條折扣的點選進行統計,每5分鐘更新一次</Text>
    </View>

樣式部分:

    headerPromptStyle: {
        height:44,
        width:width,
        backgroundColor:'rgba(239,239,239,0.5)',
        justifyContent:'center',
        alignItems:'center'
    }

提示標題補充.png

隱藏於顯示TabBar之通知的使用

  • 配置TabBar隱藏與顯示條件

    // ES6
    // 構造
      constructor(props) {
        super(props);
        // 初始狀態
        this.state = {
            selectedTab:'home',
            isHiddenTabBar:false,   // 是否隱藏tabbar
        };
      }

   <TabNavigator
                tabBarStyle={this.state.isHiddenTabBar !== true ? {} : {height:0, overflow:'hidden'}}
                sceneStyle={this.state.isHiddenTabBar !== true ? {} : {paddingBottom:0}}
            >
                {/* 首頁 */}
                {this.renderTabBarItem("首頁", 'home', 'tabbar_home_30x30', 'tabbar_home_selected_30x30', Home)}
                {/* 海淘 */}
                {this.renderTabBarItem("海淘", 'ht', 'tabbar_abroad_30x30', 'tabbar_abroad_selected_30x30', HT)}
                {/* 小時風雲榜 */}
                {this.renderTabBarItem("小時風雲榜", 'hourlist', 'tabbar_rank_30x30', 'tabbar_rank_selected_30x30', HourList)}
            </TabNavigator>
  • 這邊我們引入新的知識 —— 通知

  • 使用通知很簡單,首先需要註冊通知並在適當的地方進行銷燬


    componentDidMount() {
        // 註冊通知
        this.subscription = DeviceEventEmitter.addListener('isHiddenTabBar', (data)=>{this.tongZhi(data)});
    }

    componentWillUnmount() {
        // 銷燬
        this.subscription.remove();
    }
  • 接著在我們需要的地方傳送通知

    componentWillMount() {
        // 傳送通知
        DeviceEventEmitter.emit('isHiddenTabBar', true);
    }

    componentWillUnmount() {
        // 傳送通知
        DeviceEventEmitter.emit('isHiddenTabBar', false);
    }

隱藏顯示TabBar.gif