先看demo

寫在最前面
從開始學RN到現(xiàn)在大概有2個星期天左右了,這里先記錄一下,也算個小階段總結(jié)。就目前感覺,RN的優(yōu)勢和劣勢都很明顯;
- 優(yōu)勢
- RN是混合開發(fā)一份代碼多端使用
- 代碼與前端相似,Web轉(zhuǎn)RN比較輕松
- 劣勢
- RN組件由多個第三方維護,更新不可控,會有停更不兼容的風險
- 會由于RN版本,組件版本,Xcode版本的不同而隨機組合成各種坑(這點很令人煩躁,大部分時間都浪費在這)
- 多端兼容適配成本大,而且會隨著項目規(guī)模而增大難度,到一定程度開發(fā)成本會比原生的高,如Airbnb宣布放棄使用RN,回歸原生技術(shù)
正文
一 環(huán)境安裝
官網(wǎng)說的很詳細,按照官網(wǎng)的步驟基本沒問題,就不多贅述
官網(wǎng)地址: https://reactnative.cn/docs/getting-started.html
二 熟悉RN
創(chuàng)建Q項目,并用iOS模擬器運行起來
react-native init q
cd q
react-native run-ios
項目內(nèi)容如下:
- android:Android工程文件
- ios: iOS工程文件
- node_modules: React-native組件依賴存放的文件夾
- package.json: 依賴配置json, 類似于cocoPods中的“Podfile”
- index.js: 項目入口
...

先看index.js, import 是引入文件,AppRegistry.registerComponent(appName, () => App);整個的意思就是將工程目錄的App.js注冊成組件并引入,所以一開始顯示的即App.js里面的內(nèi)容
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
App.js文件里面大致可以分成三部分
- 引入組件
- 搭建UI
- 樣式
有過前端開發(fā)經(jīng)驗的朋友對View,Text,ScrollView這些并不陌生,在React-native中,所有組件都要單獨引入,所有組件及作用可看官方文檔
import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
} from 'react-native';
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
這里部分內(nèi)容是畫UI,基本上和html沒差多少,都是用各種組件的堆砌。學過web或者小程序之類的看起來會很簡單,沒學過的話,建議選去學學最基本的html + css
const App: () => React$Node = () => {
return (
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
.....
.....
</SafeAreaView>
</>
);
};
export default App;
這里是各種樣式,大部分都是沿用css的,看到這里大概感覺到React-native其實就是前段代碼換個殼,對于有前段知識的人學起來應(yīng)該會很輕松,沒有相關(guān)知識的話建議還是先去學學基礎(chǔ)的再來搞React-native
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
.....
.....
});
三 嘗試Demo
有這些了解后,可以試著做一個簡單的列表頁面

