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

Expo环境下React Native中SQLite使用异常的问题求助

问题描述

我正在通过Expo学习React Native,使用expo-sqlite库实现数据库初始化、数据插入和查询操作。当前遇到两个核心问题:

  • 插入数据时无报错,但await insert语句后的navigation.navigate无法执行,同时持续收到「Possible Unhandled Promise Rejection (id: 0)」警告
  • 改用then/catch处理Promise后警告消失,但仍无法跳转到AllPlaces页面

相关代码

SQLite操作代码

import * as SQLite from 'expo-sqlite';
import { Place } from '../models/place';

const database = SQLite.openDatabase('places.db');

export function init() {
  const promise = new Promise((resolve, reject) => {
    database.transaction((tx) => {
      tx.executeSql(
        `CREATE TABLE IF NOT EXISTS places (
          id INTEGER PRIMARY KEY NOT NULL,
          title TEXT NOT NULL,
          imageUri TEXT NOT NULL,
          address TEXT NOT NULL,
          lat REAL NOT NULL,
          lng REAL NOT NULL
      )`,
        [],
        () => resolve(),
        (_, error) => reject(error)
      )
    })
  })
  return promise;
}

export function insertPlace(place) {
  const promise = new Promise((resolve, reject) => {
    database.transaction((tx) => {
      tx.executeSql(
        `INSERT INTO places (title, imageUri, address, lat, lng) VALUES (?, ?, ?, ?, ?)`,
        [place.title, place.imageUri, place.address, place.lat, place.lng],
        (_, result) => resolve(result),
        (_, error) => reject(error)
      )
    })
  });
  return promise;
}

export function fetchPlaces() {
  const promise = new Promise((resolve, reject) => {
    database.transaction((tx) => {
      tx.executeSql('SELECT * FROM places', [],
        (_, result) => {
          const places = []
          for (const dp of result.rows._array) {
            places.push(new Place(dp.title, dp.imageUri, {address: dp.address, lat: dp.lat, lng: dp.lng}, dp.id))
          }
          resolve(places)
        },
        (_, error) => reject(error)
      )
    })
  });
  return promise
}

Place数据模型代码

export class Place {
  constructor(title, imageUri, location, id) {
    this.title = title;
    this.imageUri = imageUri;
    this.address = location.address;
    this.location = {lat: location.lat, lng: location.lng};
    this.id = id
  }
}

App.js代码

import { init } from './util/database';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NavigationContainer } from '@react-navigation/native';
import { useState, useEffect } from 'react';
import { StatusBar } from 'react-native';
import AllPlaces from './screens/AllPlaces';
import AddPlace from './screens/AddPlace';
import Map from './screens/Map';
import { Colors } from './constants/colors';
import AppLoading from 'expo-app-loading';

const Stack = createNativeStackNavigator();

export default function App() {
  const [dbInitialized, setDBInitialized] = useState(false);

  useEffect(() => {
    init()
      .then(() => setDBInitialized(true))
      .catch((err) => console.log(err));
  }, [])

  if (!dbInitialized) {
    return <AppLoading />
  }

  return (
    <>
      <StatusBar style="auto" />
      <NavigationContainer>
        <Stack.Navigator screenOptions={{
          headerStyle: { backgroundColor: Colors.primary50 },
          headerTintColor: Colors.gray700,
          contentStyle: { backgroundColor: Colors.gray700 }
        }}>
          <Stack.Screen name="AllPlaces" component={AllPlaces} options={({ navigation }) => ({
            title: 'Your Favorite Places',
            headerRight: ({ tintColor }) => (
              <IconButton icon="add" size={24} color={tintColor} onPress={() => navigation.navigate('AddPlace')} />
            )
          })} />
          <Stack.Screen name="AddPlace" component={AddPlace} options={{
            title: 'Add a new Place'
          }} />
          <Stack.Screen name="Map" component={Map} />
        </Stack.Navigator>
      </NavigationContainer>
    </>
  );
}

AddPlace.js初始代码

import PlaceForm from "../components/places/PlaceForm"
import { insertPlace } from "../util/database";

export default function AddPlace({navigation}) {
  async function createPlaceHandler(place) {
    await insertPlace(place);
    navigation.navigate('AllPlaces', {place: place});
  }

  return (
    <PlaceForm onCreatePlace={createPlaceHandler} />
  )
}

PlaceForm.js代码

import { useState, useCallback } from 'react';
import { ScrollView, View, Text, TextInput, Button, StyleSheet } from 'react-native';
import ImagePicker from './ImagePicker';
import LocationPicker from './LocationPicker';
import { Place } from '../../models/place';

