跳到主要内容

Query

Query 让你可以以编程方式访问 Reactive Data Client 缓存,同时保持 Reactive Data Client 应有的高性能和引用相等性保证。

可以使用 schema 查找 hook useQuery() 来渲染 Query

Query 成员​

schema​

用于从 Reactive Data Client 缓存中检索/反规范化数据的 Schema。它接受任何 Queryable schema:Entity、All、Collection、Query、 Union、Scalar,以及用于连接多个 Entity 的 Object schema。 Lazy 字段可以通过其 .query 访问器得到一个 Queryable。

process(entries, ...args)​

接收(反规范化后的)响应作为 entries 以及参数,并返回新的响应,供 useQuery 使用

用法​

新建后保持排序​

import { Collection, Entity, Query, RestEndpoint } from '@data-client/rest';

export class Post extends Entity {
  id = '';
  title = '';
  group = '';
  author = '';
}

export const getPosts = new RestEndpoint({
  path: '/:group/posts',
  searchParams: {} as { orderBy?: string; author?: string },
  schema: new Query(
    new Collection([Post], {
      nonFilterArgumentKeys: /orderBy/,
    }),
    (posts, { orderBy } = {}) => {
      if (orderBy) {
        return [...posts].sort((a, b) =>
          a[orderBy].localeCompare(b[orderBy]),
        );
      }
      return posts;
    },
  ),
});
结果
Store▶

聚合​

Fixtures
GET /users
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"},{"id":"777","name":"Albatras","isAdmin":true}]
▶resources/User
▶UsersPage
import { All, Query } from '@data-client/rest';
import { useQuery, useFetch } from '@data-client/react';
import { UserResource, User } from './resources/User';

const countUsers = new Query(
  new All(User),
  (entries, { isAdmin } = {}) => {
    if (isAdmin !== undefined)
      return entries.filter(user => user.isAdmin === isAdmin).length;
    return entries.length;
  },
);

function UsersPage() {
  useFetch(UserResource.getList);
  const userCount = useQuery(countUsers);
  const adminCount = useQuery(countUsers, { isAdmin: true });
  if (userCount === undefined) return <div>No users in cache yet</div>;
  return (
    <div>
      <div>Total users: {userCount}</div>
      <div>Total admins: {adminCount}</div>
    </div>
  );
}
render(<UsersPage />);
结果
Store▶

使用 groupBy 聚合重新组织数据​

▶resources/User
▶resources/Todo
▶TodoByUser
▶TodoJoined
import { Query } from '@data-client/rest';
import { useQuery, useFetch, useSuspense } from '@data-client/react';
import { TodoResource } from './resources/Todo';
import { UserResource } from './resources/User';
import TodoByUser from './TodoByUser';

const groupTodoByUser = new Query(
  TodoResource.getList.schema,
  todos => Object.groupBy(todos, todo => todo.userId),
);

function TodosPage() {
  useFetch(UserResource.getList);
  useSuspense(TodoResource.getList);
  useSuspense(UserResource.getList);
  const todosByUser = useQuery(groupTodoByUser);
  if (!todosByUser) return <div>Todos not found</div>;
  return (
    <div>
      {Object.keys(todosByUser).slice(5).map(userId => (
        <TodoByUser
          key={userId}
          userId={userId}
          todos={todosByUser[userId]}
        />
      ))}
    </div>
  );
}
render(<TodosPage />);
结果
Store▶

Object Schema 连接​

Query 可以接受 Object Schema,从而跨多种 Entity 类型进行连接。这让你可以在单个查询中组合来自不同 Entity 的数据。

Fixtures
GET /tickers/BTC-USD
{"product_id":"BTC-USD","price":45000}
GET /stats/BTC-USD
{"product_id":"BTC-USD","last":44950}
▶resources/Ticker
▶resources/Stats
▶PriceDisplay
import { Query } from '@data-client/rest';
import { useQuery, useFetch } from '@data-client/react';
import { TickerResource, Ticker } from './resources/Ticker';
import { StatsResource, Stats } from './resources/Stats';

// Join Ticker and Stats by product_id
const queryPrice = new Query(
  { ticker: Ticker, stats: Stats },
  ({ ticker, stats }) => ticker?.price ?? stats?.last,
);

function PriceDisplay({ productId }: { productId: string }) {
  useFetch(TickerResource.get, { product_id: productId });
  useFetch(StatsResource.get, { product_id: productId });
  const price = useQuery(queryPrice, { product_id: productId });
  
  if (price === undefined) return <div>Loading...</div>;
  return <div>Price: ${price}</div>;
}

render(<PriceDisplay productId="BTC-USD" />);
结果
Store▶

fallback 连接​

在这个例子中,Ticker 会通过 websocket 流不断更新。然而 Ticker 没有批量/列表获取接口——因此在列表视图中获取价格的效率很低。

所以这里我们可以获取 Stats 列表作为 fallback,因为它同样包含价格数据。

更多演示