1.導入組件,需要的組件大概有這些,重點是FlatList列表組件
import React, { Component } from "react";
import { Image, FlatList, StyleSheet, Text, View } from "react-native";
2.導出默認類App擴展組件,包括住下面的其他代碼
export default class App extends Component {
}
3.創(chuàng)建個長度是8的data數(shù)組,后面可給FlatList賦值用
constructor(props) {
super(props);
this.state = {
data: [{}, {}, {}, {}, {}, {}, {}, {}],
};
}
4.RN的render()函數(shù)實際上跟iOS的ViewDidLoad()函數(shù)相似,返回的就是具體的內(nèi)容,內(nèi)容很固定,只能是一個組件,這里我返回的是FlatList,字段說明如下
- data:需要給予一個數(shù)組,數(shù)組長度與列表item對應(yīng)
- style:列表樣式
- renderItem:Item渲染,這里是直接調(diào)用
renderMovie渲染 - keyExtractor:設(shè)置每個item唯一的key,類似于索引
render() {
return (
<FlatList
data={this.state.data}
style={styles.list}
renderItem={this.renderMovie.bind(this)}
keyExtractor={item => item.id}
/>
);
}
5.通過renderMovie函數(shù)返回item的內(nèi)容,這里可以任意發(fā)揮
renderMovie({ item }) {
return (
<View style={styles.container}>
<Image
source={{ uri: 'https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=e4d6ea2325dda3cc0be4bf2639d25e3c/b64543a98226cffcb1f7cc0eb2014a90f703eaa9.jpg' }}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<View style={styles.titleWithout}>
<Text style={styles.title}>羅小黑戰(zhàn)記</Text>
<Text style={styles.tip}>中國巨屏</Text>
</View>
<Text style={styles.score}>貓眼評分<Text style={styles.grade}> 9.8 </Text></Text>
<Text style={styles.starring}>主演:羅小黑,羅小白</Text>
<Text style={styles.cinema}>今天129加音樂反映124場</Text>
</View>
<Text style={styles.buy}>購買</Text>
</View>
);
}
6.最后是樣式,其實這些隨意發(fā)揮即可
這樣簡單的一個頁面就完成了,完整代碼如下,可以直接copy替代原有內(nèi)容運行即可看到效果
import React, { Component } from "react";
import { Image, FlatList, StyleSheet, Text, View, TouchableOpacity } from "react-native";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
};
}
render() {
return (
<FlatList
data={this.state.data}
style={styles.list}
renderItem={this.renderMovie.bind(this)}
keyExtractor={item => item.id}
/>
);
}
renderMovie({ item }) {
return (
<View style={styles.container}>
<Image
source={{ uri: 'https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=e4d6ea2325dda3cc0be4bf2639d25e3c/b64543a98226cffcb1f7cc0eb2014a90f703eaa9.jpg' }}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<View style={styles.titleWithout}>
<Text style={styles.title}>羅小黑戰(zhàn)記</Text>
<Text style={styles.tip}>中國巨屏</Text>
</View>
<Text style={styles.score}>貓眼評分<Text style={styles.grade}> 9.8 </Text></Text>
<Text style={styles.starring}>主演:羅小黑,羅小白</Text>
<Text style={styles.cinema}>今天129加音樂反映124場</Text>
</View>
<Text style={styles.buy}>購買</Text>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff"
},
header: {
height: 20,
marginTop: 44,
},
rightContainer: {
flex: 1,
paddingLeft: 18,
},
titleWithout: {
flexDirection: "row",
fontWeight: "bold",
alignItems: "center",
},
title: {
fontSize: 14,
marginTop: 4,
lineHeight: 0,
},
tip: {
backgroundColor: "#999",
fontSize: 8,
textAlign: "center",
color: "#fff",
height: 14,
width: 40,
lineHeight: 14,
borderRadius: 2,
marginLeft: 4,
marginTop: 4,
},
score: {
paddingTop: 8,
fontSize: 12,
color: "#666",
},
starring: {
paddingTop: 4,
fontSize: 12,
color: "#666",
},
cinema: {
paddingTop: 4,
fontSize: 12,
color: "#666",
},
buy: {
fontSize: 12,
// color:'#333',
width: 46,
height: 24,
lineHeight: 24,
textAlign: "center",
backgroundColor: "#D44145",
color: "#fff",
borderRadius: 12,
marginRight: 20,
},
grade: {
color: "#DF8D7A",
},
year: {
textAlign: "center"
},
thumbnail: {
width: 68,
height: 94,
marginLeft: 20,
marginTop: 10,
marginBottom: 10,
},
list: {
paddingTop:40,
backgroundColor: "#F5FCFF"
},
headerOutline: {
backgroundColor: "#fff",
marginTop: 44,
},
headerInside: {
backgroundColor: "#f5f5f5",
flexDirection: "row",
justifyContent: 'space-between',
marginLeft: 20,
marginRight: 20,
marginBottom: 6,
paddingTop: 10,
paddingBottom: 4,
paddingLeft: 10,
paddingRight: 10,
},
trendIcon: {
width: 10,
height: 14,
marginLeft: 10,
marginTop: -1,
},
trendText: {
height: 22,
color: '#333',
fontWeight: "bold",
},
trendR: {
color: '#333',
fontSize: 10,
fontWeight: "bold",
height: 22,
},
trendRText: {
},
trendMoney: {
color: '#D24349',
},
});
二 Navigation與Tabbar
如圖,最終目的是創(chuàng)建一個帶Navigation,Tabbar的demo,可分為三個步驟
- 安裝組件
- 創(chuàng)建tabbar上的兩個跟根頁面和一個詳情頁面
- 修改
index.js入口

