You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中RTK Query顺序执行查询:处理数据依赖问题

RTK Query 顺序执行依赖查询的解决方案

问题背景

在React项目中使用RTK Query时,存在两个关联查询:

  • useGetProfileByUserIdQuery:根据用户ID获取用户信息,其中包含classId
  • useGetScheduleByClassIdAndDayQuery:需要classId作为参数获取对应班级的日程

初始渲染时两个查询会同时执行,导致第二个查询的classId为undefined,尝试使用skip参数但未完全解决问题。

原核心代码片段

const currentUser = useSelector(selectCurrentUser);
const userId = currentUser.userId;

const { data: profileData } = useGetProfileByUserIdQuery(userId);
const classId = profileData?.data?.class?.id;

const { data: schedules, isLoading: getSchedulesLoading } =
useGetScheduleByClassIdAndDayQuery({ classId, day: selectedDay });

推荐方案:精准使用skip参数+监听前置查询状态

RTK Query的skip参数是处理依赖查询的最优方案,之前的问题在于skip条件不够严谨,未结合前置查询的状态判断。

1. 完善前置查询的状态监听

首先获取useGetProfileByUserIdQuery的加载状态和成功状态,确保只有当查询成功返回数据后,才执行后续查询:

const { 
  data: profileData, 
  isLoading: profileLoading, 
  isSuccess: profileSuccess 
} = useGetProfileByUserIdQuery(userId);
const classId = profileData?.data?.class?.id;

2. 设置第二个查询的skip条件

skip的触发条件应为:当classId不存在,或前置查询未成功完成时,跳过第二个查询:

const { data: schedules, isLoading: getSchedulesLoading } =
  useGetScheduleByClassIdAndDayQuery(
    { classId, day: selectedDay },
    { skip: !classId || !profileSuccess }
  );

3. 优化组件渲染逻辑

在UI中优先处理前置查询的加载状态,提升用户体验:

// 先展示profile加载状态
{profileLoading ? (
  <Box style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "20px" }}>
    <CircularProgress />
    <Typography variant="subtitle1" align="center">
      Memuat profil...
    </Typography>
  </Box>
) : getSchedulesLoading ? (
  // 再展示日程加载状态
  <Box style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "20px" }}>
    <CircularProgress />
    <Typography variant="subtitle1" align="center">
      Memuat jadwal...
    </Typography>
  </Box>
) : schedules?.length ? (
  // 展示日程列表
  <Box>
    {schedules.map((schedule) => (
      <div key={schedule.id}>
        <Typography variant="subtitle1">
          {schedule.day}: {formatTime(schedule.start_time)} - {formatTime(schedule.end_time)}
        </Typography>
        <Typography variant="body2">
          Guru: {schedule.teacher.full_name}, Kelas: {schedule.class.name}, Mata Pelajaran: {schedule.subject.name}
        </Typography>
      </div>
    ))}
  </Box>
) : (
  // 无数据状态
  <Box style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "20px" }}>
    <Typography variant="subtitle1" align="center" gutterBottom>
      Tidak ada data.
    </Typography>
  </Box>
)}

完整优化后的组件代码

import {
  Box,
  CircularProgress,
  Container,
  Paper,
  Tab,
  Tabs,
  Typography,
} from "@mui/material";
import { useState } from "react";
import { useGetScheduleByClassIdAndDayQuery } from "../schedulesApiSlice";
import { useSelector } from "react-redux";
import { selectCurrentUser } from "../../auth/authSlice";
import { useGetProfileByUserIdQuery } from "../../profile/profileApiSlice";

const formatTime = (dateString) => {
  const options = { hour: "numeric", minute: "numeric" };
  return new Date(dateString).toLocaleTimeString(undefined, options);
};

const StudentScheduleList = () => {
  const [value, setValue] = useState(0);
  const [selectedDay, setSelectedDay] = useState("Senin");

  const currentUser = useSelector(selectCurrentUser);
  const userId = currentUser.userId;

  // 监听前置查询的状态
  const { 
    data: profileData, 
    isLoading: profileLoading, 
    isSuccess: profileSuccess 
  } = useGetProfileByUserIdQuery(userId);
  const classId = profileData?.data?.class?.id;

  // 设置skip条件控制查询执行时机
  const { data: schedules, isLoading: getSchedulesLoading } =
    useGetScheduleByClassIdAndDayQuery(
      { classId, day: selectedDay },
      { skip: !classId || !profileSuccess }
    );

  const handleChange = (event, newValue) => {
    setValue(newValue);
    const selectedDayLabel = [
      "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"
    ][newValue];
    setSelectedDay(selectedDayLabel);
  };

  return (
    <Container sx={{ width: "100%" }}>
      <Typography variant="h4" gutterBottom>
        Daftar Jadwal
      </Typography>

      <Paper>
        <Box
          sx={{
            maxWidth: { xs: 320, sm: 480, md: 720, lg: 960, xl: 1280 },
            bgcolor: "background.paper",
            marginX: "auto",
          }}
        >
          <Tabs
            value={value}
            onChange={handleChange}
            variant="scrollable"
            scrollButtons
            allowScrollButtonsMobile
            aria-label="scrollable"
          >
            <Tab label="Senin" />
            <Tab label="Selasa" />
            <Tab label="Rabu" />
            <Tab label="Kamis" />
            <Tab label="Jumat" />
            <Tab label="Sabtu" />
            <Tab label="Minggu" />
          </Tabs>

          {/* 分层处理加载状态 */}
          {profileLoading ? (
            <Box style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "20px" }}>
              <CircularProgress />
              <Typography variant="subtitle1" align="center">
                Memuat profil...
              </Typography>
            </Box>
          ) : getSchedulesLoading ? (
            <Box style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "20px" }}>
              <CircularProgress />
              <Typography variant="subtitle1" align="center">
                Memuat jadwal...
              </Typography>
            </Box>
          ) : schedules?.length ? (
            <Box>
              {schedules.map((schedule) => (
                <div key={schedule.id}>
                  <Typography variant="subtitle1">
                    {schedule.day}: {formatTime(schedule.start_time)} - {formatTime(schedule.end_time)}
                  </Typography>
                  <Typography variant="body2">
                    Guru: {schedule.teacher.full_name}, Kelas: {schedule.class.name}, Mata Pelajaran: {schedule.subject.name}
                  </Typography>
                </div>
              ))}
            </Box>
          ) : (
            <Box style={{ display: "flex", flexDirection: "column", alignItems: "center", padding: "20px" }}>
              <Typography variant="subtitle1" align="center" gutterBottom>
                Tidak ada data.
              </Typography>
            </Box>
          )}
        </Box>
      </Paper>
    </Container>
  );
};

export default StudentScheduleList;

可选方案:使用useLazyQuery手动触发查询

如果需要更灵活的触发逻辑(比如结合其他状态变化),可以使用useLazyQuery,在前置查询成功后手动调用:

import { useLazyQuery } from '@reduxjs/toolkit/query/react';
// ...
const [getSchedules, { data: schedules, isLoading: getSchedulesLoading }] = useLazyQuery(useGetScheduleByClassIdAndDayQuery);

useEffect(() => {
  // 当前置查询成功且classId存在时,触发日程查询
  if (profileSuccess && classId) {
    getSchedules({ classId, day: selectedDay });
  }
}, [profileSuccess, classId, selectedDay, getSchedules]);

这种方式适合复杂场景,但skip参数方案更简洁,是RTK Query官方推荐的依赖查询处理方式。


内容的提问来源于stack exchange,提问作者Kartono bin Saleh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 02:14:57