我正在尝试在React组件中使用indexeddb(通过idb作为承诺包装器)。我正在管理与
useEffect
的联系,但我一直在为此苦苦挣扎:
const Component = () => {
const [database, setDatabase] = useState();
useEffect(() => {
/* to use async in `useEfect` I have to move it to its own fn */
const getDatabase = async () => {
const db = await openDB("my-db", 1, {
upgrade(db) {
db.createObjectStore("my-store");
}
});
setDatabase(db);
};
getDatabase();
return () => database.close();
}, []);
return null;
};
但是如果成分数量和
database.close()
get 被调用,
database
状态未定义并且会中断:
Codesandbox 示例。
显然这是设计的。
我发现了一个丑陋的解决方法,我使用
_database
来存储最新的数据库。
const Component = () => {
const [database, setDatabase] = useState();
useEffect(() => {
/* to use async in `useEfect` I have to move it to its own fn */
let _database
const getDatabase = async () => {
const db = await openDB("my-db", 1, {
upgrade(db) {
db.createObjectStore("my-store");
}
});
setDatabase(db);
_database = db
};
getDatabase();
return () => _database.close();
}, []);
return null;
};
但这似乎不对。这样做的正确方法是什么?
我认为,你的解决方法并不是世界上最糟糕的事情。但我也认为,如果在调用
openDB
之后但在承诺解决之前卸载组件,仍然存在竞争条件,在这种情况下 _database
将在清理函数中未定义。我认为你可以做这样的事情来处理这种情况:
const Component = () => {
const [database, setDatabase] = useState();
useEffect(() => {
let _database, closed;
const getDatabase = async () => {
const db = await openDB("my-db", 1, {
upgrade(db) {
db.createObjectStore("my-store");
}
});
if (closed) {
db.close();
} else {
setDatabase(db);
_database = db
}
};
getDatabase();
return () => {
if (_database) {
_database.close();
}
closed = true;
}
}, []);
return null;
};
我同意这不是世界上最好看的代码。所以大部分时间它都与 IndexedDB 配合使用。
就个人而言,我在使用 React 和 IndexedDB 时所做的就是在调用
ReactDOM.render
之前连接到数据库,并将该连接存储在 React 之外。然后我就可以随心所欲地使用它,而不必担心它与 React 组件生命周期的关系。不过,我认为将其放入某些包装器 React 组件中本质上并没有什么问题。你只需要小心。但是嘿,这是 IndexedDB,无论如何你都要小心:)
const useIndexedDB = (dbName: string, storeName: string, keyPath: string) => {
const [db, setDb] = useState<IDBDatabase | null>(null);
useEffect(() => {
if (window === undefined) {
return;
}
const openDb = async () => {
const request = window.indexedDB.open(dbName, 1);
request.onupgradeneeded = (event) => {
if (!event.target) {
return;
}
// @ts-ignore
const db = event.target.result as IDBDatabase;
db.createObjectStore(storeName, { keyPath });
};
request.onsuccess = (event) => {
if (!event.target) {
return;
}
// @ts-ignore
const db = event.target.result as IDBDatabase;
setDb(db);
};
};
openDb();
}, []);
const add = async (data: any) => {
if (!db) {
return;
}
const transaction = db.transaction(storeName, "readwrite");
const store = transaction.objectStore(storeName);
store.add(data);
return transaction.oncomplete;
};
const get = async (key: string) => {
if (!db) {
return;
}
const transaction = db.transaction(storeName, "readonly");
const store = transaction.objectStore(storeName);
return store.get(key);
};
const getAll = async () => {
if (!db) {
return;
}
const transaction = db.transaction(storeName, "readonly");
const store = transaction.objectStore(storeName);
return store.getAll();
};
const deleteItem = async (key: string) => {
if (!db) {
return;
}
const transaction = db.transaction(storeName, "readwrite");
const store = transaction.objectStore(storeName);
store.delete(key);
return transaction.oncomplete;
};
return { add, get, getAll, deleteItem };
};
看起来您已经创建了一个名为 useIndexedDB 的自定义 React hook,它提供了与基于浏览器的数据库 IndexedDB 交互的基本功能。该钩子初始化和管理 IndexedDB 连接并公开函数来执行常见操作,例如添加数据、按键检索数据、检索所有数据和删除项目。
const [inputData, setInputData] = useState('');
const { add, get, getAll, deleteItem } = useIndexedDB('myDatabase', 'myStore', 'id');
const handleAdd = async () => {
if (inputData.trim() === '') return;
const data = {
id: Date.now().toString(),
value: inputData.trim(),
};
await add(data);
setInputData('');
};
const handleGet = async () => {
const key = prompt('Enter the key to retrieve data:');
if (!key) return;
const result = await get(key);
alert(result ? `Value for key ${key}: ${result.value}` : 'Key not found');
};
const handleGetAll = async () => {
const result = await getAll();
alert(result ? `All data: ${JSON.stringify(result)}` : 'No data found');
};
const handleDelete = async () => {
const key = prompt('Enter the key to delete:');
if (!key) return;
await deleteItem(key);
alert(`Item with key ${key} deleted`);
};
return (
<div>
<input
type="text"
value={inputData}
onChange={(e) => setInputData(e.target.value)}
placeholder="Enter data"
/>
<button onClick={handleAdd}>Add Data</button>
<button onClick={handleGet}>Get Data</button>
<button onClick={handleGetAll}>Get All Data</button>
<button onClick={handleDelete}>Delete Data</button>
</div>
);
};