Although React Native is no longer a popular cross-platform technology, you can still see that many companies and individuals are using it. Flutter is good for Flutter, and RN also has its own place. As for which is better, we have not done it. Many comparisons.
Recently, I upgraded the book "React Native Mobile Development Practical Combat" before the upgrade. The projects in the book have the function of city switching, and the effect is shown in the figure below.
在这里插入图片描述
As you can see, this city selection page is very conventional, including the current location city and city list. On the right side, you can quickly locate it through the SlideBar. In addition, this component also supports the search function.

First, let's take a look at the city list. For this function, we can use the SectionList component, because we can use the ListHeaderComponent property of the SectionList to implement the current positioning layout, and the letter index effect on the right needs to use the scrollToLocation() function of the SectionList, as shown below Show.

const _scrollTo = (index, letter) => {
    listViewRef?.current?.scrollToLocation({itemIndex: 0, sectionIndex: index});
  };

The search function is even simpler, just use the FlatList component to display, here you can also use the list.map loop to develop the list function.
CitySelectScreen.js

import React, {useState, useEffect} from 'react';
import {
  View,
  Text,
  TextInput,
  StyleSheet,
  TouchableOpacity,
  Keyboard,
} from 'react-native';
import PropTypes from 'prop-types';

import {CityList} from './components';
import apiRequest from '../../api';
import Header from '../../common/Header/Header';

