我有多个参数的查询,我想添加参数以仅在不为空时进行查询。
我唯一想到的就是创建参数对象并有条件地添加属性,但为每个查询添加它感觉像是错误的方式。我在文档中搜索了此内容,但没有找到 我的解决方案:
query: (category: string | null) => {
const params: { [key: string]: string } = {};
if (category !== null) params.category = category;
return {
url: "/products",
params: params,
};
},
改进可能是直接从调用代码传递 params 对象,因为无论如何这些似乎都是您的“args”,然后您就不需要在每个端点中进行这种无关的检查/逻辑,只需传入确切的内容即可您想要的查询参数是。基本查询函数能够处理空/未定义的参数。
示例:
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
您可以编写一个通用函数,对输入对象图执行深度/广度优先遍历来修改(或复制+修改)它,以便删除空属性或将其设置为未定义。
我在代码中使用 RTK 查询的类似函数来对后端 API 执行序列化和反序列化。
编辑:这是我的深度优先遍历实现的副本,用于遍历 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;
如果您想使用它来遍历对象图并将所有空属性修改为未定义,那么您可以像这样定义一个访问函数(注意:此位尚未经过测试/调试,并且可能需要特殊关心数组条目):
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 );
如果您喜欢复制修改 params 对象,那么您可以这样做:
const sanitizedParams = { ...params };
depthFirstTraversal( sanitizedParams, removeNullProperties );