如何正确更改 RTK 查询中的响应数据?

rgd*_*dzv 6 reactjs redux react-redux redux-toolkit rtk-query

我正在尝试在货币转换器应用程序中使用 RTK 查询。这个应用程序是基于2个API。

首先,我正在获取一个带有货币的对象。然后,我获取一系列国家/地区并对其进行过滤,具体取决于该国家/地区是否拥有该种货币。

商店代码:

export const store = configureStore({
  reducer: {
    [currenciesAPI.reducerPath]: currenciesAPI.reducer,
    [countriesAPI.reducerPath]: countriesAPI.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(currenciesAPI.middleware, countriesAPI.middleware)
})
Run Code Online (Sandbox Code Playgroud)

API代码:

export const currenciesAPI = createApi({
  reducerPath: 'currenciesAPI',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.frankfurter.app'}),
  endpoints: (build) => ({
    fetchAllCurrencies: build.query<Currencies, void>({  
      query: () => ({
        url: '/currencies'
      }),
    })
  })
})

export const countriesAPI = createApi({
  reducerPath: 'countriesAPI',
  //tagTypes: ['Country'],
  baseQuery: fetchBaseQuery({ baseUrl: 'https://restcountries.com/v3.1'}),
  endpoints: (build) => ({
    fetchAllCountries: build.query<TransformedCountries[], string>({  
      query: () => ({
        url: '/all'
      }),
      transformResponse: (response: Countries[]) : TransformedCountries[] => {
        const countriesWithCurrency = response.filter((country: Countries) => country.currencies) // <-- MUTATING DATA HERE AND BELOW IS NORMAL?
        const transformedCountriesArray = countriesWithCurrency.map((item: Countries) => {
          const keys = Object.keys(item.currencies)
          const firstKey = keys[0]
          return {
            name: item.name.common,
            currencyFullName: item.currencies[firstKey].name,
            currencyShortName: firstKey,
            currencySymbol: item.currencies[firstKey].symbol,
            flag: item.flags.svg
          }
        })
        const finalCountriesArray = transformedCountriesArray.sort((a: TransformedCountries, b: TransformedCountries) => (a.name > b.name) ? 1 : (a.name < b.name) ? -1 : 0)
        return finalCountriesArray
      }
    })
  })
})
Run Code Online (Sandbox Code Playgroud)

组件代码:

const App: React.FC = () => {

  const { 
    data: currenciesData, 
    error: currenciesError, 
    isLoading: currenciesIsLoading 
  } = useFetchAllCurrenciesQuery()

  const { 
    data: countriesData, 
    error: countriesError, 
    isLoading: countriesIsLoading
  } = useFetchAllCountriesQuery('', {
    selectFromResult: ({ data, error, isLoading }) => ({
      data: data?.filter((country: TransformedCountries) => currenciesData && currenciesData[country.currencyShortName]), // <-- MUTATING DATA HERE AND BELOW IS NORMAL?
      error,
      isLoading
    }),
    skip: !currenciesData
  })
...
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 如果您的第二个查询取决于第一个查询的结果,在我的情况下使用“skip”或“skipToken”选项是正常做法吗?有没有更好的模式?
  2. 像我在countryAPI中那样改变“transformResponse”中的数据,然后在App组件的useFetchAllCountriesQuery中的“selectFromResult”选项中改变转换后的响应是正常的做法吗?有没有更好的模式?
  3. 在这种情况下,处理组件中的两个“isLoading”和两个“error”的好模式是什么?

Eug*_*nko 3

我还可以看到更多陷阱,但回答以下问题:

  1. 是的,一般来说没问题,但对于你的情况来说看起来并没有多大意义。如果useFetchAllCountriesQuery有查询参数,这是从useFetchAllCurrenciesQuery结果中预期的 - 听起来不错。如果这些结果被用于selectFromResult- 情况就不是这样了。

  2. 再说一次 - 一般来说是可以的,但在你的情况下看起来使用方式错误。所有这些转换都可以transformResponse在两个钩子调用之后执行 OUT of 。

  3. 有点取决于您稍后将如何使用它,但一般来说 - 只需添加带有 sum 的 new const 即可:

    const isLoading = currenciesIsLoading || countriesIsLoading;
    
    Run Code Online (Sandbox Code Playgroud)

并在 UI 中需要时使用它。

总结一下,我会这样做:

const getCountriesWithCurrency = (currencies, countries) => {
    if (currencies && countries )  {
        return countries?.filter(country => currenciesData && 
            currencies[country.currencyShortName])
    }
}

const App: React.FC = () => {

  const { 
    data: currenciesData, 
    error: currenciesError, 
    isError: currenciesIsError,
    isLoading: currenciesIsLoading 
  } = useFetchAllCurrenciesQuery()

  const { 
    data: countriesData, 
    error: countriesError, 
    isError: countriesIsError,
    isLoading: countriesIsLoading
  } = useFetchAllCountriesQuery()

  const isLoading = currenciesIsLoading || countriesIsLoading;
  const isError = currenciesIsError || countriesIsError;
  const countries = getCountriesWithCurrency(currenciesData, countriesData);

...
Run Code Online (Sandbox Code Playgroud)

错误对象也可以以某种方式聚合,具体取决于您需要的内容以及顺序。您很可能不需要聚合它们,只需根据 isError 显示错误状态并分别记录两个错误对象。

最让我担心的,也是让定义正确的代码组合变得更加困难的是,您的countriesAPI定义transformResponse部分解决了国内所有货币的问题。

我想说,嵌套对象是一个不好的模式。在一系列 ID 或代码中了解哪些货币“属于”该国家就足够了。或者只使用第一个货币 ID,就像您使用firstKey 所做的那样,但不将其解析为完整对象。API 定义应该仅与一个特定实体耦合。你的情况是国家,所以它应该与货币无关。(SOLID 原则中的“单一职责”和“接口隔离”——这就是它们的含义)

顺便说一句,selectFromResult这里很有用,可以汇总国家/地区使用的所有货币,例如

selectFromResult: ({ data, error, isLoading }) => ({
  data,
  error,
  isLoading,
  relatedCurrencies:  data?.reduce((acc, country) => ([...acc, ...Object.keys(country.currencies)]), [])
}),
Run Code Online (Sandbox Code Playgroud)

稍后,您可以通过调用货币 API 来获取货币详细信息,但不是全部,而是您真正需要的 - 相关货币的一个或多个 id,或一个国家/地区。

总体建议 - 是移动这些货币和国家的获取、过滤和 isError | isLoading summing - 将反应组件放入一个单独的自定义钩子中,该钩子将仅返回准备好的数据和组件实际将使用的标志。这会让事情变得很清楚)