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

React Context Provider中sql.js数据库实例为Null的问题排查

解决React Context中sql.js数据库实例未就绪导致的TypeError: Cannot read properties of null (reading 'exec')问题

问题根源

你的代码核心问题是:initSqlJs是异步操作,需要时间完成初始化,但App组件在首次渲染时直接调用sqliteDB.exec,此时sqliteDB还是初始的null状态,自然会触发报错。

修复方案

通过添加加载状态控制组件渲染时机+监听数据库实例状态变化来解决,具体修改如下:

1. 优化SQLiteContext,添加加载状态

修改SQLiteContext.js,新增加载状态标记数据库是否初始化完成,只有初始化完成后才渲染子组件:

import React, { createContext, useEffect, useState } from "react";
export const SQLiteContext = createContext();

const SQLiteProvider = ({ children }) => {
  const [SQL, setSQL] = useState(null);
  const [sqliteDB, setSqliteDB] = useState(null);
  const [isLoading, setIsLoading] = useState(true); // 标记数据库是否在加载中

  useEffect(() => {
    initSqlJs({ locateFile: file => `/js/${file}` })
      .then((aSQL) => {
        setSQL(aSQL);
        setSqliteDB(new aSQL.Database());
        setIsLoading(false); // 初始化完成,更新加载状态
      })
      .catch((err) => {
        console.error("SQLite初始化失败:", err);
        setIsLoading(false);
      });
  }, []);

  return (
    <SQLiteContext.Provider value={{ SQL, sqliteDB, isLoading }}>
      {/* 加载中显示占位内容,避免子组件访问未就绪的数据库 */}
      {isLoading ? <div>数据库加载中...</div> : children}
    </SQLiteContext.Provider>
  );
};

export { SQLiteProvider };

2. 修改App组件,监听数据库实例状态

在App.js中,通过useEffect监听sqliteDB的变化,确保只有当实例存在时才执行查询:

import React, { useContext, useEffect, useState } from "react";
import { SQLiteContext } from "../contexts/SQLiteContext";

export default function App() {
  const { sqliteDB } = useContext(SQLiteContext);
  const [queryResult, setQueryResult] = useState(null);

  useEffect(() => {
    if (!sqliteDB) return; // 实例未就绪时直接返回

    try {
      // 先确保目标表存在,避免查询不存在的表报错
      sqliteDB.exec("CREATE TABLE IF NOT EXISTS hello (id INTEGER PRIMARY KEY, name TEXT)");
      // 执行查询
      const res = sqliteDB.exec("SELECT * FROM hello");
      setQueryResult(JSON.stringify(res));
    } catch (err) {
      console.error("查询执行失败:", err);
    }
  }, [sqliteDB]); // 依赖sqliteDB,实例变化时重新执行

  return (
    <>
      <div>test</div>
      {queryResult && <div>查询结果:{queryResult}</div>}
    </>
  );
}

为什么之前的尝试没解决问题?

  • 合并到单个文件:异步初始化的本质问题没解决,组件渲染时机还是早于数据库实例就绪
  • 在initSqlJs回调中执行查询:如果是在Provider的回调里执行,组件依然会先渲染并尝试访问null的实例;如果是在组件内直接写,没通过useEffect监听状态变化
  • 用window.db全局初始化:全局变量虽然能拿到实例,但React组件的渲染时机无法保证晚于初始化完成,而且不符合Context的状态管理规范

额外优化建议

  • 可以在Provider的initSqlJs回调中预创建常用表,避免每个组件重复执行建表语句
  • 把数据库操作(如查询、插入)封装成方法放到Context的value中,组件直接调用封装好的方法,降低耦合度

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 09:55:20