1.先安裝react-navigation組件
注:這里有個天坑,react-navigation4與3差距很大,現(xiàn)在網(wǎng)上的教程基本使用的都是react-navigation 3,這里我也是兜兜轉(zhuǎn)轉(zhuǎn)搞了許久才意識到的,大家都是初學者,建議安裝版本3
yarn add react-navigation@3.5.1
yarn add react-native-gesture-handler
這里可能會報這個錯
Error: Requiring unknown module "447". If you are sure the module exists, try restarting Metro. You may also want to run `yarn` or `npm install`.
這個錯原因很多,可以嘗試
npm install
react-native run-ios
或者
cd ios
pod install
cd ..
react-native run-ios
2.創(chuàng)建detailsScreen.js,settingScreen.js,navigation.js文件
detailsScreen.js內(nèi)容
import React from 'react';
import {
View,
Text,
Button,
Image,
} from 'react-native';
export default class detailsScreen extends React.Component {
render() {
return (
<View style={{flex:1, alignItems:'center',justifyContent: 'center'}}><Text>詳情頁</Text></View>
);
}
}
settingScreen.js內(nèi)容
import React from 'react';
import {
View,
Text,
Button,
Image,
} from 'react-native';
export default class settingScreen extends React.Component {
render() {
return (
<View style={{flex:1, alignItems:'center',justifyContent: 'center'}}><Text>設(shè)置頁</Text></View>
);
}
}
navigation.js內(nèi)容需要分步講解一下,首先引入所有需要的組件與頁面
import React from 'react';
import { Text } from 'react-native'
import HomeScreen from "./App";
import DetailsScreen from "./detailScreen";
import SettingScreen from "./settingScreen";
import {
createStackNavigator,
createAppContainer,
createBottomTabNavigator
} from 'react-navigation';
這里是聲明HomeStack,SettingsStack兩個組件;
createStackNavigator 提供APP屏幕之間切換的能力,它是以棧的形式還管理屏幕之間的切換,新切換到的屏幕會放在棧的頂部。
const HomeStack = createStackNavigator({
Home: { screen: HomeScreen, }
})
const SettingsStack = createStackNavigator({
Settings: { screen: SettingScreen },
})
這里聲明TabNavigator
createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig)相當于iOS里面的TabBarController,屏幕下方的標簽欄
- RouteConfigs(必選):路由配置對象是從路由名稱到路由配置的映射,告訴導航器該路由呈現(xiàn)什么。
- BottomTabNavigatorConfig(可選):配置導航器的路由(如:默認首屏,navigationOptions,paths等)樣式(如,轉(zhuǎn)場模式mode、頭部模式等)。
const TabNavigator = createBottomTabNavigator(
{
Home: { screen: HomeStack },
Settings: { screen: SettingsStack }
},
{
navigationOptions: () => ({
header: null
}),
defaultNavigationOptions: ({ navigation }) => ({
tabBarLabel: ({ tintColor}) => {
const { routeName } = navigation.state
switch (routeName) {
case 'Home':
return <Text style={{ color: tintColor, fontSize: 12 }}>{'首頁'}</Text>
case 'Settings':
return <Text style={{ color: tintColor, fontSize: 12 }}>{'設(shè)置'}</Text>
}
},
tabBarIcon: ({ focused, tintColor }) => {
let urld
const { routeName } = navigation.state
switch (routeName) {
case 'Home':
return <Image source={{ uri: 'https://static.easyicon.net/preview/119/1191814.gif' }} style={[{height: 20, width: 20}]}/>
case 'Settings':
return <Image source={{ uri: 'https://static.easyicon.net/preview/121/1215319.gif' }} style={[{height: 20, width: 20}]}/>
}
}
}),
tabBarOptions: {
inactiveTintColor: 'gray',
}
}
)
最后設(shè)置路由并返回
const AppStack = createStackNavigator({
Tabs: TabNavigator,
Details: { screen: DetailsScreen },
}, {
defaultNavigationOptions: () => ({
})
})
export default createAppContainer(AppStack)
完整代碼如下
import React from 'react';
import { Text,Image} from 'react-native'
import HomeScreen from "./App";
import DetailsScreen from "./detailScreen";
import SettingScreen from "./settingScreen";
import {
createStackNavigator,
createAppContainer,
createBottomTabNavigator
} from 'react-navigation';
const HomeStack = createStackNavigator({
Home: { screen: HomeScreen, }
})
const SettingsStack = createStackNavigator({
Settings: { screen: SettingScreen },
})
const TabNavigator = createBottomTabNavigator(
{
Home: { screen: HomeStack },
Settings: { screen: SettingsStack }
},
{
navigationOptions: () => ({
header: null
}),
defaultNavigationOptions: ({ navigation }) => ({
tabBarLabel: ({ tintColor}) => {
const { routeName } = navigation.state
switch (routeName) {
case 'Home':
return <Text style={{ color: tintColor, fontSize: 12 }}>{'首頁'}</Text>
case 'Settings':
return <Text style={{ color: tintColor, fontSize: 12 }}>{'設(shè)置'}</Text>
}
},
tabBarIcon: ({ focused, tintColor }) => {
let urld
const { routeName } = navigation.state
switch (routeName) {
case 'Home':
return <Image source={{ uri: 'https://static.easyicon.net/preview/119/1191814.gif' }} style={[{height: 20, width: 20}]}/>
case 'Settings':
return <Image source={{ uri: 'https://static.easyicon.net/preview/121/1215319.gif' }} style={[{height: 20, width: 20}]}/>
}
}
}),
tabBarOptions: {
inactiveTintColor: 'gray',
}
}
)
const AppStack = createStackNavigator({
Tabs: TabNavigator,
Details:DetailsScreen,
}, {
defaultNavigationOptions: () => ({
})
})
export default createAppContainer(AppStack)
3.修改index.js入口
這里僅僅只是把入口改為navigation.js
import {AppRegistry} from 'react-native';
import Nav from './navigation.js';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => Nav);
保存基本就能看到App的架子大概形成了

