TanStack DB - 문서
TanStack DB 문서에 오신 것을 환영합니다.
TanStack DB는 API를 위한 반응형 클라이언트 스토어입니다. 빠르고 현대적인 앱을 구축하는 과정의 문제를 해결하여 다음을 지원합니다:
- 데이터를 정규화된 컬렉션에 로드하여 엔드포인트 난립과 네트워크 워터폴을 방지합니다
- 밀리초 미만의 라이브 쿼리와 실시간 반응성으로 클라이언트 성능을 최적화합니다
- 즉각적인 낙관적 쓰기로 네트워크를 상호작용 경로에서 제거합니다
데이터 로딩이 최적화됩니다. 상호작용이 즉각적으로 느껴집니다. 백엔드는 단순하게 유지되고 앱은 매우 빠르게 실행됩니다. 로드하는 데이터의 양과 관계없이 가능합니다.
빠르고 현대적인 앱 구축의 복잡성 제거
TanStack DB를 사용하면 매우 빠른 로컬 쿼리 엔진, 실시간 반응성 및 즉각적인 낙관적 업데이트를 통해 컴포넌트에 필요한 방식으로 데이터를 쿼리할 수 있습니다.
두 가지 해악 중 덜한 것을 선택하는 대신:
- 뷰별 API - 백엔드를 복잡하게 만들고 네트워크 워터폴을 유발합니다
- 모든 데이터를 로드한 후 필터링 - 로딩을 느리게 하고 클라이언트 성능을 저하시킵니다
TanStack DB는 새로운 방식을 제공합니다:
- 정규화된 컬렉션 - 백엔드를 단순하게 유지합니다
- 쿼리 기반 동기화 - 데이터 로딩을 최적화합니다
- 밀리초 미만의 라이브 쿼리 - 앱을 빠르고 반응성 있게 유지합니다
TanStack Query를 컬렉션, 라이브 쿼리 및 낙관적 뮤테이션으로 확장하며 REST API, 동기화 엔진 또는 모든 데이터 소스와 원활하게 작동합니다.
목차
- 작동 방식 — TanStack DB 개발 모델과 각 요소가 어떻게 결합되는지 이해합니다
- SSR 및 하이드레이션 —
DbClient를 사용하여 명시적 컬렉션 행 또는 라이브 쿼리 결과 스냅샷을 전송합니다 - API 참조 — 기본 요소와 함수 인터페이스를 확인합니다
- 사용 예제 — 일반적인 사용 패턴의 예제를 확인합니다
- 추가 정보 — 지원 및 추가 정보를 찾을 수 있는 위치를 확인합니다
작동 방식
TanStack DB는 다음과 같이 작동합니다:
- 컬렉션 정의 데이터를 채울 수 있는 타입이 지정된 객체 집합입니다
- 라이브 쿼리 사용 컬렉션에서 또는 컬렉션 전체에서 데이터를 쿼리합니다
- 낙관적 뮤테이션 수행 트랜잭션 뮤테이터를 사용합니다
import {
DbClient,
DbProvider,
collectionOptions,
not,
useDbClient,
useLiveQuery,
} from '@tanstack/react-db'
// Define stable collection descriptors to load data into
const todoCollection = collectionOptions('todos', () => ({
id: 'todos',
// ...your config
onUpdate: updateMutationFn,
}))
function useTodoCollection() {
return useDbClient().collection(todoCollection)
}
const Todos = () => {
const todosCollection = useTodoCollection()
// Bind data using live queries
const { data: todos } = useLiveQuery({
query: (q) =>
q.from({ todo: todoCollection }).where(({ todo }) => not(todo.completed)),
})
const complete = (todo) => {
// Instantly applies optimistic state
todosCollection.update(todo.id, (draft) => {
draft.completed = true
})
}
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} onClick={() => complete(todo)}>
{todo.text}
</li>
))}
</ul>
)
}
const dbClient = new DbClient()
const App = () => (
<DbProvider client={dbClient}>
<Todos />
</DbProvider>
)
컬렉션 정의
컬렉션은 데이터를 채울 수 있는 타입이 지정된 객체 집합입니다. 앱에 데이터를 로드하는 작업과 컴포넌트에 데이터를 바인딩하는 작업을 분리하도록 설계되었습니다.
컬렉션은 다음을 포함한 여러 방식으로 채울 수 있습니다:
- 데이터 가져오기, 예: TanStack Query를 사용하여 API 엔드포인트에서 가져오기
- 데이터 동기화, 예: ElectricSQL과 같은 동기화 엔진 사용 또는 PowerSync
- 로컬 데이터 저장, 예: 사용자 환경설정 및 설정에 localStorage 사용 또는 메모리 내 클라이언트 데이터 또는 UI 상태
- 라이브 컬렉션 쿼리에서 가져오기, 구체화된 뷰로 파생 컬렉션 생성
컬렉션에 데이터를 저장하면 컴포넌트의 라이브 쿼리를 사용하여 컬렉션 전체를 쿼리할 수 있습니다.
동기화 모드
컬렉션은 데이터 로딩을 최적화하기 위해 세 가지 동기화 모드를 지원합니다:
- Eager 모드(기본값): 전체 컬렉션을 미리 로드합니다. 사용자 환경설정이나 작은 참조 테이블처럼 대부분 정적인 <10k개 행 데이터에 적합합니다.
- On-demand 모드: 쿼리가 요청하는 항목만 로드합니다. 대규모 데이터 세트(>50k개 행), 검색 인터페이스 및 대부분의 데이터에 액세스하지 않는 카탈로그에 적합합니다.
- Progressive 모드: 쿼리 하위 집합을 즉시 로드하고 백그라운드에서 전체 데이터 세트를 동기화합니다. 첫 화면을 즉시 표시하면서 밀리초 미만의 쿼리가 필요한 협업 앱에 적합합니다.
On-demand 모드에서는 컴포넌트의 쿼리가 API 호출이 됩니다:
const productsCollection = collectionOptions('products', (client) =>
queryCollectionOptions({
id: 'products',
queryKey: ['products'],
queryClient: client.requireDependency<QueryClient>('queryClient'),
queryFn: async (ctx) => {
// Query predicates passed automatically in ctx.meta
const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
return api.getProducts(params) // e.g., GET /api/products?category=electronics&price_lt=100
},
getKey: (product) => product.id,
syncMode: 'on-demand', // ← Enable query-driven sync
})
)
TanStack DB는 중복 요청을 자동으로 통합하고, 쿼리를 확장할 때 델타 로딩을 수행하며, 조인을 최소한의 일괄 요청으로 최적화하고, TanStack Query 캐시 정책을 준수합니다. 결과적으로 사용자 지정 뷰별 API보다 네트워크 요청 수가 더 적은 경우가 많습니다.
전체 조건자 매핑 세부 정보는 쿼리 컬렉션 문서를 참조합니다.
라이브 쿼리 사용
라이브 쿼리는 컬렉션에서 데이터를 쿼리하는 데 사용됩니다. 라이브 쿼리는 반응형입니다. 기본 데이터가 쿼리 결과에 영향을 주는 방식으로 변경되면 결과가 증분 업데이트되어 쿼리에서 반환되고 다시 렌더링됩니다.
TanStack DB 라이브 쿼리는 미분 데이터플로의 TypeScript 구현인 d2ts를 사용하여 구현됩니다. 이를 통해 전체 쿼리를 다시 실행하는 대신 쿼리 결과를 증분 업데이트할 수 있습니다. 따라서 매우 복잡한 쿼리에서도 일반적으로 밀리초 미만으로 매우 빠르게 작동합니다.
성능: 정렬된 100,000개 항목 컬렉션에서 한 행을 업데이트하는 작업이 M1 Pro MacBook에서 약 0.7ms 만에 완료됩니다. 복잡한 쿼리와 대규모 데이터 세트에서도 낙관적 업데이트가 실제로 즉각적으로 느껴질 만큼 빠릅니다.
라이브 쿼리는 컬렉션 간 조인을 지원합니다. 이를 통해 다음을 수행할 수 있습니다:
- 정규화된 데이터를 컬렉션에 로드한 후 쿼리를 통해 역정규화합니다. 클라이언트에 맞춘 사용자 지정 API 엔드포인트가 필요하지 않으므로 백엔드를 단순화할 수 있습니다
- 여러 소스의 데이터를 조인합니다. 예를 들어 데이터베이스에서 일부 데이터를 동기화하고 외부 API에서 다른 데이터를 가져온 다음 이를 프런트엔드 코드의 통합 데이터 모델로 조인할 수 있습니다
모든 쿼리는 또한 쿼리할 수 있는 또 다른 컬렉션을 반환합니다.
라이브 쿼리에 대한 자세한 내용은 라이브 쿼리 문서를 참조합니다.
낙관적 뮤테이션 수행
컬렉션은 insert, update 및 delete 작업을 지원합니다. 호출되면 기본적으로 해당 onInsert, onUpdate, onDelete 핸들러를 트리거하며, 이 핸들러는 뮤테이션을 백엔드에 기록합니다.
// Define collection with persistence handlers
const todoCollection = collectionOptions('todos', () => ({
id: "todos",
// ... other config
onUpdate: async ({ transaction }) => {
const { original, changes } = transaction.mutations[0]
await api.todos.update(original.id, changes)
},
}))
const todosCollection = dbClient.collection(todoCollection)
// Immediately applies optimistic state
todosCollection.update(todo.id, (draft) => {
draft.completed = true
})
컬렉션은 낙관적 상태를 동기화된 데이터와 별도로 유지합니다. 라이브 쿼리가 컬렉션에서 읽을 때는 변경할 수 없는 동기화 데이터 위에 낙관적 뮤테이션을 덧씌운 로컬 뷰를 확인합니다.
낙관적 상태는 핸들러가 확인될 때까지 유지되며, 그 시점에 데이터가 서버에 영속화되고 다시 동기화됩니다. 핸들러에서 오류가 발생하면 낙관적 상태가 롤백됩니다.
더 복잡한 뮤테이션의 경우 createOptimisticAction을 사용하여 사용자 지정 작업을 만들거나 createTransaction을 사용하여 사용자 지정 트랜잭션을 만들 수 있습니다. 자세한 내용은 뮤테이션 가이드를 참조합니다.
단방향 데이터 흐름
이는 클라이언트를 넘어 서버까지 포함하도록 redux/flux 스타일 상태 관리 패턴을 확장하는 단방향 데이터 흐름 모델을 지원합니다:

낙관적 상태의 즉각적인 내부 루프가 먼저 실행되고, 시간이 지나면서 서버에 영속화하고 업데이트된 서버 상태를 컬렉션에 다시 동기화하는 더 느린 외부 루프가 이를 대체합니다.
API 참조
컬렉션
TanStack DB는 다양한 데이터 소스와 사용 사례를 위한 여러 내장 컬렉션 유형을 제공합니다. 각 컬렉션 유형에는 자체적인 상세 문서 페이지가 있습니다:
내장 컬렉션 유형
Fetch 컬렉션
- QueryCollection — REST API 및 데이터 가져오기를 위해 TanStack Query를 사용하여 컬렉션에 데이터를 로드합니다.
Sync 컬렉션
-
ElectricCollection — ElectricSQL의 실시간 동기화 엔진을 사용하여 Postgres에서 컬렉션으로 데이터를 동기화합니다.
-
TrailBaseCollection — TrailBase의 자체 호스팅 백엔드와 실시간 구독을 사용하여 컬렉션에 데이터를 동기화합니다.
-
RxDBCollection — 강력한 복제 및 동기화 기능을 갖춘 오프라인 우선 로컬 영속성을 위해 RxDB와 통합합니다.
-
PowerSyncCollection — PostgreSQL, MongoDB 및 MySQL 백엔드와 실시간 동기화되는 오프라인 우선 영속성을 위해 PowerSync의 SQLite 기반 데이터베이스와 동기화합니다.
로컬 컬렉션
-
LocalStorageCollection — 세션 간 유지되고 브라우저 탭 간 동기화되는 소량의 로컬 전용 상태를 저장합니다.
-
LocalOnlyCollection — 영속성이나 탭 간 동기화가 필요하지 않은 메모리 내 클라이언트 데이터 또는 UI 상태를 관리합니다.
컬렉션 스키마
모든 컬렉션은 선택적으로(강력히 권장되지만) schema를 추가할 수 있습니다.
제공하는 경우, 이는 Standard Schema 호환 스키마 인스턴스여야 하며, 예를 들어 Zod, Valibot, ArkType 또는 Effect가 있습니다.
스키마의 역할:
- 런타임 유효성 검사 - 컬렉션에 들어가기 전에 데이터가 제약 조건을 충족하는지 확인합니다
- 타입 변환 - 입력 타입을 풍부한 출력 타입으로 변환합니다(예: string → Date)
- 기본값 - 누락된 필드를 자동으로 채웁니다
- 타입 안전성 - 스키마에서 TypeScript 타입을 추론합니다
예시:
const todoSchema = z.object({
id: z.string(),
text: z.string(),
completed: z.boolean().default(false),
created_at: z.string().transform(val => new Date(val)), // string → Date
priority: z.number().default(0)
})
const todoCollection = collectionOptions(
queryCollectionOptions({
id: "todos",
schema: todoSchema,
// ...
})
)
const collection = dbClient.collection(todoCollection)
// Users provide simple inputs
collection.insert({
id: "1",
text: "Buy groceries",
created_at: "2024-01-01T00:00:00Z" // string
// completed and priority filled automatically
})
// Collection stores and returns rich types
const todo = collection.get("1")
console.log(todo.created_at.getFullYear()) // It's a Date!
console.log(todo.completed) // false (default)
컬렉션은 타입 추론에 스키마를 사용합니다. 스키마를 제공하면 명시적 타입 매개변수(예: createCollection<Todo>())도 함께 전달할 수 없습니다.
자세히 알아보기: 스키마 유효성 검사, 타입 변환 및 모범 사례에 대한 포괄적인 문서는 Schemas guide를 참조할 수 있습니다.
사용자 지정 컬렉션 타입 만들기
Collection 인터페이스를 구현하면 자체 컬렉션 타입을 만들 수 있습니다. 이 인터페이스는 ../packages/db/src/collection/index.ts에 있습니다.
참조를 위해 ../packages/db, ../packages/query-db-collection, ../packages/electric-db-collection, ../packages/trailbase-db-collection 및 ../packages/powersync-db-collection의 기존 구현을 확인할 수 있습니다. 재사용 가능한 컬렉션 구성 팩토리를 만드는 패턴은 Collection Options Creator guide도 참조할 수 있습니다.
라이브 쿼리
useLiveQuery 훅
useLiveQuery 훅을 사용하여 React 컴포넌트의 상태 변수에 라이브 쿼리 결과를 할당합니다:
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
const { data: todos } = useLiveQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, false))
.orderBy(({ todo }) => todo.created_at, 'asc')
.select(({ todo }) => ({
id: todo.id,
text: todo.text
})),
})
return <List items={ todos } />
}
조인을 사용하여 컬렉션 간에 쿼리할 수도 있습니다:
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
const { data: todos } = useLiveQuery({
query: (q) =>
q
.from({ todos: todoCollection })
.join(
{ lists: listCollection },
({ todos, lists }) => eq(lists.id, todos.listId),
'inner'
)
.where(({ lists }) => eq(lists.active, true))
.select(({ todos, lists }) => ({
id: todos.id,
title: todos.title,
listName: lists.name
})),
})
return <List items={ todos } />
}
useLiveSuspenseQuery 훅
React Suspense를 지원하려면 useLiveSuspenseQuery를 사용합니다. 이 훅은 초기 데이터 로드 중 렌더링을 일시 중단하고 data가 항상 정의되도록 보장합니다:
import { useLiveSuspenseQuery } from '@tanstack/react-db'
import { Suspense } from 'react'
const Todos = () => {
// data is always defined - no need for optional chaining
const { data: todos } = useLiveSuspenseQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, false)),
})
return <List items={ todos } />
}
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<Todos />
</Suspense>
)
라이브 쿼리의 React Suspense 섹션에서 자세한 사용 패턴과 useLiveSuspenseQuery 및 useLiveQuery의 사용 시점을 확인할 수 있습니다.
queryBuilder
컴포넌트 수명 주기 외부에서 기본 queryBuilder API를 사용하여 직접 쿼리를 만들 수도 있습니다:
import { createLiveQueryCollection, eq } from "@tanstack/db"
const completedTodos = createLiveQueryCollection({
startSync: true,
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, true)),
})
const results = completedTodos.toArray
또한 다음 사항에 유의해야 합니다:
- 쿼리 결과 자체가 컬렉션입니다
useLiveQuery는 컴포넌트를 마운트하고 언마운트할 때 라이브 쿼리 구독을 자동으로 시작하고 중지합니다. 쿼리를 수동으로 만드는 경우 구독 수명 주기를 직접 수동으로 관리해야 합니다
자세한 내용은 Live Queries 문서를 참조할 수 있습니다.
트랜잭션 뮤테이터
단순한 CRUD 작업을 넘어서는 더 복잡한 뮤테이션을 위해 TanStack DB는 뮤테이션 수명 주기를 완전히 제어하면서 사용자 지정 뮤테이션을 만들 수 있는 createOptimisticAction 및 createTransaction을 제공합니다.
다음 항목에 대한 포괄적인 문서는 Mutations guide를 참조할 수 있습니다:
createOptimisticAction를 사용하여 사용자 지정 액션 만들기createTransaction을 사용한 수동 트랜잭션- 뮤테이션 병합 동작
- 낙관적 업데이트와 비낙관적 업데이트 제어
- 임시 ID 처리
- 트랜잭션 수명 주기 상태
사용 예시
여기에서는 TanStack DB를 사용하는 두 가지 일반적인 방법을 보여줍니다:
- 기존 REST API에 TanStack Query 사용
- 백엔드와 실시간 동기화하기 위해 동기화 엔진 사용
[!TIP] 이러한 패턴을 결합할 수 있습니다. TanStack DB의 장점 중 하나는 동일한 앱에서 데이터를 로드하고 뮤테이션을 처리하는 다양한 방법을 통합할 수 있다는 점입니다. 컴포넌트는 데이터가 어디에서 왔거나 어디로 가는지 알 필요가 없습니다.
1. TanStack Query
TanStack Query를 통해 기존 REST API와 TanStack DB를 함께 사용할 수 있습니다.
단계는 다음과 같습니다:
- TanStack Query를 사용하여 데이터를 로드하는 QueryCollections 만들기
- 뮤테이션을 API 엔드포인트로 전송하여 처리하는 뮤테이션 핸들러 구현하기
import {
DbClient,
DbProvider,
collectionOptions,
useLiveQuery,
} from "@tanstack/react-db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
import { QueryClient } from "@tanstack/query-core"
const queryClient = new QueryClient()
const dbClient = new DbClient({ queryClient })
// Load data into collections using TanStack Query.
// It's common to define these in a `collections` module.
const todoCollection = collectionOptions("todos", (client) =>
queryCollectionOptions({
id: "todos",
queryKey: ["todos"],
queryClient: client.requireDependency<QueryClient>("queryClient"),
queryFn: async () => fetch("/api/todos").then((response) => response.json()),
getKey: (item) => item.id,
schema: todoSchema, // any standard schema
onInsert: async ({ transaction }) => {
const { changes: newTodo } = transaction.mutations[0]
// Handle the local write by sending it to your API.
await api.todos.create(newTodo)
},
// also add onUpdate, onDelete as needed.
})
)
const listCollection = collectionOptions("todo-lists", (client) =>
queryCollectionOptions({
id: "todo-lists",
queryKey: ["todo-lists"],
queryClient: client.requireDependency<QueryClient>("queryClient"),
queryFn: async () =>
fetch("/api/todo-lists").then((response) => response.json()),
getKey: (item) => item.id,
schema: todoListSchema,
onInsert: async ({ transaction }) => {
const { changes: newTodo } = transaction.mutations[0]
// Handle the local write by sending it to your API.
await api.todoLists.create(newTodo)
},
// also add onUpdate, onDelete as needed.
})
)
const Todos = () => {
// Read the data using live queries. Here we show a live
// query that joins across two collections.
const { data: todos } = useLiveQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.join(
{ list: listCollection },
({ todo, list }) => eq(list.id, todo.list_id),
"inner"
)
.where(({ list }) => eq(list.active, true))
.select(({ todo, list }) => ({
id: todo.id,
text: todo.text,
status: todo.status,
listName: list.name,
})),
})
// ...
}
const App = () => (
<DbProvider client={dbClient}>
<Todos />
</DbProvider>
)
이 패턴을 사용하면 기존 TanStack Query 애플리케이션 또는 REST API를 기반으로 구축된 모든 애플리케이션을 매우 빠른 컬렉션 간 라이브 쿼리와 자동으로 관리되는 낙관적 상태를 갖춘 로컬 낙관적 뮤테이션으로 확장할 수 있습니다.
2. 동기화 엔진
TanStack DB를 사용하는 가장 강력한 방법 중 하나는 동기화 엔진을 사용하여 실시간 동기화를 지원하는 완전한 로컬 우선 환경을 구축하는 것입니다. 이를 통해 기존 앱에 동기화를 점진적으로 도입하면서도 기존 API로 쓰기 작업을 처리할 수 있습니다.
동기화 엔진을 사용하는 이유
TanStack DB는 REST API와도 훌륭하게 작동하지만, 동기화 엔진은 강력한 이점을 제공합니다:
- 간편한 실시간 업데이트: WebSocket 배관 작업이 필요 없습니다. 데이터베이스에 쓰면 변경 사항이 모든 클라이언트로 자동 스트리밍됩니다
- 자동 부수 효과: 뮤테이션이 테이블 간 연쇄 변경을 트리거하면 수동 캐시 무효화 없이 영향을 받는 모든 데이터가 자동으로 동기화됩니다
- 효율적인 델타 업데이트: 변경된 행만 네트워크를 통해 전송되므로 클라이언트 측에서 대규모 데이터 세트를 실용적으로 로드할 수 있습니다
이 패턴을 사용하면 Linear와 Figma 같은 앱을 즉각적으로 느껴지게 하는 "한 번에 모두 로드" 방식을 구현할 수 있습니다.
여기에서는 ElectricSQL을 동기화 엔진으로 사용하여 이 패턴을 보여주지만, 이 패턴은 PowerSync, RxDB 및 TrailBase와 같은 다른 동기화 엔진에서도 작동합니다.
import type {
MutationFn,
PendingMutation,
} from "@tanstack/react-db"
import { collectionOptions, useDbClient } from "@tanstack/react-db"
import { electricCollectionOptions } from "@tanstack/electric-db-collection"
export const todoCollection = collectionOptions(
electricCollectionOptions({
id: "todos",
schema: todoSchema,
// Electric syncs data using "shapes". These are filtered views
// on database tables that Electric keeps in sync for you.
shapeOptions: {
url: "https://api.electric-sql.cloud/v1/shape",
params: {
table: "todos",
},
},
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const response = await api.todos.create(transaction.mutations[0].modified)
return { txid: response.txid }
},
// You can also implement onUpdate, onDelete as needed.
})
)
const AddTodo = () => {
const todosCollection = useDbClient().collection(todoCollection)
return (
<Button
onClick={() => todosCollection.insert({ text: "🔥 Make app faster" })}
/>
)
}
React Native
React Native에서 TanStack DB를 사용할 때는 React Native에 기본적으로 crypto.randomUUID()가 포함되어 있지 않으므로 UUID 생성 라이브러리를 설치하고 구성해야 합니다.
react-native-random-uuid 패키지를 설치합니다:
npm install react-native-random-uuid
그런 다음 React Native 앱의 진입점(예: App.js 또는 index.js)에서 이를 가져옵니다:
import "react-native-random-uuid"
이 폴리필은 TanStack DB가 고유 식별자를 생성하기 위해 내부적으로 사용하는 crypto.randomUUID() 함수를 제공합니다.
추가 정보
TanStack DB 사용에 관해 질문이 있거나 도움이 필요하면 Discord에서 알리거나 GitHub 토론을 시작할 수 있습니다: