- 현우
","language":"javascript"},"rv":2,"parentBlockId":"_root_","sortKey":".o0rr","rt":3},"01kg540zmsr0gcvgqtpwzyshsf":{"id":"01kg540zmsr0gcvgqtpwzyshsf","type":"image","version":3,"value":{"image":{"id":"4dre2n","imageKey":"image/slashpagePost/20260129/235525_BLDRy2deRS3yNrllmh","path":"https://upload.cafenono.com/image/slashpagePost/20260129/235525_BLDRy2deRS3yNrllmh?q=80&s=1280x180&t=outside&f=webp","originalImageSize":{"width":1773,"height":368},"dominantColor":"#25242b","isAnimated":false,"mimeType":"image/png","originalContentLength":35435,"updatedAt":"2026-01-29T14:55:26.000Z","createdAt":"2026-01-29T14:55:25.000Z","domain":{"id":"d3mqew"},"previewHash":"U24_trxvs.t7.Af6RiR%%OfQRiR%-=ogWAWB","page":{"id":"1q3vdn2pxr5x42xy49pr"},"hasTransparentPixels":false,"filename":"image-2026-1-29_18-14-13.png"},"extra":{"isRepresentative":true},"shapeConfigurable":true},"rv":4,"parentBlockId":"_root_","sortKey":".o0u","rt":1},"01kg540zmv1dwdt5w6w35fdd86":{"id":"01kg540zmv1dwdt5w6w35fdd86","type":"text","version":1,"value":{"tokens":[{"text":"데이터를 정상적으로 저장했다면, 데이터 로드는 아래와 같이 컴포저블의 일부 함수를 호출해 바로 불러와줄 수 있다."}]},"rv":3,"parentBlockId":"_root_","sortKey":".o0v","rt":1},"01kg541bgdv9662d2fs2q32n5p":{"id":"01kg541bgdv9662d2fs2q32n5p","type":"code","version":1,"value":{"code":"// Form.vue (로드)\n\n","language":"javascript"},"rv":2,"parentBlockId":"_root_","sortKey":".o0rr","rt":3},"01kg540zmsr0gcvgqtpwzyshsf":{"id":"01kg540zmsr0gcvgqtpwzyshsf","type":"image","version":3,"value":{"image":{"id":"4dre2n","imageKey":"image/slashpagePost/20260129/235525_BLDRy2deRS3yNrllmh","path":"https://upload.cafenono.com/image/slashpagePost/20260129/235525_BLDRy2deRS3yNrllmh?q=80&s=1280x180&t=outside&f=webp","originalImageSize":{"width":1773,"height":368},"dominantColor":"#25242b","isAnimated":false,"mimeType":"image/png","originalContentLength":35435,"updatedAt":"2026-01-29T14:55:26.000Z","createdAt":"2026-01-29T14:55:25.000Z","domain":{"id":"d3mqew"},"previewHash":"U24_trxvs.t7.Af6RiR%%OfQRiR%-=ogWAWB","page":{"id":"1q3vdn2pxr5x42xy49pr"},"hasTransparentPixels":false,"filename":"image-2026-1-29_18-14-13.png"},"extra":{"isRepresentative":true},"shapeConfigurable":true},"rv":4,"parentBlockId":"_root_","sortKey":".o0u","rt":1},"01kg540zmv1dwdt5w6w35fdd86":{"id":"01kg540zmv1dwdt5w6w35fdd86","type":"text","version":1,"value":{"tokens":[{"text":"데이터를 정상적으로 저장했다면, 데이터 로드는 아래와 같이 컴포저블의 일부 함수를 호출해 바로 불러와줄 수 있다."}]},"rv":3,"parentBlockId":"_root_","sortKey":".o0v","rt":1},"01kg541bgdv9662d2fs2q32n5p":{"id":"01kg541bgdv9662d2fs2q32n5p","type":"code","version":1,"value":{"code":"// Form.vue (로드)\n\n


const request = indexedDB.open('app', 1);request.onupgradeneeded = (e) => {
const db = e.target.result;
// ObjectStore 생성 (테이블)
const store = db.createObjectStore('post', { keyPath: 'id' });
// 인덱스 생성
store.createIndex('by_title', 'title');
}let db;
request.onsuccess = () => {
db = request.result;
};const tx = db.transaction("posts", "readwrite");
const store = tx.objectStore('post');store.add({
id: 1,
title: "Hello"
});const getRequest = store.get(1);
getRequest.onsuccess = () => {
console.log(getRequest.result);
};const cursorReq = store.openCursor();
cursorReq.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
console.log(cursor.value);
cursor.continue();
}
}const index = store.index('by_title'); // 2.스키마 정의 부분 확인
const req = index.get('title');
req.onsuccess = () => {
console.log(req.result);
};const tx = db.transaction("posts", "readwrite");
tx.oncomplete = () => {
console.log("트랜잭션 완료");
}
tx.onerror = () => {
console.error("트랜잭션 실패");
}request.onsuccess = () => {
const tx = db.transaction("posts", "readwrite");
const store = tx.objectStore('posts");
const req = store.get(1);
req.onsuccess = () => { // 지옥
console.log(req.result);
}
};import { openDB } from 'idb';
export const dbPromise = openDB('app-db', 1, {
upgrade(db) {
db.createObjectStore('posts', { keyPath: 'id' });
}
});const db = await dbPromise;await db.put("posts", {
id: 1,
title: 'Hello'
});const post = await db.get('posts', 1);const posts = await db.getAll('posts');await db.delete('posts', 1);const tx = db.transaction('posts', 'readwrite');
const store = tx.objectStore('posts');
await store.put({ id: 1, title: 'A' });
await store.put({ id: 2, title : 'B' });
await tx.done() // 중요// 인덱스 생성 (Upgrade 단계)
upgrade(db) {
const store = db.createObjectStore('posts', { keyPath: 'id' });
store.createIndex('by_title', 'title');
}
// 인덱스 조회
const post = await db.getFromIndex('posts', 'by_title', 'Hello');for await (const cursor of db
.transaction("posts")
.store
.openCursor()) {
console.log(cursor.value);
}// useContentsAutoSave.ts
import { openDB } from 'idb';
interface ErrorResponse {
success: boolean,
error: Object | null
}
export default function useContentsAutoSave() {
const contentDB = async () => await openDB('content-db', 1, {
upgrade(db) {
db.createObjectStore('contents', { keyPath: 'id' });
}
});
const saveContent = async (id: string, data: any): Promise<ErrorResponse> => {
try {
const db = await contentDB();
await db.put('contents', { id, ...data });
return { success: true, error: null }
} catch (error) {
return { success: false, error: {} }
}
};
const loadContent = async (id: string) => {
const db = await contentDB();
return await db.get('contents', id);
}
const removeContent = async (id: string) => {
try {
const db = await contentDB();
await db.delete('contents', id);
return { success: true, error: null }
} catch(error) {
return { success: false, error: {} }
}
}
return { contentDB, saveContent, removeContent, loadContent }
}// Form.vue (저장)
<script setup>
const { saveContent } = useContentsAutoSave();
// htmlContent는 디바운스에 걸려 3초 시간의 간격을 가지고 있다.
watch(() => props.htmlContent, async () => {
await saveMount('content', {
title: 'title',
contents: 'html-content',
contentType: 'content-type',
})
});
</script setup>// Form.vue (로드)
<script setup>
const { loadContent } = useContentsAutoSave();
onMounted(() => {
const { title, contents } = await loadContent('content');
title.value = title;
contents.value = contents;
});
</script>