typescript - Redux-Toolkit Query için parametreleri yalnızca null değilse sorguya ekle


Birden fazla parametreye sahip bir sorgum var ve parametreler null değilse sorguya eklemek istiyorum.

Akla gelen tek şey params nesnesi oluşturmak ve şartlı olarak özellikler eklemekti, ama bunu her sorgu için eklemek yanlış bir yol gibi geliyor, çünkü bununla başa çıkmanın daha iyi bir yolu olduğunu düşünüyorum. Çözümüm:

query: (category: string | null) => {
  const params: { [key: string]: string } = {};
  if (category !== null) params.category = category;

  return {
    url: "/products",
    params: params,
  };
},

Cevap #1

Girdi nesne grafiği üzerinde Derinlik/Genişlik Öncelikli Tarama gerçekleştiren genel bir fonksiyon yazarak, null olan özelliklerin silinmesini veya undefined olarak ayarlanmasını sağlayacak şekilde bunu değiştirebilir (veya kopyalayıp değiştirebilirsiniz).

Kendi kodumda, RTK Query ile arka uç API'ime veri serileştirme ve serileştirmeden çıkarma işlemleri yapmak için benzer bir fonksiyon kullanıyorum.


DÜZENLEME: İşte bir JavaScript nesne grafını gezmek için Derin Öncelikli Gezişim (Depth First Traversal) uygulamamın bir kopyası. Genel amaçlıdır ve hem ön- hem de son- gezinmeler için kullanılabilir. Kendi kodumda bunun için bir dizi birim testi yazdım ve iyi çalışıyor.

import { isArray } from "lodash";

export const DefaultRootName = "[root]";

export interface DepthFirstTraversalOptions<TCustom = any> {
  // if true (default), then the algorithm will iterate over any and all array entries
  traverseArrayEntries: boolean; 

  // pre (default) = visit the root before the children. post = visit all the children and then the root.
  order: "pre" | "post"; 

  // If order = "pre", then this function is used to determine if the children will be visited after
  //  the parent node. This parameter will be ignored if order != "pre".
  skipChildren?: (parent: Readonly<any>, context: Readonly<VisitContext<TCustom>>) => boolean;

  // You may optionally provide a name for the root. This name will appear as the first entry within
  // context.path. This doesn't effect the functionality, but it maybe helpful for debugging.
  rootName?: string;
}

export interface VisitContext<TCustom = any> {
  path: string[];
  options: DepthFirstTraversalOptions;
  visited: Set<any>;
  visitStack: any[];
  custom: TCustom;
}

// Clone the source node, and apply any transformations as needed.
export type VisitFunction<TCustom = any> 
  = (current: Readonly<any>, context: Readonly<VisitContext<TCustom>>) => void;

/**
 * Performs a Depth First Traversal over all of the objects in the graph, starting from <source>.
 * Properties are not visited in any particular order.
 * @param source 
 * @param visit 
 * @param options 
 */
export function depthFirstTraversal<TCustom = any>(source: any, visit: VisitFunction<TCustom>, 
  custom?: TCustom, options?: Partial<DepthFirstTraversalOptions<TCustom>>) {

  const realOptions: DepthFirstTraversalOptions<TCustom> = {
    traverseArrayEntries: true,
    order: "pre",
    ...options
  };

  const visited = new Set<any>([source]); 
  const visitStack = [source];
  const path: string[] = [realOptions.rootName ?? DefaultRootName];

  const ctx: VisitContext = {
    path,
    options: realOptions,
    visited,
    visitStack,
    custom
  };

  __DepthFirstTraversal<TCustom>(source, visit, ctx);
}

// performs a depth-first traversal of the source object, visiting every property.
// First the root/source is visited and then its child properties, in no particular order. 
function __DepthFirstTraversal<TCustom = any>(source: any, visit: VisitFunction<TCustom>, context: VisitContext<TCustom>) {

  // assume that the context has already been updated prior to this internal call being made
  if (context.options.order === "pre") {
    visit(source, context);
    if (context.options.skipChildren?.(source, context)) {
      return;
    }
  }

  // check to see if the source is a primitive type. If so, we are done.
  // NOTE: the source could be undefined/null. 
  if (Object(source) !== source) { 
    if (context.options.order === "post") {
      visit(source, context);
    }
    return;
  }

  if (!context.options.traverseArrayEntries && isArray(source)) {
    if (context.options.order === "post") {
      visit(source, context);
    }
    return;
  }

  // visit any child nodes
  Object.keys(source).forEach(field => {
    const curr = source[field];

    if (context.visited.has(curr)) { 
      // We have already traversed through it via some other reference
      return; 
    } if (Object(curr) === curr) {
      // it is not a primitive, and this is our first time traversing to it 
      // register it to prevent re-iterating over the same object in the event that there
      // is a loop in the object graph.
      context.visited.add(curr);
    }

    context.visitStack.push(curr);
    context.path.push(field);
    __DepthFirstTraversal(curr, visit, context);
    context.path.pop();
    context.visitStack.pop();
  });

  if (context.options.order === "post") {
    visit(source, context);
  }
}

export default depthFirstTraversal;

Eğer bunu bir nesne grafiğinde gezinmek ve tüm null özellikleri undefined olarak değiştirmek için kullanmak isteseydiniz, o zaman bir ziyaret fonksiyonunu şu şekilde tanımlayabilirdiniz (NOT: Bu kısım test edilmemiş/hata ayıklanmamış olup, dizi elemanları için özel dikkat gerektirebilir):

const removeNullProperties:VisitFunction = 
(current, context) => {

  if(current !== null) { 
    return; 
  }

  // parent will be undefined if it is the root
  const parent = context.visitStack.at(-2); 
  const field = context.path.at(-1);

  parent[field] = undefined;
}; 

Alternatif olarak, özelliği şöyle silebilirsiniz: delete parent[field];

Bunu, giriş nesne grafiğinizde (params) yineleme yapmak için önceki fonksiyonumla şu şekilde uygulayabilirsiniz:

depthFirstTraversal( params, removeNullProperties );

Cevap #2

Bir geliştirme, parametre nesnesini doğrudan çağıran koddan geçirmek olabilir, çünkü bunlar zaten sizin “args”ınız gibi görünüyor ve böylece her uç noktada bu gereksiz kontrol/lojik işlemine gerek kalmaz, sadece sorgu parametrelerinin ne olmasını istediğinizi geçirin. Temel sorgu fonksiyonu boş/undefined parametreleri işleyebilecek kapasitededir.

Örnek :

getProducts: build.query</* ReturnType */, Record<string, any> | undefined>({
  query: (params) => {
    return {
      url: "/products",
      params
    }
  }
}),

veya isterseniz her uç nokta için daha açık olun:

getProducts: build.query</* ReturnType */, { category: string } | undefined>({
  query: (params) => {
    return {
      url: "/products",
      params
    }
  }
}),

Örnek Kullanım:

useGetProductsQuery(); // pass undefined
useGetProductsQuery({}); // pass empty params object
useGetProductsQuery({ category: "foo" }); // pass foo category