const CitySelectScreen = ({location = '上海市', navigation}) => {
  let inputRef = null;
  const [cities, setCities] = useState([]);
  const [currentCityList, setCurrentCityList] = useState({});
  const [isFocused, setIsFocused] = useState(false);
  const [result, setResult] = useState([]);
  const [keyword, setKeyword] = useState('');

  useEffect(() => {
    getCities();
  }, []);

  const onChangeText = e => {
    setKeyword(e);
  };

  const onSelectCity = city => {
    setTimeout(() => {
      navigation.navigate('SelectCinemaScreen', {
        title: city.CITY_NAME,
        CITY_CD: city.CITY_CD,
      });
    }, 200);
    setResult([]);
  };

  const searchSubmit = () => {
    if (isFocused) {
      inputRef.blur();
      setIsFocused(false);
      setResult([]);
      Keyboard.dismiss();
    } else {
      setIsFocused(true);
      inputRef.focus();
    }
  };

  const getCities = async () => {
    let url = 'https://prd-api.cgv.com.cn/product/areas/that/group';
    const res = await apiRequest.get(url);
    setCities(res);
  };

  const searchCities = async () => {
    let url = 'https://prd-api.cgv.com.cn/product/areas/that/group';
    const params = {condition: keyword};
    const res = await apiRequest.get(url, params);
    console.log(res[0].data);
    setResult(res[0].data);
  };

  const onCurrentPress = (name = '上海市') => {
    cities.map(item =>
      item.data.map(val => {
        if (val.CITY_NAME === name) {
          onSelectCity(val);
          return null;
        }
      }),
    );
  };

  const renderSearchView = () => {
    return (
      <View style={styles.searchView}>
        <TextInput
          style={{flex: 1}}
          assignRef={c => {
            inputRef = c;
          }}
          onChangeText={onChangeText}
          returnKeyType="search"
          onSubmitEditing={() => {
            if (keyword) {
              searchCities();
            }
          }}
          onFocus={() => setIsFocused(true)}
          placeholder="输入城市名或拼音"
        />
        <TouchableOpacity onPress={() => searchSubmit(isFocused)}>
          <Text style={styles.searchTxt}>{!isFocused ? '搜索' : '取消'}</Text>
        </TouchableOpacity>
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <Header title={'选择城市'} />
      {renderSearchView()}
      <View style={{flex: 1}}>
        {(!isFocused && !keyword && keyword.length < 1) || !isFocused ? (
          <CityList
            onCurrentCityPress={onCurrentPress}
            onSelectCity={onSelectCity}
            currentCity={location}
            allCityList={cities}
            currentCityList={currentCityList}
          />
        ) : (
          <SearchResult list={result} onSelectCity={onSelectCity} />
        )}
      </View>
    </View>
  );
};

const SearchResult = ({list, onSelectCity}) => {
  return (
    <View style={{marginTop: 10}}>
      {list.map((item, index) => (
        <TouchableOpacity
          activeOpacity={0.7}
          key={index.toString()}
          style={styles.rowView}
          onPress={() => {
            onSelectCity(item);
          }}>
          <View style={styles.rowdata}>
            <Text type="subheading">{item.CITY_NAME}</Text>
          </View>
        </TouchableOpacity>
      ))}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  searchView: {
    height: 48,
    flexDirection: 'row',
    backgroundColor: '#fff',
    alignItems: 'center',
    paddingLeft: 10,
    paddingRight: 10,
  },
  searchTxt: {
    color: '#FC5869',
    marginRight: 5,
    fontSize: 16,
  },
  rowView: {
    backgroundColor: '#fff',
    height: 44,
    paddingLeft: 13,
    justifyContent: 'center',
  },
  leftIcon: {
    width: 28,
    height: 28,
    paddingLeft: 13,
  },
});

CitySelectScreen.propTypes = {
  cities: PropTypes.array,
  getCities: PropTypes.func,
};

export default CitySelectScreen;

The CityList.js code is as follows:

import React, {useEffect, useRef} from 'react';
import {
  View,
  SectionList,
  TouchableOpacity,
  StyleSheet,
  Text,
  Image,
  Dimensions,
} from 'react-native';
import PropTypes from 'prop-types';
import ItemSeparatorComponent from '../../../../common/ItemSeparator';
import location from '../../../../assets/images/home/location.png';
import refresh from '../../../../assets/images/home/refresh.png';

const {width} = Dimensions.get('window');

const propTypes = {
  keyword: PropTypes.string,
  onChangeTextKeyword: PropTypes.func,
};

const defaultProps = {};

const CityList = ({
  onSelectCity,
  allCityList = [],
  currentCity,
  onCurrentCityPress,
  position: _position,
}) => {
  const listViewRef = useRef(null);

  useEffect(() => {
    console.log(allCityList);
  }, []);

  const city =
    currentCity && currentCity.city
      ? currentCity.city
      : '定位失败,请手动选择城市';

  const _cityNameClick = cityJson => {
    onSelectCity(cityJson);
  };

  const getLocation = async () => {
    // await PermissionsAndroid.request(
    //   PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    // );
    // const res = await Geocode.reverse({
    //   latitude: '29.604451007313266',
    //   longitude: '106.52727499999997',
    // });
    // _position(res);
  };

  const CityHeader = props => {
    const {currentCity = '上海', onCurrentCityPress} = props;
    return (
      <View>
        <View style={styles.sectionTitle}>
          <Text style={{fontSize: 15}}>当前城市</Text>
        </View>
        <TouchableOpacity
          activeOpacity={0.7}
          onPress={() => onCurrentCityPress(currentCity)}
          style={styles.headerContainer}>
          <View style={styles.headerLeft}>
            <Image source={location} style={{width: 20, height: 20}} />
            <Text type="subheading" style={{marginLeft: 2}}>
              {currentCity}
            </Text>
          </View>
          <TouchableOpacity activeOpacity={0.7} onPress={() => getLocation()}>
            <Image source={refresh} style={{width: 20, height: 20}} />
          </TouchableOpacity>
        </TouchableOpacity>
      </View>
    );
  };

  const _renderListRow = (cityJson, rowId) => {
    return (
      <TouchableOpacity
        activeOpacity={0.7}
        key={`list_item_${cityJson.item.CITI_CD}`}
        style={styles.rowView}
        onPress={() => _cityNameClick(cityJson.item)}>
        <View style={styles.rowData}>
          <Text type="subheading">{cityJson.item.CITY_NAME}</Text>
        </View>
      </TouchableOpacity>
    );
  };

  const _scrollTo = (index, letter) => {
    listViewRef?.current?.scrollToLocation({itemIndex: 0, sectionIndex: index});
  };

  const _renderRightLetters = (letter, index) => {
    return (
      <TouchableOpacity
        key={`letter_idx_${index}`}
        activeOpacity={0.6}
        onPress={() => {
          _scrollTo(index, letter);
        }}>
        <View style={styles.letter}>
          <Text>{letter}</Text>
        </View>
      </TouchableOpacity>
    );
  };

  return (
    <View style={styles.container}>
      <SectionList
        getItemLayout={(param, index) => ({
          length: 44,
          offset: 44 * index,
          index,
        })}
        ListHeaderComponent={
          <CityHeader
            currentCity={city}
            onCurrentCityPress={onCurrentCityPress}
          />
        }
        ref={listViewRef}
        sections={allCityList}
        keyExtractor={(item, index) => index.toString()}
        renderItem={_renderListRow}
        ItemSeparatorComponent={() => <ItemSeparatorComponent />}
        renderSectionHeader={({section: {name}}) => (
          <View style={styles.sectionTitle}>
            <Text style={{fontSize: 15}}>{name}</Text>
          </View>
        )}
        stickySectionHeadersEnabled={true}
      />
      <View style={styles.letterSpace}>
        {allCityList.map((item, index) =>
          _renderRightLetters(item.name, index),
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F4F4F4',
  },
  sectionTitle: {
    paddingVertical: 5,
    paddingLeft: 12,
    backgroundColor: '#F3F4F5',
  },
  iconContainer: {
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
  },
  leftIconContainer: {
    marginEnd: 12,
  },
  rightIconContainer: {
    marginStart: 8,
  },
  headerView: {
    width: width,
    display: 'flex',
    flexDirection: 'row',
    position: 'relative',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  leftIcon: {
    width: 15,
    height: 13,
    marginLeft: 15,
    marginRight: 5,
  },
  rowView: {
    paddingLeft: 12,
    backgroundColor: '#fff',
  },
  rowData: {
    width: width,
    height: 44,
    justifyContent: 'center',
  },
  headerContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    backgroundColor: '#fff',
    paddingHorizontal: 12,
    height: 44,
  },
  headerLeft: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  letter: {
    marginBottom: 3,
  },
  letterSpace: {
    position: 'absolute',
    right: 4,
    bottom: 0,
    top: 0,
    justifyContent: 'center',
  },
});

CityList.propTypes = propTypes;
CityList.defaultProps = defaultProps;

export default CityList;

In addition, our network request uses Axios. For related content, please refer to the introduction of my previous article: React Native uses axios for network requests

Source code: https://github.com/xiangzhihong/rn_city


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》