该系列其他文章可以点击查看专栏👉🏻👉🏻react-query手把手教程系列
场景
在前一篇文章中,修改github的用户名时,如果前端正在请求后端修改时,将会出加载中的提示。一般情况下,这种修改用户名的请求多数都会成功,为了减少用户烦躁的等待,你可以立即将用户修改的新用户名显示在界面上。
上面的这种行为,可以称作乐观更新。其实非常容易能够理解,你会乐观的估计后端一定可以更新成功,提前将用户修改的内容展现给他。
实践
基于评论组件演示乐观更新
还是以github为例,下面的代码是一个issue评论列表的伪代码:
// 获取当前issue评论列表内容
async function fetchIssueComments({
org,
repo,
issueNumber,
}) {
const response = await fetch(
`https://api.github.com/repos/${org}/${repo}/issues/${issueNumber}/comments`
);
if (!response.ok) {
throw new Error(
`Could not fetch comments for issue ${issueNumber}`
);
}
return response.json();
}
// 评论组件
const Comment = ({ comment }) => {
return (
<div>
<div className="comment-header">
<div className="comment-author">
{comment.user.login}
</div>
<div className="comment-date">
{new Date(comment.created_at).toLocaleDateString()}
</div>
</div>
<div>{comment.body}</div>
</div>
);
};
// issue评论列表组件
const IssueComments = ({ org, repo, issueNumber }) => {
const commentsQuery = useQuery(
["comments", org, repo, issueNumber],
fetchIssueComments
);
return commentsQuery?.data?.map((comment) => (
<Comment key={comment.id} comment={comment} />
));
};
可以看到上面代码的评论组件中,将会展示评论内容、当前评论人信息以及创建评论的日期。接下来的操作过程,将会对以上这三个内容进行乐观更新。
常规操作
下面的代码是常规提交评论的操作伪代码:
// ①
function addComment({ org, repo, issueNumber, comment }) {
return fetch(
`https://api.github.com/repos/${org}/${repo}/issues/${issueNumber}/comments`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ body: comment }),
}
).then((res) => res.json());
}
// ②
function CommentForm({ org, repo, issueNumber }) {
const queryClient = useQueryClient();
const addCommentMutation = useMutation(addComment, {
// ③
onSuccess: (data, variables) => {
const { org, repo, issueNumber } = variables;
queryClient.setQueryData(
["comments", org, repo, issueNumber],
(comments) => comments.concat(data)
);
},
// ④
onSettled: (data, error, variables) => {
const { org, repo, issueNumber } = variables;
queryClient.invalidateQueries([
"comments",
org,
repo,
issueNumber,
]);
},
});
return <form>{/* ... */}</form>;
}
①:实现了一个添加评论的方法,传入创建评论所需要的参数,就可以请求向github创建一条评论。
②:实现了一个提交评论表单的方法,请注意代码里面的内容,③是添加成功后向缓存中写入数据,并且不管成功失败④中都会刷新数据,保证当前展示给用户的是最新数据。
乐观更新
下面将会为大家展示,如何使用乐观更新。
首先来分析一下,如果后端能够更新成功,什么数据时前端可以确定的数据:
- 用户的评论内容
comment - 创建时间
created_at,基于发送请求的时间即可 - 当前的评论的用户名
user.login,从缓存中获取当前用户的信息
由于id并不展示给用户,其实可以通过前端生成随机数的方式,暂时用一个伪id代替,因此代码大致如下所示:
function CommentForm({ org, repo, issueNumber }) {
const queryClient = useQueryClient();
const username = useUser();
const addCommentMutation = useMutation(addComment, {
// ①
onMutate: (variables) => {
const comment = {
id: Math.random().toString(),
body: variables.comment,
created_at: new Date(),
user: {
login: username,
}
};
queryClient.setQueryData(
["comments", org, repo, issueNumber],
(comments) => comments.concat(comment)
);
},
onSettled: (data, error, variables) => {
const { org, repo, issueNumber } = variables;
queryClient.invalidateQueries([
"comments",
org,
repo,
issueNumber,
]);
},
});
return <form>{/* ... */}</form>;
}
此时一旦调用addCommentMutation.mutate方法,首先会立即触发①中的回调,展示界面先将假数据展示出来。不管成功与否都会刷新当前界面,此时真数据将会替代假数据的展示。
回滚机制
假如我在onMutate里面设置了数据,但是请求失败了怎么办?虽然onSettled会重新请求数据,但是如果请求比较慢,此时假数据就会一直展示。
为了应对这种情况react-query提供了restoreCache方法,你可以在onSuccess以及onError的回调中轻松使用该方法,代码如下:
const addCommentMutation = useMutation(addComment, {
onMutate: (variables) => {
const savedCache = queryClient.getQueryData(
["comments", org, repo, issueNumber]
);
const comment = {
id: Math.random().toString(),
body: variables.comment,
created_at: new Date(),
user: {
login: username,
}
}
queryClient.setQueryData(
["comments", org, repo, issueNumber],
(comments) => comments.concat(comment)
);
return () => {
queryClient.setQueryData(
["comments", org, repo, issueNumber],
savedCache
)
}
},
onSuccess: (data, variables, restoreCache) => {
restoreCache();
queryClient.setQueryData(
["comments", org, repo, issueNumber],
(comments) => comments.concat(data)
);
},
onSettled: (data, variables) => {
queryClient.invalidateQueries(["comments", org, repo, issueNumber]);
},
onError: (error, variables, restoreCache) => {
restoreCache();
}
})
现在我们的代码就非常完美了,一旦用户请求失败就会回滚之前设置的假数据。如果请求成功会从后端请求真实的数据覆盖之前的假数据。而在这段时间的过程中,用户会先看到一个乐观更新的数据,而不是加载动画。这样可以在某种程度上提升用户体验。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情