export default function PlaceForm({onCreatePlace}) {
  const [enteredTitle, setEnteredTitle] = useState();
  const [selectedImage, setSelectedImage] = useState();
  const [pickedLocation, setPickedLocation] = useState();

  function changeTitleHandler(enteredText) {
    setEnteredTitle(enteredText);
  }

  function takeImageHandler(imageUri) {
    setSelectedImage(imageUri);
  }

  const pickLocationHandler = useCallback((location) => {
    setPickedLocation(location);
  }, []);

  function savePlaceHandler() {
    const placeData = new Place(enteredTitle, selectedImage, pickedLocation);
    onCreatePlace(placeData);
  }

  return (
    <ScrollView style={styles.form}>
      <View>
        <Text style={styles.label}>Title</Text>
        <TextInput style={styles.input} onChangeText={changeTitleHandler} value={enteredTitle} />
      </View>
      <ImagePicker onTakeImage={takeImageHandler} />
      <LocationPicker onPickLocation={pickLocationHandler} />
      <Button onPress={savePlaceHandler}>Add Place</Button>
    </ScrollView>
  )
}

const styles = StyleSheet.create({
  form: {
    flex: 1,
    padding: 24
  },
  label: {
    fontWeight: 'bold',
    marginBottom: 4,
    color: 'white'
  },
  input: {
    marginBottom: 16,
    padding: 8,
    backgroundColor: 'white',
    borderRadius: 4
  }
});

AllPlaces.js代码

import PlacesList from "../components/places/PlacesList";
import { useState, useEffect } from 'react';
import { useIsFocused } from '@react-navigation/native';

export default function AllPlace ({route}) {
  const [loadedPlaces, setLoadedPlaces] = useState([]);
  const isFocused = useIsFocused();

  useEffect(() => {
    async function loadPlaces() {
      await fetchPlaces();
    }

    if(isFocused) {
      loadPlaces();
    }
  }, [isFocused])
  return (
    <PlacesList places={loadedPlaces} />
  )
}

修改后的AddPlace.js代码

import PlaceForm from "../components/places/PlaceForm"
import { insertPlace } from "../util/database";

export default function AddPlace({navigation}) {
  async function createPlaceHandler(place) {
    await insertPlace(place).then((result) => navigation.navigate('AllPlaces')).catch((error) => console.log(error));
  }

  return (
    <PlaceForm onCreatePlace={createPlaceHandler} />
  )
}

问题分析与解决方案

核心问题1:SQL插入参数错误

insertPlace函数中,你直接读取place.lat和place.lng,但根据Place类的定义,经纬度数据存储在place.location对象下,这会导致插入时传入undefined,违反数据库表的NOT NULL约束,最终Promise被拒绝,后续代码无法执行。

修复代码(util/database.js):

export function insertPlace(place) {
  const promise = new Promise((resolve, reject) => {
    database.transaction((tx) => {
      tx.executeSql(
        `INSERT INTO places (title, imageUri, address, lat, lng) VALUES (?, ?, ?, ?, ?)`,
        // 修正经纬度的取值路径
        [place.title, place.imageUri, place.address, place.location.lat, place.location.lng],
        (_, result) => resolve(result),
        (_, error) => reject(error)
      )
    })
  });
  return promise;
}

核心问题2:AllPlaces数据未更新

AllPlaces组件中,loadPlaces函数仅调用了fetchPlaces,但未将返回的数据赋值给状态,导致页面无法渲染新插入的数据。

修复代码(AllPlaces.js):

import { fetchPlaces } from "../util/database"; // 补充导入
import PlacesList from "../components/places/PlacesList";
import { useState, useEffect } from 'react';
import { useIsFocused } from '@react-navigation/native';

// 修正组件名拼写错误:AllPlace → AllPlaces
export default function AllPlaces ({route}) {
  const [loadedPlaces, setLoadedPlaces] = useState([]);
  const isFocused = useIsFocused();

  useEffect(() => {
    async function loadPlaces() {
      try {
        const places = await fetchPlaces();
        setLoadedPlaces(places); // 将获取到的数据更新到状态
      } catch (error) {
        console.error('加载数据失败:', error);
      }
    }

    if(isFocused) {
      loadPlaces();
    }
  }, [isFocused])
  return (
    <PlacesList places={loadedPlaces} />
  )
}

优化异步处理逻辑

AddPlace中混合使用await和then/catch的写法不规范,改用try/catch更清晰,便于排查错误:

优化代码(AddPlace.js):

import PlaceForm from "../components/places/PlaceForm"
import { insertPlace } from "../util/database";

export default function AddPlace({navigation}) {
  async function createPlaceHandler(place) {
    try {
      await insertPlace(place);
      navigation.navigate('AllPlaces');
    } catch (error) {
      console.error('插入数据失败:', error);
    }
  }

  return (
    <PlaceForm onCreatePlace={createPlaceHandler} />
  )
}

可选优化:添加表单校验

在PlaceForm的savePlaceHandler中添加必填项校验,避免插入无效数据:

function savePlaceHandler() {
  if (!enteredTitle || !selectedImage || !pickedLocation) {
    alert('请填写所有必填字段!');
    return;
  }
  const placeData = new Place(enteredTitle, selectedImage, pickedLocation);
  onCreatePlace(placeData);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 08:05:38