重写duozhuavue书架

Apollo Client 缓存处理小结 中提到了书架功能存在的问题:

每一本书籍对应一个查询(即一个 HTTP 请求),当从未登录状态切换到登录状态时,主页所有已经加载的书籍都会发送请求,判断它是否在当前用户的书架上。

理想的实现中,在用户登录后,应该只发送一个请求,用于获取书单信息流。

改进

后端

  1. 将查询 isBookInBookshelf 移动到 Book 类型下
1
2
3
4
5
6
const bookType = `
type Book {
...
isBookInBookshelf(userId: ID!): Boolean!
}
`;
  1. 实现该字段的 resolver 函数
1
2
3
4
5
6
7
8
9
10
11
const bookResolver = {
...
Book: {
...
isBookInBookshelf: async({ id }, { userId } , { models }) => {
if(userId === "") return false;
const user = await models.User.findById(userId);
return user.bookShelf.indexOf(id) !== -1;
}
}
};

前端

  1. 修改 GET_CATEGORY_FEED schema,接收 userId 参数,返回的书籍中包含 isBookInBookshelf 字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
export const GET_CATEGORY_FEED = gql`
query getCategoryFeed(
...
$userId: ID!
) {
categoryFeed(first: $first, after: $after) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
name
items(first: $itemsFirst, after: $itemsAfter) {
pageInfo {
endCursor
hasNextPage
}
edges {
node {
id
...
isBookInBookshelf(userId: $userId)
}
}
}
}
}
}
}
`;
  1. 在组件中修改查询,传入参数 userId
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const userId = useLoggedInUserId();
const {
result: categoryFeedResult,
loading: categoryFeedLoading,
error: categoryFeedError,
fetchMore,
networkStatus,
} = useQuery(
GET_CATEGORY_FEED,
() => ({
after: after.value,
first: first.value,
itemsAfter: "",
itemsFirst: 3,
userId
}),
{
notifyOnNetworkStatusChange: true,
}
);
  1. 修改缓存的处理方式
1
2
3
4
5
6
7
8
9
10
11
12
const cache = new InMemoryCache({
typePolicies: {
...
Book: {
fields: {
isBookInBookshelf: {
keyArgs: ["userId"]
}
}
}
},
});
  1. 修改添加书籍或删除书籍时的逻辑
    乐观更新:先修改客户端状态,再发送网络请求。如果请求失败,重置客户端状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const addToBookShelf = () => {
// 乐观更新,先修改客户端状态
isInBookshelf.value = !isInBookshelf.value;
// 再发送请求(send mutation
toggleBookshelf();
};

const { mutate: toggleBookshelf, onDone: onToggle } = useMutation(
TOGGLE_BOOKSHELF_MUTATION,
() => ({
variables: {
bookId: bookId.value,
userId,
},
})
);

onToggle(({ data: { toggleBookshelf } }) => {
if (toggleBookshelf.success === true) {
toast.success(toggleBookshelf.message);
} else {
// 请求失败,重置客户端状态
isInBookshelf.value = !isInBookshelf.value;
toast.info(toggleBookshelf.message);
}
});