【问题标题】:Can i subscribe to filtered RTK query data results across multiple components?我可以跨多个组件订阅过滤后的 RTK 查询数据结果吗?
【发布时间】:2023-01-24 22:22:53
【问题描述】:

我有一系列组件,它们都使用从 RTK 查询响应派生的数据。虽然来自具有相同查询参数的相同响应,但每个组件都需要数据通过一组相对昂贵的客户端过滤器(其参数是 redux 存储切片属性),每个组件可能不相同。但是,所有组件都需要数据通过至少两个特定的过滤器。这种关系如下图所示:

是否可以订阅数据它已经被一个特定的过滤器/一组过滤器转换了吗?

我考虑过的方法:

  • 在所有组件中使用查询,在某些 useEffect 或 useMemo 中应用任何需要的过滤器。这不是可取的,因为这意味着至少有 2 个过滤器被复制 nComponents 次。
  • 使用 createSlice extraReducers 选项并监听查询完成,然后执行过滤操作。这工作正常,因为我可以在减速器中使用过滤器参数,但我相信一旦过滤器参数已更新但查询数据保持不变,我就无法用新参数重复操作。
  • 订阅一个组件,在每个对应的filter stage之后发布数据到slice, 为每个组件订阅相应的数据。这就是我目前实现它的方式,但它并不理想,因为它将我希望避免的组件耦合在一起,使一个任意选择的组件膨胀,并产生频繁的大状态动作,这会减慢我的应用程序。
  • 提高对共享祖先组件的查询订阅,然后将数据作为道具传递。这并不理想,因为这些组件相对于它们的共同祖先处于不同的深度,我想这会导致至少对某些组件进行支柱钻孔。
  • 使用 React 上下文与相应组件共享前 2 个过滤器操作的结果。还没有研究这么多;它可以与查询订阅一起使用吗?

直觉上,我认为一些作为 API 结果和组件订阅数据之间的中间件运行的回调是理想的。我知道 API 切片中可定义的 transformResponse 选项,但我认为它不适合或不可能用于这种情况。

const queryResult = endpointName.useQuery(args, filterArgs, (data, filterArgs) => {
    return data.performSomeSharedFilterOperationHere(filterArgs);
    } 
);

理想情况下,数据会在查询参数更改或过滤器参数更改时更新。我想这与简单的 useEffect 实现之间的区别在于,在 useEffect 场景中,数据不是“共享”的,并且过滤器操作发生 nSubscribedComponents 次。

RTK 中是否有任何允许我正在寻找的行为的内容?

【问题讨论】:

    标签: javascript reactjs redux redux-toolkit rtk-query


    【解决方案1】:

    我认为这里的正确答案是使用the selectFromResult option in the query hooks

    创建以下 Reselect 选择器:

    
    const selectFilter1 = createSelector(
      // Start by taking the actual response data as its input
      (inputData) => inputData,
      (data) => // magically transform here
    )
    
    const selectFilter2 = createSelector(
      selectFilter1,
      (filteredData) => // magically transform here
    )
    
    // repeat for filters 2 and 3
    

    然后,在组件中:

      const { filteredData} = useGetPostsQuery(undefined, {
        selectFromResult: result => ({
          // We can optionally include the other metadata fields from the result here
          ...result,
          // Include a field called `filteredData` in the result object, 
          // and memoize the calculation
          filteredData: selectFilter3(result.data)
        })
      })
    

    这些组件将共享相同的选择器实例,因此每次调用选择器时都应传入相同的 result.data 引用,因此计算应该记忆。前几个选择器应该记住它们的结果,因此 selectFilter3 只需要在 selectFilter2 的结果发生变化等时重新计算。

    【讨论】:

    • 这是一种非常酷的方法,但是对于如何访问传递的值和“输入选择器”中的状态有点困惑。如果我创建这些选择器,其中输入(数据)的一部分来自组件的请求,而另一部分(过滤器参数)直接来自状态,我会把这些选择器写成const selectFiltered = createSelector([(state, paramsObject) => paramsObject.queryData, (state, paramsObject) => state.filterArg1, (state, paramsObject) => state.filterArg2], (dataToFilter, filterArg1, filterArg2) => { /* transform */});吗?
    • 选择器通常写到accept the Redux root state as an argument。然而,selectFromResult只要为您提供此请求的特定缓存条目。如果你想合并来自这个缓存条目的值在 Redux 状态的其他地方,您有两个选择:1) selectFromResult 如图所示 + useSelector 对于其他状态,结合 useMemo; 2) 使用endpoint.select()创建一个新的选择器,与createSelector中的其他选择器结合,在useSelector中读取最终结果。
    • 您会推荐其中哪些?我找到了一个用例,我可能需要从选择器中的多个查询访问数据,在这种情况下,endpoint.select() 似乎更适合。
    【解决方案2】:

    这是我的代码,数据来了,但是当你输入新值时,数据没有更新,请告诉我哪里错了?

    const [searchName, setSearchName] = useState('');
    
      const onChange = (event) => {
        setSearchName(event.target.value);
      };
    
      const { addItem } = useActions();
    
      const filteredProducts = useMemo(() => {
        return createSelector(
          (data) => data,
          (searchName) => searchName,
    
          (data, searchName) => data?.product?.filter((product) => product.name === product.name.includes(searchName)) ?? data
        );
      }, [searchName]);
    
      const { data, isLoading } = useGetProductsQuery(searchName, {
        selectFromResult: (result) => ({
          ...result,
          data: filteredProducts(result.data),
        }),
      });
    
    export const wooCommerceReduxApi = createApi({
      reducerPath: 'productsApi',
      refetchOnFocus: true,
      baseQuery: fetchBaseQuery({ baseUrl: '/' }),
      tagTypes: ['Product'],
      endpoints: (build) => ({
        getProducts: build.query<Product[], string>({
          async queryFn() {
            const response = await getProductsApi();
    
            if (response.error) {
              throw new Error();
            }
    
            return { data: response.data as Product[] };
          },
    
          providesTags: (result, error, arg) => (result ? [...result.map(({ id, arg }) => ({ type: 'Product' as const, id })), 'Product'] : ['Product']),
        }),
      }),
    });
    

    【讨论】: