跳到主要内容

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
);
}

最佳实践

  1. 设置合理的 limit:根据 UI 需求设置,通常 20-100 条
  2. 避免大偏移量:offset 超过 1000 时考虑使用 findByCursor
  3. 使用索引字段:where 条件和 orderBy 尽量使用有索引的字段
  4. 结合 count:分页时同时获取总数,提供完整的分页信息
  5. 缓存查询结果:对于不常变化的数据,考虑缓存查询结果

参考