3C科技 娛樂遊戲 美食旅遊 時尚美妝 親子育兒 生活休閒 金融理財 健康運動 寰宇綜合

Zi 字媒體

2017-07-25T20:27:27+00:00
加入好友
在進行了2個星期的基礎學習(Flexbox, React.js, JSX, JavaScript)之後,想通過一個實戰項目來提高React Native的開發水平,於是找到了下面這個項目:一. 項目介紹這是我在學習賈鵬輝老師在慕課網上的一個很火的React Native實戰的教程后,寫出的課程Demo。該課程是慕課網裡很火的一個React Native課程,當初在看了課程介紹和課程安排覺得講解的點還是很全的,所以毫不猶豫地買了下來。從看視頻,敲代碼到重構,改bug,大概花了2個多星期的時間,除了調用友盟的SDK以及CodePush集成之外,其他的部分都基本完成了,JavaScript代碼佔據了95%,基本上算是一個純React Native項目,而且同時可以在iOS和Android設備上運行:上排是iOS模擬器 | 下排是Android模擬器而且比較吸引人的是該項目可以實現多個主題的切換:多主題切換主題切換的技術實現會在下文給出。用一個動圖來過一遍大致的需求:Demo GitHub地址:GitHubPopular-SJ可以按照README文件里的方法運行該項目。上傳到GitHub已經過賈老師允許值得一提的是:這確實是一門物有所值的課程,可以讓想入門React Native的開發者少走很多彎路。雖然我上傳的Demo可以實現視頻里大部分功能,但是經過調試,修改後的代碼信息量還是很有限的,而且老師在視頻中講解的很多關於實際開發的知識點在代碼中並沒有體現出來,所以還是建議各位報名參加課程來提高自己的開發水平。二. React Native開發的幾個關鍵技術點首先用一張思維導圖來看一下第二節講的內容:2.1 組件化的思想React Native是React在移動端的跨平台方案。如果想更快地理解和掌握React Native開發,就必須先了解React。React是FaceBook開源的一個前端框架,它起源於 Facebook 的內部項目,並於 2013 年 5 月開源。因為React 擁有較高的性能,代碼邏輯非常簡單,所以越來越多的人已開始關注和使用它,目前該框架在Github上已經有7萬+star。React採用組件化的方式開發,通過將view構建成組件,使得代碼更加容易得到復用,能夠很好的應用在大項目的開發中。有一句話說的很形象:在React中,構建應用就像搭積木一樣。因此,如果想掌握React Native,就必須先了解React中的組件。那麼問題來了,什麼是組件呢?在React中,在UI上每一個功能相對獨立的模塊就會被定義為組件。 相對小的組件可以通過組合或者嵌套的方式構成大的組件,最終完成整體UI的構建。因此,整個UI是一個通過小組件構成的大組件,而且每個組件只關心自己部分的邏輯,彼此獨立。React認為一個組件應該具有如下特徵:可組合(Composeable):一個組件易於和其它組件一起使用,或者嵌套在另一個組件內部。如果一個組件內部創建了另一個組件,那麼說父組件擁有它創建的子組件,通過這個特性,一個複雜的UI可以拆分成多個簡單的UI組件;可重用(Reusable):每個組件都是具有獨立功能的,它可以被使用在多個UI場景;可維護(Maintainable):每個小的組件僅僅包含自身的邏輯,更容易被理解和維護;舉個,我們看一下這個Demo使用的導航欄:封裝好的導航欄就可以被稱之為一個組件,它符合上述三個特點:可組合:可以將導航欄組件放在頁面組件中作為頁面組件的子組件。而且在導航欄組件的內部,也有按鈕組件等子組件。可重用:如果封裝好了該組件,就可以放在任意需要導航欄的頁面(組件)使用,也可以放在其他項目中使用。可維護:因為具有獨立的功能和展示邏輯,所以便於定位和修改。在了解了組件的基本概念以後,我們來看一下組件其他的一些相關知識。2.2 組件的屬性與狀態在React Native(React.js)里,組件所持有的數據分為兩種:屬性(props):組件的props是不可變的,它只能從其他的組件(例如父組件)傳遞過來。狀態(state):組件的state是可變的,它負責處理與用戶的交互。在通過用戶點擊事件等操作以後,如果使得當前組件的某個state發生了改變,那麼當前組件就會觸發render方法刷新自己。舉一個這個項目的收藏頁面來說:我們可以看到這個頁面有兩個子頁面,一個是『最熱』頁面(組件),另一個是『趨勢『頁面(組件)。那麼這兩個組件都有什麼props和state呢?props首先看一下props:由於props是從其父組件傳遞過來的,那麼可想而知,props的聲明應該是在當前組件的父組件里來做。在React Native中,通常props的聲明是和當前組件的聲明放在一起的://最熱子頁面//趨勢子頁面在這裡,收藏頁面是父組件,而最熱頁面和趨勢頁面是其子組件。在收藏頁面組件里聲明了最熱頁面和趨勢頁面的組件。而且我們也可以看到,最熱頁面和趨勢頁面組件都用的是同一個組件:FavoriteTabPage,而這兩個頁面的不同點只在於傳入的兩個props的不同:tabLabel和flag。而在FavoriteTabPage組件內部,如果想調用flag這個props,可以使用this.props.flag來調用。再來看一下state:state下面是最熱和趨勢頁面的組件:class FavoriteTabPage extends Component{ //組件的構造方法 constructor(props){ super(props); this.state={ dataSource:new ListView.DataSource({rowHasChanged:(r1,r2)=>r1!==r2}), isLoading:false, } } ... }這裡面定義了兩個state:dataSource:列表的數據源isLoading:是否正在刷新這兩個state都是將來可能經常變化的。比如在網路請求以後,列表的數據源會被替換掉,這個時候就要調用 this.setState({ //把新的值newDataArr對象傳給dataSource dataSource:newDataArr })來觸發render方法來刷新列表組件。2.3 組件的生命周期和iOS開發里ViewController的生命周期類似,組件也有生命周期,大致分為三大階段:Mounting:已插入真實 DOMUpdating:正在被重新渲染Unmounting:已移出真實 DOMDOM是前端的一個概念,暫時可以粗略理解為一個頁面的樹形結構。在每個階段都有相應的狀態和與之對應的回調函數,具體可以看下圖:從上圖中我們可以看到,React 為每個狀態都提供了兩種回調函數,will 函數在進入狀態之前調用,did 函數在進入狀態之後調用。在這裡講一下這其中幾個重要的回調函數:render該函數是組件的渲染回調函數,該函數是必須實現的,並且必須返回一個組件或一個包含多個子組件的組件。注意:該函數可以被調用多次:初始化時的渲染以及state改變以後的渲染都會調用這個函數。componentDidMount在初始化渲染執行之後立刻調用一次,也就是說,在這個函數調用時,當前組件已經渲染完畢了,相當於iOS開發中ViewController里的viewDidLoad方法。我們通常在這個方法里執行網路請求操作。componentWillReceiveProps(object nextProps)在當前組件接收到新的 props 的時候調用。此函數可以作為 react 在 prop 傳入之後, render 渲染之前更新 state 的機會。新的props可以從參數里取到,老的 props 可以通過 this.props 獲取到。注意:在初始化渲染的時候,該方法不會調用。shouldComponentUpdate(object nextProps, object nextState):在接收到新的 props 或者 state,將要渲染之前調用。如果確定新的 props 和 state 不會導致組件更新,則此處應該 返回 false,這樣組件就不會更新,減少了性能上不必要的損耗。注意:該方法在初始化渲染的時候不會調用。componentWillUnmount在組件從 DOM 中移除的時候立刻被調用。例如當前頁面點擊返回鍵跳轉到上一頁面的時候就會調用。我們通常在這個方法里移除通知。具體做法在後文會提到。到此,已經講解了一些組件相關的知識,下面來看一下我們如何使用組件來搭建界面。2.4 使用組件來搭建界面在這裡我們舉幾個例子來看一下在React Native里搭建View的方式。首先我們來看一下最熱頁面的cell是如何布局的:2.41 搭建cell組件首先舉一個在最熱標籤頁面列表裡的一個cell為例,講解一下一個簡單的UI組件是如何實現的:最熱標籤頁面的cell我們把該組件定名為:RespositoryCell,結合代碼來看一下具體的實現:export default class RespositoryCell extends Component{ ... render{ //獲取當前cell的數據賦值給item let item = this.props.projectModel.item?this.props.projectModel.item:this.props.projectModel; //收藏按鈕 let favoriteButton = this.onPressFavorite} > return( //整個cell的view //1. 項目名稱 {item.full_name} //2. 項目介紹 {item.description} //3. 底部 container //3.1 作者container//3.11 作者名稱 Author: //3.12 作者頭像//3.2 star container //3.21 star標題 Starts: //3.21 star數量 {item.stargazers_count}//3.3 收藏按鈕 {favoriteButton}) } }這裡省略了處理交互事件等的函數,為了讓大家集中在cell的布局和樣式上。這裡聲明了RespositoryCell組件,它繼承於Component,也就是組件類,即是說,聲明組件的時候必須都要繼承與這個類。集中看一下該組件的render方法,它返回的是該組件的實際布局:在語法上使用JSX,類似於HTML的標籤式語法,很清楚地將cell的層級展現了出來:最外層被一個View組件包裹著,裡面第一層有三個子組件:兩個Text組件和一個作為底部背景的View組件。底部背景的View組件又有三個子組件:View組件(顯示作者信息),View組件(顯示star信息),收藏按鈕。試著結合代碼來看 一下下面的圖片,可以看出組件的實際布局與代碼的布局是高度一致的: Cell 布局然而僅僅定義組件的層級關係是不夠的,我們還需要定義組件的樣式(例如圖片組件的大小樣式等等),這時候就通過定義一個樣式的對象(通常使用常量對象)來定義一些需要使用的樣式://樣式常量 const styles =StyleSheet.create({ //項目cell的背景view的style cellContainerViewStyle:{ //背景色 backgroundColor:'white', //內邊距 padding:10, //外邊距 marginTop:4, marginLeft:6, marginRight:6, marginVertical:2, //邊框 borderWidth:0.3, borderColor:'#dddddd', borderRadius:1, //iOS的陰影 shadowColor:'#b5b5b5', shadowOffset:{width:3,height:2}, shadowOpacity:0.4, shadowRadius:1, //Android的陰影 elevation:2 }, //項目標題的style repositoryTitleStyle:{ fontSize:15, marginBottom:2, color:'#212121', }, //項目介紹的style repositoryDescriptionStyle:{ fontSize:12, marginBottom:2, color:'#757575' }, //底部container的style bottomContainerViewStyle:{ flexDirection:'row', justifyContent:'space-between' }, //作者container的style authorContainerViewStyle:{ flexDirection:'row', alignItems:'center' }, //作者頭像圖片的style authorAvatarImageStyle:{ width:16, height:16 }, //星星container的style starContainerViewStyle: { flexDirection:'row', alignItems:'center' }, //底部文字的style bottomTextStyle:{ fontSize:11, }, //收藏按鈕的圖片的style favoriteImageStyle:{ width:18, height:18 } })在上面這段代碼里定義了RespositoryCell組件所使用的所有樣式,通過將其賦值給對應子組件的style屬性來實現對組件樣式的修改,例如我們看一下項目標題的組件和其樣式的定義:{item.full_name}在這裡,我們首先定義了一個Text組件用來顯示項目的標題。然後將styles.repositoryTitleStyle賦給了當前Text組件的style,而標題的具體內容,則通過item.full_name來獲取。需要注意的是,在JSX的語法中,對象需要被{}來包裹住,否則會被認為是常量。比如,如果這裡寫成:item.full_name那麼所有項目cell的標題則都會顯示為''item.full_name'',有圖有真相:這是初學者比較常犯的錯誤,所以要注意:在搭建頁面的時候,一定要區分是對象還是常量。如果是對象就必須要用大括弧括起來!如果是對象就必須要用大括弧括起來!如果是對象就必須要用大括弧括起來!這裡每個樣式裡面的長,寬,內外邊距,以及flexDirection等flexBox相關的布局屬性就不介紹了。可以通過查找本文最後的相關鏈接來學習。2.42 搭建靜態表格頁在React Native中搭建個人頁,設置頁這種靜態表格頁面的時候,可以用ScrollView組件包裹各種封裝好的cell組件的形式實現。看一下這個Demo的個人頁的效果圖和代碼實現:個人頁我們在項目中新建一個JavaScript文件,取名為取名為MinePage.js 。該文件就是個人頁面的實現。結合代碼來看一下它的實現(刪除了處理點擊cell的邏輯處理代碼)://區域一:引用區: //引用React,Component(組件類)以及React Native中自帶的組件 import React, { Component } from 'react'; import { StyleSheet, Text, View, Image, ScrollView, TouchableHighlight, } from 'react-native'; //引入項目中定義的其他組件(頁面組件)和常量,路徑為相對路徑 import NavigationBar from '../../common/NavigationBar' import {MORE_MENU} from '../../common/MoreMenu' import GlobalStyles from '../../../res/styles/GlobalStyles' import ViewUtil from '../../util/ViewUtils' import {FLAG_LANGUAGE}from '../../dao/LanguageDao' import AboutPage from './AboutPage' import CustomKeyPage from './CustomKeyPage' import SortPage from './SortKeyPage' import AboutMePage from './AboutMePage' import CustomThemePage from './CustomThemePage' import BaseComponent from '../../base/BaseCommon' //區域二:頁面組件定義區域: export default class MinePage extends BaseComponent { ... //渲染頁面中List中每個cell的統一函數 createSettingItem(tag,icon,text){ return ViewUtil.createSettingItem(=>this.onClick(tag),icon,text,this.state.theme.styles.tabBarSelectedIcon,null); } render{ return {/*=============項目信息Section=============*/} this.onClick(MORE_MENU.About)} > GitHub Popular 項目信息 {/*分割線*/} {/*=============趨勢管理Section=============*/} {/*自定義語言*/} {this.createSettingItem(MORE_MENU.Custom_Language,require('../../../res/images/ic_custom_language.png'),'自定義語言')} {/*語言排序*/} {this.createSettingItem(MORE_MENU.Sort_Language,require('../../../res/images/ic_swap_vert.png'),'語言排序')} {/*=============標籤管理Section=============*/} {/*自定義標籤*/} {this.createSettingItem(MORE_MENU.Custom_Key,require('../../../res/images/ic_custom_language.png'),'自定義標籤')} {/*標籤排序*/} {this.createSettingItem(MORE_MENU.Sort_Key,require('../../../res/images/ic_swap_vert.png'),'標籤排序')} {/*標籤移除*/} {this.createSettingItem(MORE_MENU.Remove_Key,require('../../../res/images/ic_remove.png'),'標籤移除')} {/*=============設置Section=============*/} 設置 {/*自定義主題*/} {this.createSettingItem(MORE_MENU.Custom_Theme,require('../../../res/images/ic_view_quilt.png'),'自定義主題')} {/*展示自定義主題頁面*/} {this.renderCustomTheme} } } //區域三:定義頁面組件樣式區: const styles = StyleSheet.create({ itemInfoItemStyle:{ flexDirection:'row', justifyContent:'space-between', alignItems:'center', padding:10, height:76, backgroundColor:'white' }, groupTitleStyle:{ marginLeft:10, marginTop:15, marginBottom:6, color:'gray' } });在上面的代碼中,我們可以看到一個頁面組件的全貌,它大致分為三個區域:引用區域定義組件區域定義樣式區域下面兩個區域在上一節已經介紹過。第一個區域,引用區域一般寫在組件文件的開頭,在這裡一般是需要引入該組件需要的其他組件或者常量。現在看一下該組件的render函數,它返回了用來包裹整個頁面的View組件,該組件有兩個子組件NavigationBar組件(導航欄),傳入了兩個props:title和style。ScrollView組件,包裹了項目信息Cell的View組件,分割線,項目Cell的View組件。需要注意的是,每個cell的組件都比較類似,所以在這裡將生成它的代碼封裝起來做一個函數來調用:createSettingItem(tag,icon,text){ return ViewUtil.createSettingItem(=>this.onClick(tag),icon,text,this.state.theme.styles.tabBarSelectedIcon,null); }可以看到這個函數傳入的參數有三個:用來作標記的tag,圖片 和標題文字。它的返回值通過調用ViewUtil組件的createSettingItem方法來實現。這個方法用於統一生成類似布局的cell。看一下這個函數的實現://ViewUtils.js static createSettingItem(callBack,icon,text,tintColor,expandableIcon){ //如果不傳入icon,則不顯示 let image = null; if (icon){ image = } return ( {image} {text} ) }這個函數有5個參數:callback:點擊cell時調用的方法,需要父組件傳入icon:cell左側的圖片text:cell標題tintColor:cell的主題顏色expandableIcon:cell右側的圖片(三角箭頭)因為在React Native中沒有特定的Button組件,所以實現組件的點擊都是通過被TouchableHighlight等可點擊組件包裹來實現的。常用的可以實現點擊效果的是View組件和Text組件。注意一下TouchableHighlight裡面傳入的兩個props:如果需要在點擊時顏色不變,可以將它的underlayColor設為transparent。可以把點擊時觸發的函數傳給它的onPress屬性。所以,如果該cell被點擊了,就會觸發傳入的callback。這個callback就等於當初傳過來的箭頭函數:ViewUtil.createSettingItem(=>this.onClick(tag),icon,text,this.state.theme.styles.tabBarSelectedIcon,null);該函數是在個人頁被調用的,用來實現點擊cell時的跳轉等操作。注意,在這個ViewUtils類中,我們可以定義很多常用的View組件,例如這種設置頁面的cell,導航欄上的返回按鈕等等。現在cell的實現講完了,下面講一下分割線和session的title。先來看一下分割線:它的樣式調用了GlobalStyles的cellBottomLineStyle。因為GlobalStyles是全局的樣式文件(單獨寫在了一個js文件中),可以使用它來專門管理一些常用的樣式。這樣一來,我們就不需要在不同頁面的組件頁面裡面重複聲明樣式常量了。我們看一下如何定義全局的樣式文件://GlobalStyles.js module.exports ={ //cell分割線樣式 cellBottomLineStyle: { height: 0.4, opacity:0.5, backgroundColor: 'darkgray', }, //cell背景色樣式 cell_container: { flex: 1, backgroundColor: 'white', padding: 10, marginLeft: 5, marginRight: 5, marginVertical: 3, borderColor: '#dddddd', borderStyle: null, borderWidth: 0.5, borderRadius: 2, shadowColor: 'gray', shadowOffset: {width:0.5, height: 0.5}, shadowOpacity: 0.4, shadowRadius: 1, elevation:2 }, //當前屏幕高度 window_height:height, //當前屏幕寬度 window_width:width, };因為使用了module.exports方法,在這裡定義的全局樣式可以在外部隨意使用。最後,Section Title的View就比較簡單了,就是一個帶有灰色文字的View組件。趨勢管理2.43 搭建app基本骨架:TabBar + NavigationBar做移動開發的朋友們應該比較了解,底部TabBar,頂部NavigationBar是移動app很主流的一個全局界面方案。然而在原生的React Native組件裡面,沒有將二者整合在一起的組件。幸運的是,有一個第三方組件比較好的將二者整合到了一起:react-native-tab-navigator.在它的主頁告訴我們其導入方式是在項目主目錄下執行:npm install react-native-tab-navigator —save命令。但是我建議使用yarn來引入所有第三方的組件:yarn add react-native-tab-navigator。因為使用npm命令安裝第三方組件的時候有時會出現問題。而且建議引入第三方組件的時候都是用yarn來操作,比較保險一點。在確認react-native-tab-navigator組件下載到了npm文件夾以後,就可以在項目中導入使用了。下面來看一下使用方法://導入 react-native-tab-navigator 組件,取名為 TabNavigator(隨意取名) import TabNavigator from 'react-native-tab-navigator'; //每個tab對應的唯一標識,可以在外部獲取 export const FLAG_TAB = { flag_popularTab: 'flag_popularTab', flag_trendingTab: 'flag_trendingTab', flag_favoriteTab: 'flag_favoriteTab', flag_myTab: 'flag_myTab' } export default class HomePage extends BaseComponent { constructor(props){ super(props); let selectedTab = this.props.selectedTab?this.props.selectedTab:FLAG_TAB.flag_popularTab this.state = { selectedTab:selectedTab, theme:this.props.theme } } _renderTab(Component, selectedTab, title, renderIcon) { return ( } renderSelectedIcon={ => } onPress={ => this.onSelected(selectedTab)}> ) } render { return ( {this._renderTab(PopularPage, FLAG_TAB.flag_popularTab, '最熱', require('../../../res/images/ic_polular.png'))} {this._renderTab(TrendingPage, FLAG_TAB.flag_trendingTab, '趨勢', require('../../../res/images/ic_trending.png'))} {this._renderTab(FavoritePage, FLAG_TAB.flag_favoriteTab, '收藏', require('../../../res/images/ic_favorite.png'))} {this._renderTab(MinePage, FLAG_TAB.flag_myTab, '我的', require('../../../res/images/ic_my.png'))} ) } }

本文由yidianzixun提供 原文連結

寫了 5860316篇文章,獲得 23313次喜歡
精彩推薦