配置导航

本项目应用 React Navigation 构建路由系统。有关 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>
    )
  }
}

results matching ""

    No results matching ""