接下來要設(shè)置一下點擊事件,讓demo可以跳轉(zhuǎn)
先回到App.js頁面 設(shè)置首頁導航欄標題
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state
const onPressRightButtonFunc = params.openPublisher || function () { }
return {
title: '首頁',
}
}
引入TouchableOpacity設(shè)置點擊事件
import { TouchableOpacity } from "react-native";
...
...
renderMovie({ item }) {
const navigate = this.props.navigation;
return (
<TouchableOpacity activeOpacity={0.5} onPress={() => navigate.navigate('Details')} > //'Details'是之前在navigation.js聲明好的了
... //這里是之前item的UI代碼
</TouchableOpacity>
到這里基本已經(jīng)完成了這個demo,其他的都是一些重復的UI工作也不贅述了,這是稍微優(yōu)化過的代碼和詳情頁,看不懂的可以根據(jù)根據(jù)這源碼來。
這里我的源碼是將基本組件都下好,下載運行即可,因為比較大先上傳到百度云。
鏈接: https://pan.baidu.com/s/1854tyx1R_Bjz4A57xvxn1g 提取碼: kgmb
網(wǎng)上的其他demo對新人都很不友好,需要安裝各個組件再運行起來,各種報錯容易勸退新人
后記
初衷是想讓新手快速的入門制作一個demo,后面發(fā)現(xiàn)還是需要一點web經(jīng)驗的,內(nèi)容有點多,說得沒那么細致的地方請見諒。后續(xù)會一直持續(xù)更新這個demo;
如果覺得對這篇文章對您有一點幫助的話,歡迎關(guān)注,戳這里 → 蘆葦科技