You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无第三方依赖,自定义React Native Google Places自动完成组件的专业方法

React Native 自定义 Google Places 自动完成实现方案

要完全自定义输入框和结果展示且不依赖第三方库,核心是基于 Google Places Autocomplete API 手动实现输入监听、请求调度、结果渲染这几个核心环节,以下是专业的分步实现方式:

1. 准备工作:配置 Google Places API

  • 前往 Google Cloud 控制台,创建项目并启用Places API和Places Autocomplete Web Service
  • 创建 API 密钥,限制密钥的使用范围(比如仅允许你的 React Native 应用包名/签名调用,避免密钥泄露)

2. 自定义带防抖的 TextInput 组件

为避免频繁触发 API 请求,需给输入添加防抖逻辑,同时完全控制输入框样式:

import { useState, useCallback } from 'react';
import { TextInput, StyleSheet } from 'react-native';

// 防抖工具函数
const debounce = (func, delay) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
};

const CustomPlacesInput = ({ onInputChange }) => {
  const [inputValue, setInputValue] = useState('');

  const debouncedOnChange = useCallback(
    debounce((text) => onInputChange(text), 300),
    []
  );

  const handleTextChange = (text) => {
    setInputValue(text);
    debouncedOnChange(text);
  };

  return (
    <TextInput
      style={styles.input}
      value={inputValue}
      onChangeText={handleTextChange}
      placeholder="输入地点搜索..."
      clearButtonMode="always"
      // 可添加任意自定义样式、键盘类型等属性
    />
  );
};

const styles = StyleSheet.create({
  input: {
    height: 50,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    paddingHorizontal: 16,
    margin: 16,
    backgroundColor: '#fff',
  },
});

export default CustomPlacesInput;

3. 实现 Google Places API 请求函数

直接通过 Fetch 调用官方 Web Service,处理参数与响应解析:

const GOOGLE_PLACES_API_KEY = '你的API密钥';

const fetchPlacesAutocomplete = async (inputText) => {
  if (!inputText.trim()) return [];

  try {
    const response = await fetch(
      `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent(inputText)}&key=${GOOGLE_PLACES_API_KEY}&language=zh-CN`
    );

    if (!response.ok) throw new Error(`API请求失败:${response.status}`);

    const data = await response.json();
    if (data.status === 'OK') {
      return data.predictions.map(prediction => ({
        description: prediction.description,
        placeId: prediction.place_id,
        structuredFormatting: prediction.structured_formatting,
      }));
    } else {
      console.error('Places API返回错误:', data.status);
      return [];
    }
  } catch (error) {
    console.error('获取地点列表失败:', error);
    return [];
  }
};

4. 自定义搜索结果展示组件

完全控制结果列表的样式与交互,支持高亮匹配文本:

import { View, Text, TouchableOpacity, StyleSheet, FlatList } from 'react-native';

const PlacesResultList = ({ places, onPlaceSelect, inputValue }) => {
  const renderItem = ({ item }) => {
    // 高亮输入匹配文本
    const highlightMatch = (text) => {
      if (!inputValue) return text;
      const regex = new RegExp(`(${inputValue})`, 'gi');
      const parts = text.split(regex);
      return parts.map((part, index) =>
        regex.test(part) ? (
          <Text key={index} style={styles.highlightText}>{part}</Text>
        ) : (
          <Text key={index}>{part}</Text>
        )
      );
    };

    return (
      <TouchableOpacity
        style={styles.resultItem}
        onPress={() => onPlaceSelect(item)}
      >
        <Text style={styles.primaryText}>
          {highlightMatch(item.structuredFormatting.main_text)}
        </Text>
        <Text style={styles.secondaryText}>
          {item.structuredFormatting.secondary_text}
        </Text>
      </TouchableOpacity>
    );
  };

  if (places.length === 0) {
    return (
      <View style={styles.emptyContainer}>
        <Text style={styles.emptyText}>暂无匹配地点</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={places}
      renderItem={renderItem}
      keyExtractor={(item) => item.placeId}
      style={styles.listContainer}
    />
  );
};

const styles = StyleSheet.create({
  listContainer: {
    marginHorizontal: 16,
    maxHeight: 300,
    backgroundColor: '#fff',
    borderRadius: 8,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  resultItem: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  primaryText: {
    fontSize: 16,
    fontWeight: '500',
    color: '#333',
  },
  secondaryText: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  highlightText: {
    color: '#1677ff',
    fontWeight: '600',
  },
  emptyContainer: {
    padding: 24,
    backgroundColor: '#fff',
    borderRadius: 8,
    marginHorizontal: 16,
  },
  emptyText: {
    color: '#999',
    textAlign: 'center',
  },
});

export default PlacesResultList;

5. 整合所有组件到主页面

处理状态管理,串联输入、请求、结果展示逻辑:

import { useState, useCallback } from 'react';
import { View, StyleSheet, ActivityIndicator } from 'react-native';
import CustomPlacesInput from './CustomPlacesInput';
import PlacesResultList from './PlacesResultList';
import { fetchPlacesAutocomplete } from './placesApi';

const PlacesAutocompleteScreen = () => {
  const [places, setPlaces] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = useCallback(async (text) => {
    setInputValue(text);
    if (!text.trim()) {
      setPlaces([]);
      return;
    }
    setIsLoading(true);
    const results = await fetchPlacesAutocomplete(text);
    setPlaces(results);
    setIsLoading(false);
  }, []);

  const handlePlaceSelect = (place) => {
    // 处理选中逻辑,比如获取详细地址、坐标等
    console.log('选中地点:', place);
    setInputValue(place.description);
    setPlaces([]);
    // 可选:调用 Place Details API 获取更多信息
  };

  return (
    <View style={styles.container}>
      <CustomPlacesInput onInputChange={handleInputChange} />
      {isLoading ? (
        <View style={styles.loadingContainer}>
          <ActivityIndicator size="small" color="#1677ff" />
        </View>
      ) : (
        <PlacesResultList
          places={places}
          onPlaceSelect={handlePlaceSelect}
          inputValue={inputValue}
        />
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
    paddingTop: 20,
  },
  loadingContainer: {
    padding: 16,
    backgroundColor: '#fff',
    borderRadius: 8,
    marginHorizontal: 16,
    alignItems: 'center',
  },
});

export default PlacesAutocompleteScreen;

6. 关键优化点

  • 防抖优化:设置300-500ms的防抖延迟,减少API请求次数
  • 安全处理:生产环境建议通过后端代理转发API请求,避免密钥暴露
  • 错误处理:捕获网络错误与API返回错误,避免应用崩溃
  • 本地化:在API请求中添加language参数,返回对应语言的结果
  • 结果缓存:对近期搜索关键词进行缓存,减少重复请求

内容的提问来源于stack exchange,提问作者SAI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:23:14