react-query手把手教程11-用placeholderData及initialData提升用户体验

937 阅读5分钟

该系列其他文章可以点击查看专栏👉🏻👉🏻react-query手把手教程系列

介绍

在很多情况下,你没有必要为用户展示loading动画,因为你可以使用当前的一些缓存数据来默认填充UI的显示界面。毕竟很多人非常讨厌菊花动画,转的人心烦意乱(笔者就是其中之一)。这也是提升用户体验的一种办法。

本篇文章将会向你介绍,应该如何使用placeholderData以及initialData配置项。

实践

Placeholder Data

其实这个标题可以翻译为占位数据,但是一旦换成占位数据,大家的观感就没有那么强。说起placeholder,大家一定就会想起各种三方UI库中,输入组件的placeholder属性。比如下面的提示Enter your username

image.png

placeholder的数据仅仅是一个非常不重要的内容,一旦用户输入了数据,这个placeholder就会被立即替代。

有了比照之后,现在就来看看react-query中的placeholderData配置项。

const issuesQuery = useQuery(
  ["issues", repo, org], 
  fetchIssues,
  {
    // ①
    placeholderData: [],
  }
)

①:在还未第一次请求到数据之前的这段时间,如果获取issuesQuery.data的值,此时应该为[],一旦issuesQuery查询从后端获取到了数据,此时issuesQuery.data的值将会被替换为实际的后端值。

其实placeholderData类似于解构时;如果解构项的值为undefined,将会采用开发者设置的默认值,比如下面的例子:

const { data = [] } = useQuery(
  ["issues", repo, org], 
  fetchIssues,
)

如果解构时dataundefined,将会默认赋值[],与上面的效果一致。

当你需要为用户提供假数据时,就需要使用placeholderData

比如在获取用户数据时,为了UI不那么难看,可以先默认展示一个占位头像,这里默认展示掘金酱的头像:

const UserAvatar = () => {
  const userQuery = useQuery(
    ["user"],
    fetchUser,
    {
      placeholderData: {
        avatar_url: "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/mirror-assets/168e0858b6ccfd57fe5~tplv-t2oaga2asx-no-mark:180:180:180:180.awebp",
      }
    }
  )

  return (
    <img src={userQuery.data.avatar_url} alt="Avatar" />
  )
}

但是请注意,之前有介绍过依赖查询,假如某个依赖查询,依赖了该数据进行判断,就会出现问题。此时需要使用isPlaceholderData属性来判断当前数据是否真实:

如果有遗忘了依赖查询的同学,请点击这里补课👉🏻react-query手把手教程③-并行请求及依赖请求

const userQuery = useQuery(
  ["user"],
  fetchUser,
)
const userIssues = useQuery(
  ["userIssues", userQuery.data.login],
  fetchUserIssues,
  {
    enabled: !userQuery.isPlaceholderData 
      && userQuery.data?.login,
  }
)

在上面的例子中,如果当前用户数据是一个PlaceholderData数据,那么就不去执行userIssues查询。

请注意!!

placeholderData并不会存储在缓存中,因此它只是用于处理虚假数据使用!!如果你希望使用真实数据进行预加载并将该数据放入缓存中,请使用下面介绍的initialData

Initial Data

placeholderData相比initialData就属于真实数据的一种。

比如在页面刚刚加载时,你有一些硬编码的数据,需要在前端展示,此时就需要将这个数据灌入initialData中,下面的代码中,系统会硬编码一位管理员,并将其作为初始化的数据传入,此时数据虽然不是从后端请求的,但是一样与后端的数据类似,不是占位数据,因此需要在initialData中传入:

const hardCodedAdminUsers = [
  {
    id: 1,
    login: "orange",
    name: "修仙大橙子",
    avatar_url: "https://p3-passport.byteimg.com/img/user-avatar/346a615dd202d44344742d6b177c0f65~100x100.awebp",
  },
]
const adminUsersQuery = useQuery(
  ["adminUsers"],
  fetchAdminUsers,
  {
    initialData: hardCodedAdminUsers,
  }
)

拓展一下上面的例子,假设你为数据设置了过期时间(staleTime),那么此时传入的initialData将会在这个时间(staleTime)前都是最新(fresh)的,即使你触发了默认刷新的动作,但是由于数据不是过期状态(slate),此时react-query也不会帮你刷新数据。

不过你可以设置一个initialDataUpdatedAt选项来解决这个问题,initialData将会在initialDataUpdatedAt + staleTime时间后过期。

例子如下:

const adminUsersQuery = useQuery(
  ["adminUsers"],
  fetchAdminUsers,
  {
    initialData: hardCodedAdminUsers,
    staleTime: 1000 * 60 * 60 * 24, // 1 day
    initialDataUpdatedAt: 1677073883689, // 2023-02-22 21:51:23(中国标准时间) 
  }
)

上面的例子中,这个hardCodedAdminUsers数据在2023-02-23 21:51:23之前都不会过期,因此不会触发任何自动化的刷新查询操作,不过一旦过期后,如果满足条件就会触发刷新查询操作。

从其它查询中获取初始化数据(Initial Data)

在平时的开发中,大家都有经验,往往展示的列表页中的信息,通常都会包含许多详情页的数据,此时就可以使用列表页面的数据,作为初始化数据:

假如你有一个列表页查询是这样的:

const issuesListQuery = useQuery(
  ["issues", repo, owner],
  fetchIssues
);

此时列表页数据中会包含很多列表项数据,此时一旦用户打开详情页,就在之前加载过的列表页数据中,寻找当前详情页的数据信息,作为initialData,假代码如下:

const queryClient = useQueryClient();
const issueDetailQuery = useQuery(
  ["issue", repo, owner, issueNumber],
  fetchIssue,
  {
    initialData: () => {
      // ①
      const issues = queryClient.getQueryData(["issues", repo, owner])
      
      if (!issues) return undefined;
      
      // ②
      const issue = issues.find(issue => issue.number === issueNumber)
      return issue;
    }
  },
)

①:通过queryClient查询当前缓存中所有的列表数据内容

②:从缓存中寻找当前详情页需要的缓存内容

如果你不需要缓存数据,也可以作为placeholderData数据传入。

getQueryState

在之前硬编码管理员的例子中,可以自己手动传入初始化数据的生成时间。但是在刚刚的例子中,这个数据是从别的查询中获得的,该怎么设置时间呢?

react-query提供了queryClient.getQueryState方法,让你可以知道该查询被查询了多少次、查询是否包含错误以及最后一次的更新时间(dataUpdatedAt)。

因此可以向initialDataUpdatedAt中传入这个值,例子如下:

const issueDetailQuery = useQuery(
  ["issue", repo, owner, issueNumber],
  fetchIssue,
  {
    staleTime: 1000 * 60,
    initialData: () => {
      const issues = queryClient.getQueryData(["issues", repo, owner])
      if (!issues) return undefined;
      const issue = issues.find(issue => issue.number === issueNumber)
      return issue;
    }, 
    initialDataUpdatedAt: () => {
      const {dataUpdatedAt} = queryClient.getQueryState(["issues", repo, owner])
      return dataUpdatedAt;
    }
  },
)

此时你就可以精确地控制缓存的过期时间,将新旧数据做很好的区分,方便react-query更合理地帮你处理相关查询。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19 天,点击查看活动详情