文檔導航
- 0、項目結構
- 1、環境搭建
- 2、Index組件初始化
- 3、引入tabbar導航器
- 4、搭建antd-mobile-rn環境
- 5、創建Home組件
- 6、創建Swiper組件
- 7、創建HotCate組件
- 8、創建Top10組件
- 9、更改Swiper和Home組件
- 10、創建List組件
- 11、react-navigation
- 12、創建詳情頁
- 13、創建美食地圖頁
- 14、是否顯示地圖頁籤
- 15、拍照功能
- 16、項目發佈
- 17、項目源碼下載
項目結構
|-- 項目結構
|-- .gitignore
|-- app.json
|-- App.tsx
|-- babel.config.js
|-- images.d.ts
|-- package-lock.json
|-- package.json
|-- tsconfig.json
|-- yarn.lock
|-- .expo-shared
| |-- assets.json
|-- assets
| |-- icon.png
| |-- splash.png
| |-- images
| |-- cookbook-active.png
| |-- cookbook.png
| |-- location-active.png
| |-- location.png
| |-- menu-active.png
| |-- menu.png
| |-- more-active.png
| |-- more.png
| |-- search.png
| |-- swiper-1.png
| |-- swiper-2.jpeg
| |-- swiper-3.jpeg
|-- context
| |-- navigation.js
|-- mock
| |-- cookbook-category.json
| |-- cookbook-detail.json
| |-- cookbook-hotcate.json
| |-- cookbook-list-json.json
| |-- cookbook-list.json
| |-- mock.js
| |-- route.json
|-- pages
| |-- cate
| | |-- Cate.tsx
| | |-- style_cate.js
| |-- detail
| | |-- Detail.tsx
| | |-- style_detail.js
| |-- home
| | |-- Home.tsx
| | |-- HotCate.tsx
| | |-- style_home.js
| | |-- Swiper.tsx
| | |-- Top10.tsx
| |-- index
| | |-- Index.tsx
| | |-- styled_index.js
| | |-- style_index.js
| |-- map
| | |-- Map.tsx
| |-- more
| |-- More.tsx
|-- store
| |-- index.ts
|-- utils
|-- http.js
環境搭建
本項目是應用 ReactNative
,TypeScript
,Mobx
等技術開發的一個“美食大全”的項目,基本的環境搭建,大家參照本文基礎部分。
expo init rn-cookbooks
然後選擇 blank (TypeScript):
? Choose a template:
----- Managed workflow -----
blank a minimal app as clean as an empty canvas
❯ blank (TypeScript) same as blank but with TypeScript configuration
tabs several example screens and tabs using react-navigation
----- Bare workflow -----
minimal bare and minimal, just the essentials to get you started
minimal (TypeScript) same as minimal but with TypeScript configuration
啓動項目:
cd rn-cookbooks
yarn start
Index組件初始化
在根目錄下創建 pages/index
文件夾,在裏面創建一個 Index.tsx
文件,編輯內容:
// pages/index/Index.tsx
import React, { Component } from 'react'
import {
View,
Text,
StyleSheet
} from 'react-native'
interface Props {
}
interface State {
}
export default class Index extends Component<Props, State> {
constructor(props) {
super(props)
}
state: State = {
}
componentDidMount() {
}
render() {
return (
<View style={styles.container}>
<Text>
Index 組件內容
</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})
修改根目錄下的 App.tsx
:
import React from 'react'
import Index from './pages/index/Index'
export default function App() {
return (
<Index></Index>
)
}
引入tabbar導航器
在項目環境命令行裏安裝 tabbar 導航器,詳細內容可參見 react-native-tab-navigator 官網
yarn add react-native-tab-navigator -S
修改 index.tsx, 引入 tab-navigator 代碼:
import React, { Component } from 'react'
import TabNavigator from 'react-native-tab-navigator'
import {
View,
Text
} from 'react-native'
import {
Img
} from './styled_index'
import styles from './style_index'
import cookbook from '../../assets/images/cookbook.png'
import cookbookActive from '../../assets/images/cookbook-active.png'
import category from '../../assets/images/menu.png'
import categoryActive from '../../assets/images/menu-active.png'
import map from '../../assets/images/location.png'
import mapActive from '../../assets/images/location-active.png'
import more from '../../assets/images/more.png'
import moreActive from '../../assets/images/more-active.png'
interface Props {
}
interface State {
selectedTab: string
}
class Index extends Component<Props, State> {
constructor(props: Props) {
super(props)
}
state: State = {
selectedTab: 'home'
}
componentDidMount() {
}
render() {
return (
<TabNavigator
tabBarStyle={styles.tabBarStyle}
>
<TabNavigator.Item
selected={this.state.selectedTab === 'home'}
title="美食大全"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={cookbook} />}
renderSelectedIcon={() => <Img source={cookbookActive} />}
onPress={() => this.setState({ selectedTab: 'home' })}
>
{<View><Text>美食大全</Text></View>}
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'category'}
title="分類"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={category} />}
renderSelectedIcon={() => <Img source={categoryActive} />}
onPress={() => this.setState({ selectedTab: 'category' })}
>
{<View><Text>分類</Text></View>}
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'map'}
title="地圖"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={map} />}
renderSelectedIcon={() => <Img source={mapActive} />}
onPress={() => this.setState({ selectedTab: 'map' })}
>
{<View><Text>地圖</Text></View>}
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'more'}
title="更多"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={more} />}
renderSelectedIcon={() => <Img source={moreActive} />}
onPress={() => this.setState({ selectedTab: 'more' })}
>
{<View><Text>更多</Text></View>}
</TabNavigator.Item>
</TabNavigator>
)
}
}
export default Index
問題:
- ts 提示引入的 png 不能識別,飄紅了。解決方案是在項目跟目錄下創建
images.d.ts
文件,內容如下:
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
在 pages/index
下創建樣式文件:
下面使用兩種形式書寫樣式表,到時候自行開發按需選擇
- 安裝 styled-components 模塊
npm i styled-components -D
- 創建 style
d
_index.js, 內容如下:
import styled from 'styled-components'
const Img = styled.Image `
width: 25px;
height: 25px;
`
export {
Img
}
再創建一個樣式文件 style_index.js, 內容如下:
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
titleStyle: {
color: '#666'
},
tabBarStyle: {
paddingBottom: 34,
height: 80
},
selectedTitleStyle: {
color: '#000'
}
})
tabbar 的兼容處理
安裝 expo-device
npm i expo-device -S
修改 index.ts
, 根據您設備情況引入不同的樣式,此處只是測試性代碼,只做了iphone XR 和 其他非 “齊劉海” iPhone 手機:
// 載入模塊
import * as Device from 'expo-device'
// 在 TabNavigator 上修改 tabBarStyle
<TabNavigator
tabBarStyle={
Device.deviceName === 'iPhone Xʀ' ? styles.tabBarStyle : null
}
>
搭建antd-mobile-rn環境
在開始之前,推薦先學習 React 和 ES2015。使用了
babel
,試試用 ES2015 的寫法來提升編碼的愉悅感。確認 Node.js 已經升級到 v4.x 或以上。
1. 創建一個項目
可以是已有項目,或者是使用 create-react-native-app 新創建的空項目,你也可以從 官方示例 腳手架裏拷貝並修改
完整步驟請查看此處文檔: antd-mobile-sample/create-react-native-app
2. 安裝
npm install @ant-design/react-native --save
or
yarn add @ant-design/react-native
鏈接字體圖標
react-native link @ant-design/icons-react-native
3. 使用
按需加載
下面兩種方式都可以只加載用到的組件,選擇其中一種方式即可。
-
使用 babel-plugin-import(推薦)。
// .babelrc or babel-loader option { "plugins": [ ["import", { libraryName: "@ant-design/react-native" }] // 與 Web 平臺的區別是不需要設置 style ] }
然後改變從 @ant-design/react-native 引入模塊方式即可。
import { Button } from '@ant-design/react-native';
創建Home組件
在項目根目錄下創建
pages/home
文件夾,在這個文件夾下創建Home.tsx
文件,內容如下:
import React, { Component } from 'react'
import Swiper from './Swiper'
import HotCate from './HotCate'
interface Props {
}
interface State {
}
class Home extends Component<Props, State> {
render() {
return (
<>
<Swiper></Swiper>
<HotCate></HotCate>
</>
)
}
}
export default Home
此時在 Home.tsx 中引入 Swiper 和 HotCate 兩個組價。
創建Swiper組件
在根目錄下創建 utils 文件夾,在這個文件夾裏創建 http.js 文件,內容如下:
// utils/http.js
export const get = (url) => {
return fetch(url, {
method: 'get'
})
.then(response => response.json())
.then(result => {
return result.data
})
}
在 pages/home 文件夾裏再創建一個 Swiper.tsx 組件,內容如下:
import React, { Component } from 'react'
import { Carousel } from '@ant-design/react-native'
import { get } from '../../utils/http'
import {
View,
Image
} from 'react-native'
import styles from './style_home'
interface Props {
}
interface State {
list: Array<any>
}
class Swiper extends Component<Props, State> {
state = {
list: []
}
async componentDidMount() {
let list = await get('http://localhost:9000/api/swiper')
this.setState({
list
})
}
render() {
return (
<Carousel
style={styles.wrapper}
selectedIndex={0}
autoplay
infinite
>
{
this.state.list.slice(0, 5).map((value, index) => {
return (
<View
style={styles.containerHorizontal}
key={value.id}
>
<Image
source={{uri: value.img}}
style={styles.slideImg}
resizeMode='cover'
></Image>
</View>
)
})
}
</Carousel>
)
}
}
export default Swiper
在 page/home 文件裏創建 style_home.js 文件,編輯樣式如下:
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
// swiper
wrapper: {
height: 170
},
containerHorizontal: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
height: 170
},
slideImg: {
height: 170,
width: '100%'
},
})
創建HotCate組件
在 pages/home
文件夾裏構建 HotCate.tsx
文件,內容爲:
import React, { Component } from 'react'
import { Grid } from '@ant-design/react-native'
import { get } from '../../utils/http'
import styles from './style_home'
import {
View,
Text,
Image,
StyleSheet
} from 'react-native'
interface Props {
}
interface State {
hotCate: Array<object>
}
export default class HotCate extends Component<Props, State> {
state = {
hotCate: []
}
_renderItem(el, index) {
return (
<View
style={styles.container}
>
{el.img ? <Image source={{uri: el.img}} style={styles.gridImg}></Image> : null}
<Text style={styles.gridText}>{el.title}</Text>
</View>
)
}
async componentDidMount() {
let hotCate = await get('http://localhost:9000/api/hotcate')
// 補全最後一項數據
hotCate.push({
img: '',
title: '更多...'
})
this.setState({
hotCate
})
}
render() {
return (
<View>
<Grid
data={this.state.hotCate}
renderItem={this._renderItem}
hasLine={false}
></Grid>
</View>
)
}
}
修改 pages/home/style_home.js
文件,樣式如下:
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
// hotcate
container: {
paddingTop: 20,
paddingBottom: 10
},
gridContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
gridText: {
fontSize: 16,
margin: 6
},
gridImg: {
width: 70,
height: 70,
borderRadius: 5
},
})
創建Top10組件
Top10組件渲染的數據和Swiper組件可以使用同一個接口的數據,因此我們決定應用Mobx來管理這個數據。
安裝 Mobx 相關模塊
npm i mobx mobx-react -S
構建 store
在項目根目錄下創建 store 文件夾,在這個文件下創建 index.js 文件:
// store/index.js
import {
observable,
action,
computed
} from 'mobx'
class Store {
// swiper 與 top10 共享的數據
@observable
list = []
// swiper 數據過濾
@computed
get swiper() {
return this.list.slice(0, 5).map((value, index) => {
return {
img: value.img
}
})
}
// top10 數據過濾
@computed
get top10() {
return this.list.slice(0, 10).map((value, index) => {
return {
img: value.img,
all_click: value.all_click,
favorites: value.favorites,
name: value.name
}
})
}
// 裝載 list 數據
@action.bound
setList(data) {
this.list = data
}
}
export default new Store()
開始構建 Top.tsx 組件
在 pages/home
下創建 Top.tsx
文件:
// pages/home/Top.tsx
import React, { Component } from 'react'
import { Grid } from '@ant-design/react-native'
import { observer, inject } from 'mobx-react'
import {
View,
Text,
Image
} from 'react-native'
import styles from './style_home.js'
interface Props {
// store 作爲組件的 props
store?: any
}
interface State {
}
// 注入 store 與 將類變爲可觀察的對象
@inject('store')
@observer
class Top10 extends Component<Props, State> {
renderTop10(el, index) {
return (
<View style={styles.top10ItemContainer}>
<View style={styles.top10ImgContainer}>
<Image style={styles.Top10Img} source={{uri: el.img}}></Image>
</View>
<View style={styles.top10DesContainter}>
<Text style={styles.top10Titie}>{el.name}</Text>
<Text style={styles.Top10Desc}>{el.all_click} {el.favorites}</Text>
</View>
</View>
)
}
render() {
return (
<View style={styles.top10Container}>
<View style={styles.top10Head}>
<Text style={styles.top10HeadText}>精品好菜</Text>
</View>
<View style={styles.gridContainer}>
<Grid
data={this.props.store.top10}
columnNum={2}
hasLine={false}
renderItem={this.renderTop10}
/>
</View>
</View>
)
}
}
export default Top10
💣注意:expo-cli 構建的項目,默認 ts 配置不支持裝飾器,會給出如下警告:
Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.
需要修改項目根目錄下的 tsconfig.json,添加:
"experimentalDecorators": true
如果不能起作用,重新啓動VSCode即可。
添加 top10 樣式
// pages/home/style_home.js
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
// top10
top10Container: {
paddingBottom: 44,
backgroundColor: '#eee'
},
top10Head: {
height: 50,
paddingLeft: 10,
justifyContent: 'flex-end',
},
top10HeadText: {
fontSize: 18
},
top10ItemContainer: {
flex: 1,
paddingRight: 10
},
top10DesContainter: {
marginLeft: 10,
paddingTop: 10,
paddingBottom: 10,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff'
},
top10ImgContainer: {
paddingLeft: 10,
paddingTop: 10,
flex: 1
},
Top10Img: {
width: '100%',
height: '100%',
},
top10Titie: {
fontSize: 20
},
Top10Desc: {
color: '#666'
}
})
更改Swiper和Home組件
Swiper 組件和 Top10 組件共享了數據,因此在 store 構建好後,需要改造一下:
// pages/home/Swiper.tsx
import React, { Component } from 'react'
import { Carousel } from '@ant-design/react-native'
import { get } from '../../utils/http'
import { observer, inject } from 'mobx-react'
import {
View,
Image
} from 'react-native'
import styles from './style_home'
interface Props {
// store 作爲組件的 props
store?: any
}
interface State {
}
// 注入 store 與 將類變爲可觀察的對象
@inject('store')
@observer
class Swiper extends Component<Props, State> {
state = {
list: []
}
async componentDidMount() {
let list = await get('http://localhost:9000/api/swiper')
this.props.store.setList(list)
}
render() {
return (
<Carousel
style={styles.wrapper}
selectedIndex={0}
autoplay
infinite
>
{
this.props.store.swiper.map((value, index) => {
return (
<View
style={styles.containerHorizontal}
key={value.id}
>
<Image
source={{uri: value.img}}
style={styles.slideImg}
resizeMode='cover'
></Image>
</View>
)
})
}
</Carousel>
)
}
}
export default Swiper
改造 Home.tsx
組件
在 Home.tsx 組件引入 Top10 組件,同時添加 ScrollView 組件,實現頁面滾動效果。
// page/home/Home.tsx
import React, { Component } from 'react'
import { ScrollView } from 'react-native'
import Swiper from './Swiper'
import HotCate from './HotCate'
import Top10 from './Top10'
interface Props {
}
interface State {
}
class Home extends Component<Props, State> {
render() {
return (
<ScrollView>
<Swiper></Swiper>
<HotCate></HotCate>
<Top10></Top10>
</ScrollView>
)
}
}
export default Home
創建List組件
接下來構建另一個頁面,首先在 pages 目錄下創建 list 文件夾,在此文件夾裏創建 List.tsx 組件文件和 style_list.js 樣式文件。
List.tsx
// pages/list/List
import React, { Component, createRef } from 'react'
import {
inject,
observer
} from 'mobx-react'
import {
View,
Text,
Image,
FlatList
} from 'react-native'
import styles from './style_list'
interface Props {
store?: any
}
interface State {
// 記錄上拉加載更多的當前頁碼
curPage: number,
// 頁面顯示的數據
datalist: Array<object>,
// 控制下拉刷新的開關
refresh: boolean
}
let pageSize = 10
@inject('store')
@observer
export default class List extends Component<Props, State> {
constructor (
public props: Props,
public flatlist,
) {
super(props)
this.flatlist = createRef()
}
state = {
curPage: 1,
datalist: [],
refresh: false
}
// 渲染 Flatlist 組件數據
_renderItem(item) {
let {img, name, burdens, all_click, favorites} = item.item.data
return (
<View style={styles.listWrap}>
<View style={styles.imgWrap}>
<Image style={styles.image} source={{uri: img}}></Image>
</View>
<View style={styles.descWrap}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.subtitle} numberOfLines={1}>{burdens}</Text>
<Text style={styles.desc}>{all_click} {favorites}</Text>
</View>
</View>
)
}
// 處理用戶拉到底端的響應函數
_handleReachEnd() {
// 如果還有數據,一直加載
if (this.state.curPage < Math.ceil(this.props.store.list.length / pageSize)) {
this.setState((state) => {
return {
curPage: state.curPage + 1
}
}, () => {
this._loadData()
})
}
}
// 下拉刷新的響應函數
_handleRefresh() {
this.setState({
refresh: true
})
// 此處可以異步獲取後端接口數據,具體實現思路見上拉加載。
setTimeout(() => {
this.setState({
refresh: false
})
}, 2000)
}
// 加載數據
// 注:這裏的 key: value.id 由於模擬接口會出現重複的情況
_loadData() {
let data = this.props.store.list.slice(0, this.state.curPage * pageSize)
let flatListData = data.map((value, index) => ({
data: value,
key: value.id
})
)
this.setState({
datalist: flatListData
})
}
// 執行第一次數據加載
componentDidMount() {
setTimeout((params) => {
this._loadData()
}, 0)
}
render() {
return (
<FlatList
ref={this.flatlist}
renderItem={this._renderItem.bind(this)}
data={this.state.datalist}
refreshing={this.state.refresh}
onEndReached={this._handleReachEnd.bind(this)}
onEndReachedThreshold={1}
onRefresh={this._handleRefresh.bind(this)}
></FlatList>
)
}
}
style_list.js
樣式
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
listWrap: {
flexDirection: 'row',
padding: 10,
borderBottomWidth: 1,
borderStyle: 'solid',
borderBottomColor: '#eee'
},
imgWrap: {
width: 135,
paddingRight: 10
},
image: {
width: 115,
height: 75
},
descWrap: {
flex: 1
},
title: {
fontSize: 20,
lineHeight: 30
},
subtitle: {
fontSize: 16,
color: '#666',
lineHeight: 30,
overflow: 'hidden'
},
desc: {
fontSize: 12,
color: '#666'
}
})
react-navigation
本項目應用 React Navigation 構建路由系統。
安裝 React Navigation 環境
npm install @react-navigation/native
npm install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
npm install @react-navigation/stack
給App.tsx配置路由
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import { Provider } from 'mobx-react'
import store from './store/'
import Index from './pages/index/Index'
import List from './pages/list/List'
import Detail from './pages/detail/Detail'
const Stack = createStackNavigator()
export default function App() {
// 這裏配置了三個頁面
return (
<NavigationContainer>
<Provider store={store}>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#ee7530'
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
fontSize: 20
}
}}
>
<Stack.Screen
name="Index"
component={Index}
options={{
title: '首頁'
}}
/>
<Stack.Screen
name="List"
component={List}
options={{
title: '熱門'
}}
/>
<Stack.Screen
name="Detail"
component={Detail}
options={{
title: '詳情'
}}
/>
</Stack.Navigator>
</Provider>
</NavigationContainer>
)
}
創建 Context
爲了讓組件能收到路由的信息,這裏我們自己構建了一個 Context。
在根目錄下創建一個context目錄,在此目錄下創建一個 navigation.js
文件,內容如下:
// context/navigations.js
import { createContext } from 'react'
const navigationContext = createContext()
let { Provider, Consumer } = navigationContext
export {
navigationContext,
Provider,
Consumer
}
修改 index.tsx
1.解構出Provider
import { Provider } from '../../context/navigation'
2.通過Context 的Provider,將props遞交給後代組件
<Provider value={{...this.props}}>
<Home></Home>
</Provider>
<Provider value={{...this.props}}>
<List></List>
</Provider>
3.全部內容
// pages/index/Index.tsx
import React, { Component, ContextType } from 'react'
import TabNavigator from 'react-native-tab-navigator'
import * as Device from 'expo-device'
// 解構出 Provider
import { Provider } from '../../context/navigation'
import {
View,
Text
} from 'react-native'
import {
Img
} from './styled_index'
import styles from './style_index'
import cookbook from '../../assets/images/cookbook.png'
import cookbookActive from '../../assets/images/cookbook-active.png'
import category from '../../assets/images/menu.png'
import categoryActive from '../../assets/images/menu-active.png'
import map from '../../assets/images/location.png'
import mapActive from '../../assets/images/location-active.png'
import more from '../../assets/images/more.png'
import moreActive from '../../assets/images/more-active.png'
import Home from '../home/Home'
import List from '../list/List'
import Detail from '../detail/Detail'
interface Props {
navigation?: any
}
interface State {
selectedTab: string
}
class Index extends Component<Props, State> {
constructor(props: Props) {
super(props)
}
state: State = {
selectedTab: 'home'
}
componentDidMount() {
}
render() {
return (
<>
<TabNavigator
tabBarStyle={Device.deviceName === 'iPhone Xʀ' ? styles.tabBarStyle : null}
>
<TabNavigator.Item
selected={this.state.selectedTab === 'home'}
title="美食大全"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={cookbook} />}
renderSelectedIcon={() => <Img source={cookbookActive} />}
onPress={() => {
this.setState({ selectedTab: 'home' })
this.props.navigation.setOptions({ title: '美食大全' })
}}
>
{/* 通過Context 的Provider,將props遞交給後代組件 */}
<Provider value={{...this.props}}>
<Home></Home>
</Provider>
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'category'}
title="熱門"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={category} />}
renderSelectedIcon={() => <Img source={categoryActive} />}
onPress={
() => {
this.setState({ selectedTab: 'category' })
this.props.navigation.setOptions({ title: '熱門' })
}
}
>
{/* 通過Context 的Provider,將props遞交給後代組件 */}
<Provider value={{...this.props}}>
<List></List>
</Provider>
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'map'}
title="地圖"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={map} />}
renderSelectedIcon={() => <Img source={mapActive} />}
onPress={() => {
this.setState({ selectedTab: 'map' })
this.props.navigation.setOptions({ title: '地圖' })
}}
>
{<View><Text>地圖</Text></View>}
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'more'}
title="更多"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={more} />}
renderSelectedIcon={() => <Img source={moreActive} />}
onPress={() => {
this.setState({ selectedTab: 'more' })
this.props.navigation.setOptions({ title: '更多' })
}}
>
{<View><Text>更多</Text></View>}
</TabNavigator.Item>
</TabNavigator>
</>
)
}
}
export default Index
修改 home.tsx
1. 將路由信息傳給HotCate
<HotCate { ...this.props }></HotCate>
2.定義Props
interface Props {
navigation?: any
}
3.全部內容
// pages/home/Home.tsx
import React, { Component } from 'react'
import { ScrollView, StatusBar } from 'react-native'
import Swiper from './Swiper'
import HotCate from './HotCate'
import Top10 from './Top10'
interface Props {
navigation?: any
}
interface State {
}
class Home extends Component<Props, State> {
render() {
return (
<ScrollView>
<StatusBar backgroundColor="blue" barStyle="light-content" />
<Swiper></Swiper>
{/* 將路由信息傳給HotCate */}
<HotCate { ...this.props }></HotCate>
<Top10></Top10>
</ScrollView>
)
}
}
export default Home
修改 HotCate.tsx
1. 導入
import { Consumer } from '../../context/navigation'
2. 路由到“熱門”頁面
_onPress = (navigation) => {
return () => {
navigation.push('List')
}
}
<View style={styles.container}>
<Consumer>
{
({navigation}) => {
return (
<Grid
data={this.state.hotCate}
renderItem={this._renderItem}
hasLine={false}
onPress={this._onPress(navigation)}
></Grid>
)
}
}
</Consumer>
</View>
3. 全部代碼
// pages/home/HotCate.tsx
import React, { Component } from 'react'
import { Grid } from '@ant-design/react-native'
import { get } from '../../utils/http'
import { Consumer } from '../../context/navigation'
import styles from './style_home'
import {
View,
Text,
Image
} from 'react-native'
interface Props {
}
interface State {
hotCate: Array<object>
}
export default class HotCate extends Component<Props, State> {
state = {
hotCate: []
}
_renderItem(el, index) {
return (
<View
style={styles.gridContainer}
>
{el.img ? <Image source={{uri: el.img}} style={styles.gridImg}></Image> : null}
<Text style={styles.gridText}>{el.title}</Text>
</View>
)
}
_onPress = (navigation) => {
return () => {
navigation.push('List')
}
}
async componentDidMount() {
let hotCate = await get('http://localhost:9000/api/hotcate')
// 補全最後一項數據
hotCate.push({
img: '',
title: '更多...'
})
this.setState({
hotCate
})
}
render() {
return (
<View style={styles.container}>
<Consumer>
{
({navigation}) => {
return (
<Grid
data={this.state.hotCate}
renderItem={this._renderItem}
hasLine={false}
onPress={this._onPress(navigation)}
></Grid>
)
}
}
</Consumer>
</View>
)
}
}
修改 Top10.tsx
1. 通過 contextType 定義 context
import { navigationContext } from '../../context/navigation'
2. 導航到詳情頁,並傳參
import { navigationContext } from '../../context/navigation'
static contextType = navigationContext
_onPress = (e) => {
this.context.navigation.push('Detail', { name: e.name })
}
<Grid
data={this.props.store.top10}
columnNum={2}
hasLine={false}
renderItem={this._renderTop10}
onPress={this._onPress}
/>
3. 全部代碼
// pages/home/Top10.tsx
import React, { Component } from 'react'
import { Grid } from '@ant-design/react-native'
import { observer, inject } from 'mobx-react'
import { navigationContext } from '../../context/navigation'
import {
View,
Text,
Image
} from 'react-native'
import styles from './style_home.js'
interface Props {
// store 作爲組件的 props
store?: any
}
interface State {
}
// 注入 store 與 將類變爲可觀察的對象
@inject('store')
@observer
class Top10 extends Component<Props, State> {
static contextType = navigationContext
_renderTop10(el, index) {
return (
<View style={styles.top10ItemContainer}>
<View style={styles.top10ImgContainer}>
<Image style={styles.Top10Img} source={{uri: el.img}}></Image>
</View>
<View style={styles.top10DesContainter}>
<Text style={styles.top10Titie}>{el.name}</Text>
<Text style={styles.Top10Desc}>{el.all_click} {el.favorites}</Text>
</View>
</View>
)
}
_onPress = (e) => {
this.context.navigation.push('Detail', { name: e.name })
}
render() {
return (
<View style={styles.top10Container}>
<View style={styles.top10Head}>
<Text style={styles.top10HeadText}>精品好菜</Text>
</View>
<View style={styles.gridContainer}>
<Grid
data={this.props.store.top10}
columnNum={2}
hasLine={false}
renderItem={this._renderTop10}
onPress={this._onPress}
/>
</View>
</View>
)
}
}
export default Top10
修改 List.tsx
1. 載入路由相關模塊,實現路由到詳情頁的功能,主要代碼:
// 1. 載入Context
import { navigationContext } from '../../context/navigation'
// 2. 在 Props 裏定義 navigation
interface Props {
store?: any,
navigation?: any
}
// 3. 在類裏定義 contextType 靜態變量
static contextType = navigationContext
// 4. 在組件類裏定義路由跳轉響應方法
_onPress = (name: string) => {
return () => {
// 鑑於此頁面從 TabBar 和 首頁兩個入口進入
// 路由跳轉的方式也不同
if (this.context) {
// 從Tabbar進入
this.context.navigation.push('Detail', {name})
} else {
// 從首頁進入
this.props.navigation.push('Detail', {name})
}
}
}
// 5. 應用 TouchableOpacity 組件綁定路由跳轉事件
<TouchableOpacity
onPress={this._onPress(name)}
>
<View style={styles.listWrap}>
<View style={styles.imgWrap}>
<Image style={styles.image} source={{uri: img}}></Image>
</View>
<View style={styles.descWrap}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.subtitle} numberOfLines={1}>{burdens}</Text>
<Text style={styles.desc}>{all_click} {favorites}</Text>
</View>
</View>
</TouchableOpacity>
2. 全部代碼
import React, { Component, createRef } from 'react'
import { navigationContext } from '../../context/navigation'
import {
inject,
observer
} from 'mobx-react'
import {
View,
Text,
Image,
FlatList,
TouchableOpacity
} from 'react-native'
import styles from './style_list'
interface Props {
store?: any,
navigation?: any
}
interface State {
// 記錄上拉加載更多的當前頁碼
curPage: number,
// 頁面顯示的數據
datalist: Array<object>,
// 控制下拉刷新的開關
refresh: boolean
}
let pageSize = 10
@inject('store')
@observer
export default class List extends Component<Props, State> {
constructor (
public props: Props,
public flatlist,
) {
super(props)
this.flatlist = createRef()
}
state = {
curPage: 1,
datalist: [],
refresh: false
}
static contextType = navigationContext
_onPress = (name: string) => {
return () => {
if (this.context) {
this.context.navigation.push('Detail', {name})
} else {
this.props.navigation.push('Detail', {name})
}
}
}
// 渲染 Flatlist 組件數據
_renderItem(item) {
let {img, name, burdens, all_click, favorites} = item.item.data
return (
<TouchableOpacity
onPress={this._onPress(name)}
>
<View style={styles.listWrap}>
<View style={styles.imgWrap}>
<Image style={styles.image} source={{uri: img}}></Image>
</View>
<View style={styles.descWrap}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.subtitle} numberOfLines={1}>{burdens}</Text>
<Text style={styles.desc}>{all_click} {favorites}</Text>
</View>
</View>
</TouchableOpacity>
)
}
// 處理用戶拉到底端的響應函數
_handleReachEnd() {
// 如果還有數據,一直加載
// 加載更多,由於Mock數據問題,有ID重複問題
// if (this.state.curPage < Math.ceil(this.props.store.list.length / pageSize)) {
// console.log(this.state.curPage)
// this.setState((state) => {
// return {
// curPage: state.curPage + 1
// }
// }, () => {
// this._loadData()
// })
// }
}
// 下拉刷新的響應函數
_handleRefresh() {
this.setState({
refresh: true
})
// 此處可以異步獲取後端接口數據,具體實現思路見上拉加載。
setTimeout(() => {
this.setState({
refresh: false
})
}, 2000)
}
// 加載數據
_loadData() {
let data = this.props.store.list.slice(0, this.state.curPage * pageSize)
let flatListData = data.map((value, index) => ({
data: value,
key: value.id
})
)
this.setState({
datalist: flatListData
})
}
// 執行第一次數據加載
componentDidMount() {
setTimeout((params) => {
this._loadData()
}, 0)
}
render() {
return (
<View style={styles.container}>
<FlatList
ref={this.flatlist}
renderItem={this._renderItem.bind(this)}
data={this.state.datalist}
refreshing={this.state.refresh}
onEndReached={this._handleReachEnd.bind(this)}
onEndReachedThreshold={1}
onRefresh={this._handleRefresh.bind(this)}
></FlatList>
</View>
)
}
}
創建詳情頁
在路由信息定義好後,就可以構建詳情頁了。
Detail.tsx
// pages/detail/Detail.tsx
import React, { Component } from 'react'
import { get } from '../../utils/http'
import {
View,
ScrollView,
Text,
Image,
StatusBar,
TouchableOpacity,
Alert
} from 'react-native'
import styles from './style_detail'
interface Props {
navigation?: any,
route?: any
}
interface State {
detail: {}
}
export default class Detail extends Component<Props, State> {
state = {
detail: null
}
async componentDidMount() {
let result = await get('http://localhost:9000/api/detail')
this.setState({
detail: result
})
// 根據路由傳遞過來參數,修改本頁的 title
this.props.navigation.setOptions({ title: this.props.route.params.name })
}
render() {
let detail = this.state.detail
return (
<>
{
detail && (
<ScrollView>
<View style={styles.container}>
<StatusBar backgroundColor="blue" barStyle="light-content" />
<Image
source={{uri: detail.img}}
style={styles.mainImg}
></Image>
<View style={styles.mainInfo}>
<View>
<Text style={styles.mainInfoName}>{detail.name}</Text>
</View>
<View>
<Text style={styles.mainInfoSubTitle}>{detail.all_click}瀏覽/{detail.favorites}收藏</Text>
</View>
<TouchableOpacity
onPress={() => Alert.alert('已經收藏.')}
>
<View style={styles.mainInfoButtonWrap}>
<Text style={styles.mainInfoButton}>收藏</Text>
</View>
</TouchableOpacity>
</View>
<View style={styles.infoWrap}>
<View>
<Text style={styles.infoTitle}>心得</Text>
</View>
<View>
<Text style={styles.infoText}>
{detail.info}
</Text>
</View>
<View>
<Text style={[styles.infoTitle, {marginTop: 20}]}>做法</Text>
</View>
<View>
{
detail.makes.map((value) => {
return (
<View
key={value.num}
>
<View>
<Text style={styles.makesTitle}>{value.num} {value.info}</Text>
</View>
<View>
<Image
source={{uri: value.img}}
style={styles.makesImg}
></Image>
</View>
</View>
)
})
}
</View>
</View>
</View>
</ScrollView>
)
}
</>
)
}
}
style_detail.js 頁面樣式
// pages/detail/style_detail.js
import { StyleSheet } from 'react-native'
export default StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#eee',
paddingBottom: 34
},
// main
mainImg: {
width: '100%',
height: 250
},
mainInfo: {
height: 170,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff'
},
mainInfoName: {
fontSize: 24
},
mainInfoSubTitle: {
marginTop: 10,
fontSize: 12,
color: '#666'
},
// button
mainInfoButtonWrap: {
width: 140,
height: 40,
backgroundColor: '#df7b42',
marginTop: 20,
borderRadius: 6
},
mainInfoButton: {
textAlign: 'center',
lineHeight: 40,
color: '#fff',
fontSize: 16
},
// info
infoWrap: {
marginTop: 15,
backgroundColor: '#fff',
paddingTop: 25,
paddingLeft: 15,
paddingBottom: 25
},
infoTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 20
},
infoText: {
fontSize: 16,
lineHeight: 20,
paddingRight: 20
},
// makes
makesTitle: {
fontSize: 16,
paddingRight: 30
},
makesImg: {
width: 300,
height: 210,
margin: 20
}
})
創建美食地圖頁
“美食地圖” 主要應用的知識點是 WebView, 根據官網推薦,使用 react-native-webview 項目來實現在RN裏嵌入Web頁面。
模塊安裝
npm install --save react-native-webview
react-native link react-native-webview
Map.tsx 文件構建
在項目根目錄 pages 下創建目錄 map, 在 map 目錄下創建 Map.tsx 文件,文件內容如下:
import React, { Component } from 'react'
import { View } from 'react-native'
import { WebView } from 'react-native-webview'
interface Props {
}
interface State {
}
export default class Map extends Component<Props, State> {
state = {}
render() {
return (
<View
style={{
width: '100%',
flex: 1
}}
>
<WebView
source={{ uri: 'https://map.baidu.com/search/%E7%BE%8E%E9%A3%9F/@12959238.56,4825347.47,12z?querytype=s&da_src=shareurl&wd=%E7%BE%8E%E9%A3%9F&c=131&src=0&pn=0&sug=0&l=12&b=(12905478.56,4795011.47;13012998.56,4855683.47)&from=webmap&biz_forward=%7B%22scaler%22:2,%22styles%22:%22pl%22%7D&device_ratio=2' }}
style={{
width: '100%',
height: '100%'
}}
/>
</View>
)
}
}
是否顯示地圖頁籤
更多頁面實現了兩個功能:
1、是否顯示地圖頁籤
2、拍照功能
是否顯示地圖頁籤
更新 store
添加了 isShow 屬性,和 setVisible 方法。
// store/index.js
import {
observable,
action,
computed
} from 'mobx'
class Store {
// swiper 與 top10 共享的數據
@observable
list = []
// 定義是否顯示地圖按鈕
@observable
isShow = true
// swiper 數據過濾
@computed
get swiper() {
return this.list.slice(0, 5).map((value, index) => {
return {
img: value.img
}
})
}
// top10 數據過濾
@computed
get top10() {
return this.list.slice(0, 10).map((value, index) => {
return {
img: value.img,
all_click: value.all_click,
favorites: value.favorites,
name: value.name
}
})
}
// 裝載 list 數據
@action.bound
setList(data) {
this.list = data
}
// 修改是否顯示地圖按鈕
@action.bound
setVisible(status) {
this.isShow = status
}
}
export default new Store()
添加 More.tsx 文件
在根目錄pages下創建 more 文件夾,再創建 More.tsx 文件,內容如下
// pages/more/More.tsx
import React, { Component } from 'react'
import { View, Text, Switch, AsyncStorage } from 'react-native'
import { observer, inject } from 'mobx-react'
interface Props {
store?: any
}
interface State {
}
@inject('store')
@observer
export default class Profile extends Component<Props, State> {
state = {
}
async componentDidMount() {
}
render() {
return (
<View>
<View style={{
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'flex-start',
padding: 20
}}>
<View style={{
height: 30,
justifyContent: 'center',
alignItems: 'center'
}}>
<Text>是否顯示地圖:</Text>
</View>
<Switch
value={this.props.store.isShow}
onValueChange={(value) => {
this.props.store.setVisible(value)
AsyncStorage.setItem('isShow', value.toString())
}}
></Switch>
</View>
</View>
)
}
}
修改 pages/index/Index.tsx 文件
1、修改代碼的要點
// 定義 store
interface Props {
navigation?: any
store?: any
}
// 記錄用戶緩存
async componentDidMount() {
let isShow = await AsyncStorage.getItem('isShow')
this.props.store.setVisible(JSON.parse(isShow))
}
// 在 tabbar 裏修改
{
this.props.store.isShow
? (
<TabNavigator.Item
selected={this.state.selectedTab === 'map'}
title="地圖"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={map} />}
renderSelectedIcon={() => <Img source={mapActive} />}
onPress={() => {
this.setState({ selectedTab: 'map' })
this.props.navigation.setOptions({ title: '地圖' })
}}
>
<Map></Map>
</TabNavigator.Item>
)
: null
}
2. 全部代碼
// pages/index/Index.tsx
import React, { Component, ContextType } from 'react'
import TabNavigator from 'react-native-tab-navigator'
import * as Device from 'expo-device'
import { observer, inject } from 'mobx-react'
import { Provider } from '../../context/navigation'
import {
View,
Text,
AsyncStorage
} from 'react-native'
import {
Img
} from './styled_index'
import styles from './style_index'
import cookbook from '../../assets/images/cookbook.png'
import cookbookActive from '../../assets/images/cookbook-active.png'
import category from '../../assets/images/menu.png'
import categoryActive from '../../assets/images/menu-active.png'
import map from '../../assets/images/location.png'
import mapActive from '../../assets/images/location-active.png'
import more from '../../assets/images/more.png'
import moreActive from '../../assets/images/more-active.png'
import Home from '../home/Home'
import List from '../list/List'
import Map from '../map/Map'
import More from '../more/More'
interface Props {
navigation?: any
store?: any
}
interface State {
selectedTab: string
}
@inject('store')
@observer
class Index extends Component<Props, State> {
constructor(props: Props) {
super(props)
}
state: State = {
selectedTab: 'home'
}
async componentDidMount() {
let isShow = await AsyncStorage.getItem('isShow')
this.props.store.setVisible(JSON.parse(isShow))
}
render() {
return (
<>
<TabNavigator
tabBarStyle={Device.deviceName === 'iPhone Xʀ' ? styles.tabBarStyle : null}
>
<TabNavigator.Item
selected={this.state.selectedTab === 'home'}
title="美食大全"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={cookbook} />}
renderSelectedIcon={() => <Img source={cookbookActive} />}
onPress={() => {
this.setState({ selectedTab: 'home' })
this.props.navigation.setOptions({ title: '美食大全' })
}}
>
<Provider value={{...this.props}}>
<Home></Home>
</Provider>
</TabNavigator.Item>
<TabNavigator.Item
selected={this.state.selectedTab === 'category'}
title="熱門"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={category} />}
renderSelectedIcon={() => <Img source={categoryActive} />}
onPress={
() => {
this.setState({ selectedTab: 'category' })
this.props.navigation.setOptions({ title: '熱門' })
}
}
>
<Provider value={{...this.props}}>
<List></List>
</Provider>
</TabNavigator.Item>
{
this.props.store.isShow
? (
<TabNavigator.Item
selected={this.state.selectedTab === 'map'}
title="地圖"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={map} />}
renderSelectedIcon={() => <Img source={mapActive} />}
onPress={() => {
this.setState({ selectedTab: 'map' })
this.props.navigation.setOptions({ title: '地圖' })
}}
>
<Map></Map>
</TabNavigator.Item>
)
: null
}
<TabNavigator.Item
selected={this.state.selectedTab === 'more'}
title="更多"
titleStyle={styles.titleStyle}
selectedTitleStyle={styles.selectedTitleStyle}
renderIcon={() => <Img source={more} />}
renderSelectedIcon={() => <Img source={moreActive} />}
onPress={() => {
this.setState({ selectedTab: 'more' })
this.props.navigation.setOptions({ title: '更多' })
}}
>
<More></More>
</TabNavigator.Item>
</TabNavigator>
</>
)
}
}
export default Index
拍照功能
更多頁面實現了兩個功能:
1、是否顯示地圖頁籤
2、拍照功能
安裝模塊
npm install expo-camera -S
改寫 More.tsx 代碼
以下代碼是 拍照 和 切換顯示地圖按鈕 的全部代碼。
import React, { Component } from 'react'
import { View, Text, Switch, AsyncStorage, TouchableOpacity, Image } from 'react-native'
import { observer, inject } from 'mobx-react'
import * as Permissions from 'expo-permissions'
import { Camera } from 'expo-camera'
interface Props {
store?: any
}
interface State {
hasCameraPermission: boolean
type: boolean
isTakePic: boolean,
picUri: string
}
@inject('store')
@observer
export default class Profile extends Component<Props, State> {
camera = null
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
isTakePic: false,
picUri: 'http://placehold.it/240x180'
}
async componentDidMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({
hasCameraPermission: status === 'granted'
})
}
showTakePicScene() {
this.setState({
isTakePic: true
})
}
async takePicture() {
let result = await this.camera.takePictureAsync()
this.setState({
isTakePic: false,
picUri: result.uri
})
}
render() {
return (
<>
{
this.state.isTakePic
? (
<Camera
style={{ flex: 1 }}
type={this.state.type}
ref={ref => {
this.camera = ref
}}
>
<TouchableOpacity
onPress={this.takePicture.bind(this)}
>
<View style={{
marginLeft: 20,
width: 80,
height: 40,
backgroundColor: '#f9efd4',
justifyContent: 'center',
alignItems: 'center'
}}>
<Text>拍照</Text>
</View>
</TouchableOpacity>
</Camera>
)
: (
<View>
<View style={{
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'flex-start',
padding: 20
}}>
<View style={{
height: 30,
justifyContent: 'center',
alignItems: 'center'
}}>
<Text>是否顯示地圖:</Text>
</View>
<Switch
value={this.props.store.isShow}
onValueChange={(value) => {
this.props.store.setVisible(value)
AsyncStorage.setItem('isShow', value.toString())
}}
></Switch>
</View>
<TouchableOpacity
onPress={this.showTakePicScene.bind(this)}
>
<View style={{
marginLeft: 20,
width: 80,
height: 40,
backgroundColor: '#df7b42',
justifyContent: 'center',
alignItems: 'center'
}}>
<Text style={{color: '#fff'}}>拍照</Text>
</View>
</TouchableOpacity>
<View>
<Image style={{marginLeft: 20, marginTop: 20, width: 240, height: 180}} source={{uri: this.state.picUri}}></Image>
</View>
</View>
)
}
</>
)
}
}
項目發佈
本項目發佈利用expo發佈功能,詳細可參考 構建獨立的應用程序
安裝 Expo CLI
此步驟已經完成。
配置 app.json
{
"expo": {
"name": "rn-cookbooks",
"slug": "rn-cookbooks",
"privacy": "public",
"sdkVersion": "36.0.0",
"platforms": [
"ios",
"android",
"web"
],
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"bundleIdentifier": "com.qianfeng.felixlu",
"buildNumber": "1.0.0"
},
"android": {
"package": "com.qianfeng.felixlu",
"versionCode": 1
}
}
}
開始Build
expo build:android
或:
expo build:ios
build 過程監測
輸入以上命令後,會在控制檯看到下邊信息:
Build started, it may take a few minutes to complete.
You can check the queue length at https://expo.io/turtle-status
You can monitor the build at
https://expo.io/dashboard/felixlurt/builds/15b2ae11-c98d-48dc-879e-9ff05fb0b9f1
可以通過訪問 https://expo.io/dashboard/felixlurt/builds/15b2ae11-c98d-48dc-879e-9ff05fb0b9f1
來監控build過程。(注意鏈接是終端打印的,這個鏈接只是個示例)
build 成功後,點擊 “Download” 按鈕即可下載打完的APP安裝包了。
注:iOS 需要有開發者賬號,沒有賬號的童鞋建議運行
expo build:android
進行試驗
項目源碼下載
👉點擊下載 👈
🎉感謝