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 使用
用法
新建后保持排序
getPosts
NewPost
PostList
UserList
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,因为它同样包含价格数据。
探索 coin-app 示例