ค้นหาเว็บไซต์

typescript - Redux-Toolkit Query เพิ่มพารามิเตอร์ไปยัง query ก็ต่อเมื่อไม่เป็น null


ฉันมีคำค้นพร้อมพารามิเตอร์หลายตัว และฉันต้องการเพิ่มพารามิเตอร์เข้าไปในคำค้นเฉพาะเมื่อไม่ใช่ค่า null

สิ่งเดียวที่ฉันนึกออกคือสร้างอ็อบเจ็กต์ params และเพิ่มคุณสมบัติแบบมีเงื่อนไข แต่การเพิ่มสิ่งนี้สำหรับทุกคำถามรู้สึกเหมือนเป็นวิธีที่ผิด เพราะฉันคิดว่าต้องมีวิธีที่ดีกว่าที่จะจัดการกับสิ่งนี้ วิธีแก้ของฉัน:

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

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

คำตอบ #1

คุณสามารถเขียนฟังก์ชันทั่วไปที่ทำการ Traversal แบบ Depth/Breadth First ผ่านกราฟวัตถุที่ป้อนเข้าไปเพื่อแก้ไข (หรือคัดลอก + แก้ไข) มันเพื่อให้คุณสมบัติที่เป็น null ถูกลบหรือถูกตั้งค่าเป็น undefined

ฉันใช้ฟังก์ชันที่คล้ายกันในโค้ดของฉันกับ RTK Query เพื่อทำการซีเรียลไลซ์และดีซีเรียลไลซ์ไปยัง/จาก API ฝั่งเซิร์ฟเวอร์ของฉัน


แก้ไข: นี่คือตัวอย่างสำเนาของการใช้งาน Depth First Traversal ของฉันสำหรับการวนซ้ำกราฟอ็อบเจ็กต์ของ JavaScript มันเป็นแบบทั่วไปและสามารถใช้ได้ทั้งการวนล่วงหน้าและหลังฉันได้เขียนชุดทดสอบหน่วยจำนวนหนึ่งสำหรับมันในโค้ดของฉันเองและมันทำงานได้ดี

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;

ถ้าคุณต้องการใช้มันเพื่อเดินผ่านกราฟวัตถุและแก้ไขคุณสมบัติที่เป็นค่า null ทั้งหมดให้เป็น undefined คุณสามารถกำหนดฟังก์ชัน visit ดังนี้ (หมายเหตุ: ส่วนนี้ยังไม่ได้ทดสอบ/แก้ข้อผิดพลาด และอาจต้องระวังเป็นพิเศษสำหรับรายการในอาร์เรย์)

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;
}; 

อีกทางหนึ่ง คุณก็สามารถลบคุณสมบัตินั้นได้ดังนี้: delete parent[field];

คุณสามารถใช้สิ่งนี้กับฟังก์ชันก่อนหน้าของฉันเพื่อวนซ้ำบนกราฟวัตถุอินพุตของคุณ (params) ดังนี้:

depthFirstTraversal( params, removeNullProperties );

คำตอบ #2

การปรับปรุงอาจเป็นการส่งออบเจ็กต์ params ตรง ๆ จากโค้ดที่เรียกใช้งาน เนื่องจากสิ่งเหล่านี้ดูเหมือนจะเป็น "args" ของคุณอยู่แล้ว แล้วคุณก็ไม่จำเป็นต้องมีการตรวจสอบ/ตรรกะแบบเกินจำเป็นนี้ในแต่ละ endpoint เพียงแค่ส่งสิ่งที่คุณต้องการให้เป็นพารามิเตอร์ของ query ฟังก์ชัน query พื้นฐานสามารถจัดการกับ params ว่าง/undefined ได้

ตัวอย่าง :

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

หรือจะระบุให้ชัดเจนมากขึ้นต่อแต่ละจุดสิ้นสุดถ้าคุณต้องการ:

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

ตัวอย่างการใช้งาน:

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