React-Native 之 專案實戰(一)
前言
- 本文有配套視訊,可以酌情觀看。
- 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯絡我。
- 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關。
- 如文中內容對您造成不便,煩請聯絡 [email protected] 處理,謝謝。
- 轉載麻煩註明出處,謝謝。
原始碼託管到 github 上,需要原始碼的 點我下載,喜歡的話記得 Star,謝謝!
ES5轉ES6
- 關於ES6語法,建議大家可以看下阮老師的 ECMAScriot 6
- 快速瞭解的話,大家可以參考一下 es6語法快速上手
專案簡介
- 先來看下我們仿照的這款APP的效果:
- 從上圖中,我們可以看出複雜度並不大,但是時間關係我們儘量將所有的模組都做完,並完善細節。
譯註:
建議開啟 視訊 配合 文字 學習,以免有某些細節文中沒有提到,但以文中內容為準(根據反饋進行相應更新)
之所以選擇這款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 Source
為Images.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 端配置完畢。
- 接著,來配置 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 配置完畢。
目錄結構與命名規則
- 為了方便理解,我們這邊先不按照常規的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,
}
});
- 至此,主體框架搭建完畢。
自定義導航欄樣式
從效果圖中可以看出,導航欄的樣式都差不多,因為我們前面已經設定了 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 的樣式。
接下來我們來自定義一下 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,
}
});
- 從效果圖中可以看出,我們還少了上面的提示標題,這邊很簡單,我們也來快速完成一些
{/* 頂部提示 */}
<View style={styles.headerPromptStyle}>
<Text>根據每條折扣的點選進行統計,每5分鐘更新一次</Text>
</View>
樣式部分:
headerPromptStyle: {
height:44,
width:width,
backgroundColor:'rgba(239,239,239,0.5)',
justifyContent:'center',
alignItems:'center'
}
隱藏於顯示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);
}