find
查询多个实体,支持分页、排序和复杂查询条件。这是最常用的批量查询方法。
方法签名
find(options: FindOptions<T>): Observable<InstanceType<T>[]>
参数说明
interface FindOptions<T> {
// 查询条件
where: RuleGroup<InstanceType<T>>;
// 排序条件(可选)
orderBy?: OrderBy[];
// 获取数据量
// 默认值: 100
limit?: number;
// 分页偏移量
// 默认值: 0
offset?: number;
}
使用场景
1. 基础分页查询
import { firstValueFrom, switchMap } from 'rxjs';
// 获取前 20 条待办事项
const todos = await rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: []
},
limit: 20,
offset: 0
})),
firstValueFrom
);
2. 带排序的查询
// 按创建时间降序,ID 升序排列
const sortedTodos = await rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: []
},
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
],
limit: 50
})),
firstValueFrom
);
3. 带过滤条件的查询
// 查询已完成的待办事项
const completedTodos = await rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: [
{
field: 'completed',
operator: '=',
value: true
}
]
},
orderBy: [
{ field: 'completedAt', sort: 'desc' }
],
limit: 100
})),
firstValueFrom
);
4. 复杂条件查询
// 查询高优先级或即将到期的未完成任务
const urgentTodos = await rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: [
{
field: 'completed',
operator: '=',
value: false
},
{
combinator: 'or',
rules: [
{
field: 'priority',
operator: '=',
value: 'high'
},
{
field: 'dueDate',
operator: '<=',
value: new Date(Date.now() + 86400000) // 24小时内
}
]
}
]
},
orderBy: [
{ field: 'dueDate', sort: 'asc' }
],
limit: 50
})),
firstValueFrom
);
5. 分页实现
interface PaginationParams {
page: number;
pageSize: number;
}
async function getTodosByPage({ page, pageSize }: PaginationParams) {
return rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: []
},
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
],
limit: pageSize,
offset: (page - 1) * pageSize
})),
firstValueFrom
);
}
// 获取第一页,每页 20 条
const page1 = await getTodosByPage({ page: 1, pageSize: 20 });
// 获取第二页
const page2 = await getTodosByPage({ page: 2, pageSize: 20 });
6. 在组件中使用
Angular
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-todo-list',
template: `
<div *ngFor="let todo of todos$ | async">
<h3>{{ todo.title }}</h3>
<p>{{ todo.description }}</p>
</div>
`
})
export class TodoListComponent implements OnInit {
todos$!: Observable<Todo[]>;
ngOnInit() {
this.todos$ = rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: [
{ field: 'completed', operator: '=', value: false }
]
},
orderBy: [
{ field: 'priority', sort: 'desc' },
{ field: 'createdAt', sort: 'asc' }
],
limit: 50
}))
);
}
}
React
import { useEffect, useState } from 'react';
import { firstValueFrom, switchMap } from 'rxjs';
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const [page, setPage] = useState(1);
const pageSize = 20;
useEffect(() => {
const subscription = rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: []
},
orderBy: [
{ field: 'createdAt', sort: 'desc' }
],
limit: pageSize,
offset: (page - 1) * pageSize
}))
).subscribe(setTodos);
return () => subscription.unsubscribe();
}, [page]);
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<h3>{todo.title}</h3>
<p>{todo.description}</p>
</div>
))}
<button onClick={() => setPage(p => Math.max(1, p - 1))}>上一页</button>
<button onClick={() => setPage(p => p + 1)}>下一页</button>
</div>
);
}
Vue
<template>
<div>
<div v-for="todo in todos" :key="todo.id">
<h3>{{ todo.title }}</h3>
<p>{{ todo.description }}</p>
</div>
<button @click="prevPage">上一页</button>
<button @click="nextPage">下一页</button>
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
import { switchMap } from 'rxjs';
const todos = ref<Todo[]>([]);
const page = ref(1);
const pageSize = 20;
watchEffect(() => {
const subscription = rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules: []
},
orderBy: [
{ field: 'createdAt', sort: 'desc' }
],
limit: pageSize,
offset: (page.value - 1) * pageSize
}))
).subscribe(value => {
todos.value = value;
});
return () => subscription.unsubscribe();
});
function prevPage() {
if (page.value > 1) page.value--;
}
function nextPage() {
page.value++;
}
</script>
7. 搜索功能实现
async function searchTodos(keyword: string) {
return rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'or',
rules: [
{
field: 'title',
operator: 'contains',
value: keyword
},
{
field: 'description',
operator: 'contains',
value: keyword
}
]
},
orderBy: [
{ field: 'createdAt', sort: 'desc' }
],
limit: 100
})),
firstValueFrom
);
}
// 搜索包含 "重要" 的待办事项
const results = await searchTodos('重要');
find vs findAll vs findByCursor
| 方法 | 分页方式 | 性能 | 适用场景 |
|---|---|---|---|
find | 基于偏移量 | 中等 | 传统分页,需要跳页 |
findAll | 无分页 | 可能较慢 | 小数据集,需要全部数据 |
findByCursor | 基于游标 | 高 | 无限滚动,大数据集 |
// find - 传统分页
const page1 = await rxdb.pipe(
switchMap(() => Todo.find({
where: { combinator: 'and', rules: [] },
limit: 20,
offset: 0
})),
firstValueFrom
);
// findAll - 获取所有数据
const allTodos = await rxdb.pipe(
switchMap(() => Todo.findAll({
where: { combinator: 'and', rules: [] }
})),
firstValueFrom
);
// findByCursor - 游标分页
const cursorPage = await rxdb.pipe(
switchMap(() => Todo.findByCursor({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
],
limit: 20
})),
firstValueFrom
);
性能优化
1. 合理设置 limit
// ✅ 好:根据需求设置合理的 limit
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 50 // 根据 UI 显示需求设置
});
// ❌ 不好:limit 过大导致性能问题
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 10000 // 过大
});
2. 使用索引字段排序
// ✅ 好:使用索引字段排序
Todo.find({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' }, // createdAt 有索引
{ field: 'id', sort: 'asc' } // id 是主键
],
limit: 20
});
// ⚠️ 慢:使用非索引字段排序
Todo.find({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'description', sort: 'asc' } // 非索引字段
],
limit: 20
});
3. 避免大偏移量
// ⚠️ 性能问题:大偏移量
Todo.find({
where: { combinator: 'and', rules: [] },
limit: 20,
offset: 10000 // 偏移量过大,性能差
});
// ✅ 更好的方案:使用 findByCursor
Todo.findByCursor({
where: { combinator: 'and', rules: [] },
orderBy: [
{ field: 'createdAt', sort: 'desc' },
{ field: 'id', sort: 'asc' }
],
after: lastItem,
limit: 20
});
常见模式
1. 带总数的分页
async function getTodosWithTotal(page: number, pageSize: number) {
const where = {
combinator: 'and' as const,
rules: []
};
const [items, total] = await Promise.all([
rxdb.pipe(
switchMap(() => Todo.find({
where,
orderBy: [
{ field: 'createdAt', sort: 'desc' as const }
],
limit: pageSize,
offset: (page - 1) * pageSize
})),
firstValueFrom
),
rxdb.pipe(
switchMap(() => Todo.count({ where })),
firstValueFrom
)
]);
return {
items,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
2. 过滤 + 搜索 + 分页
interface TodoQueryParams {
keyword?: string;
completed?: boolean;
priority?: string;
page: number;
pageSize: number;
}
async function queryTodos(params: TodoQueryParams) {
const rules: any[] = [];
// 搜索关键词
if (params.keyword) {
rules.push({
combinator: 'or',
rules: [
{ field: 'title', operator: 'contains', value: params.keyword },
{ field: 'description', operator: 'contains', value: params.keyword }
]
});
}
// 完成状态
if (params.completed !== undefined) {
rules.push({
field: 'completed',
operator: '=',
value: params.completed
});
}
// 优先级
if (params.priority) {
rules.push({
field: 'priority',
operator: '=',
value: params.priority
});
}
return rxdb.pipe(
switchMap(() => Todo.find({
where: {
combinator: 'and',
rules
},
orderBy: [
{ field: 'createdAt', sort: 'desc' }
],
limit: params.pageSize,
offset: (params.page - 1) * params.pageSize
})),
firstValueFrom
);
}
最佳实践
- 设置合理的 limit:根据 UI 需求设置,通常 20-100 条
- 避免大偏移量:offset 超过 1000 时考虑使用 findByCursor
- 使用索引字段:where 条件和 orderBy 尽量使用有索引的字段
- 结合 count:分页时同时获取总数,提供完整的分页信息
- 缓存查询结果:对于不常变化的数据,考虑缓存查询结果
参考
- findAll - 查询所有数据
- findByCursor - 基于游标的分页查询
- findOne - 查询单个实体
- count - 统计数量
- 查询操作符 - 查询条件操作符详解