网站搜索

reactjs - 在我的自定义 React hook 中,是否有可能编写一个函数,可以动态返回一个有效的 Firebase 查询?


对于我的 React 应用程序,我正在尝试编写一个自定义 hook,用于查询 Firebase 中的 Firestore 数据库。

到目前为止,我写了一个使用预定义查询的钩子,它可以正常工作。我无法弄清楚的是如何编写一个能够动态生成该查询的函数。

这是我目前所做的:

import { useState, useCallback } from 'react';
import { initializeApp } from "firebase/app";
import { getFirestore, getDocs, collection, query, where, orderBy, limit } from 'firebase/firestore';

import firebaseConfig from "./firebase.config";

const useQuery = () => {
  
  // NOTE for clarity i am showing initialization of firebase here
  // although this actually happens in another file and is imported.
  const firebaseApp = initializeApp(firebaseConfig);
  const firestore = getFirestore(firebaseApp);

  const firestoreRef = collection(firestore, 'myCollection');
  
  const [snapshot, setSnapshot] = useState([]);

  const getSnapshot = useCallback(async(queryConstraints:any) => {
  
  // instead of this pre-defined query ...
  const firestoreQuery = query(firestoreRef,
    where('someField', '>=', 'someValue'),
    where('anotherField', '==', 'anotherValue'),
    orderBy('timestamp', 'asc'),
    limit(10)
  );
    
  // ... Id like to be able to take the incoming argument
  // titled 'queryConstraints' of which its value
  // would be an object like this one:
      //  {[{
      //   where: 'someField',
      //   operator: '>=',
      //   value: 'someValue'
      //  },{
      //   where: 'anotherField',
      //   operator: '==',
      //   value: 'anotherValue'
      //  }], 10, 'timestamp', 'asc'}

  // ... and pass it to a function, lets call it 'configAValidQuery' which
  // would parse the object and return a valid query to be applied as follows:

  //  const myQuery = await configAValidQuery(queryConstraints);
  //  const firestoreQuery = query(firestoreRef, myQuery);

    try {
      const docs = await getDocs(firestoreQuery);
      if(docs.empty) {
        // TO DO: HANDLE EMPTY RESULT
      } else {
        setSnapshot(doc.data());
      }
    } catch(error:any) {
      // TO DO: HANDLE ERROR
    }

  }, [firestoreRef]);

  return {
    snapshot,
    getSnapshot
  }
}

export default useQuery;

我在弄清楚如何动态构建基本上相当于以逗号分隔的函数调用列表时遇到麻烦,因为查询约束包括导入的 Firebase 方法 where、orderBy 和 limit。

我需要的函数大概会像下面这样,虽然这显然行不通:

const configAValidQuery = useCallback((conditions:any, limit:number, orderBy:string, direction:string) => {
      let query:any;
      if(conditions.length > 0) {
        conditions.map((item:any) => {
          return(query = query.where(item.where, item.operator, item.value))
        });
        query = query.orderBy(orderBy, direction);
        if(limit > 0) {
          query = query.limit(limit);
        }
        return query;
      } else {
        query = query.orderBy(orderBy, direction);
        if(limit > 0) {
          query = query.limit(limit);
        }
        return query;
      }
  },[]);

firebase/firestore 包似乎不包含用于此目的的预定义接口或函数。

有什么建议吗?

答案#1

如果你在使用 TypeScript,像这样的东西可能会很好

import {
  query,
  where,
  orderBy,
  limit
} from "firebase/firestore";



type Clause = "where" | "limit" | "orderBy";
type Operator = "==" | "!=" | "<" | ">" | "<=" | ">=";

interface WhereArgs {
  column: string;
  operator: Operator;
  value: string | number;
}

interface OrderByArgs {
  column: string;
  order: "asc" | "desc";
}

interface LimitArgs {
  amount: number;
}

type Command =
  | ({ clause: "where" } & WhereArgs)
  | ({ clause: "orderBy" } & OrderByArgs)
  | ({ clause: "limit" } & LimitArgs);

const options = {
  where: (args: WhereArgs) => {
    const { column, operator, value } = args;
    return where(column, operator, value);
  },
  limit: (args: LimitArgs) => {
    const { amount } = args;
    return limit(amount);
  },
  orderBy: (args: OrderByArgs) => {
    const { column, order } = args;
    return orderBy(column, order);
  }
};

const commands: Array<Extract<Command, { clause: Clause }>> = [
  {
    clause: "where",
    column: "anotherField",
    operator: ">=",
    value: "someValue"
  },
  {
    clause: "where",
    column: "anotherField",
    operator: "==",
    value: "anotherValue"
  },
  {
    clause: "limit",
    amount: 10
  },
  {
    clause: "orderBy",
    column: "timestamp",
    order: "asc"
  }
];

const constraints = [];

for (const command of commands) {
  const { clause, ...rest } = command;
  constraints.push(options[command.clause](rest as any));
}

query(null as any, ...constraints);

这是一个链接

答案#2

你问题中的 Firstival 'query Constraints' 不是一个对象,我不知道它是什么。

我假设这个对象应该看起来像这样:

const qConstrains = {
        where: [
            { val1: 'fieldName', operator: '>=', val2: 'valueToCompare'},
            { val1: 'anotherfieldName', operator: '>=', val2: 'valueToCompare'}
        ],
        orderBy: { timestamp: 'timestamp', direction: 'asc'},
        limit: 10
    }

好吧,上面的对象是错误的,因为查询函数使用 QueryConstraints[],它是一个数组而不是对象。它是一个没有对象但有函数的数组,所以你需要做的就是创建一个对象数组,这样你就可以将其保存在数据库中,并在使用时将其转换为函数数组。

这是一个正确数组的示例:

const qConstrains = [
        { where: 'fieldName', operator: '>=', value: 'valueToCompare'},
        { where: 'anotherfieldName', operator: '>=', value: 'valueToCompare'},
        { orderBy: 'timestamp', direction: 'asc'},
        { limit: 10 }
    ]

将对象数组转换为函数数组的功能:

function convertArrayObjectsToArrayFunctions(queryConstains: any[]) {
    let newQueryConstains: any[] = []
    for(let item of queryConstains) {
        if(item['where']) newQueryConstains.push(where(item.where, item.operator, item.value)) 
        if(item['orderBy']) newQueryConstains.push(orderBy(item.orderBy, item['direction'] ? item.direction : undefined))
        if(item['limit']) newQueryConstains.push(limit(item.limit))
    }
    return newQueryConstains
}

使用示例:

// Do not forget to use ... operator when creating a query.
const q = query(collection(db,'posts'),
   ...convertArrayObjectsToArrayFunctions(qConstrains))

家庭作业孩子们!XD 上面的函数不包含可能出现的所有函数。你的任务是为以下情况添加额外的 if 语句:'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'

重要: 如果您收到关于索引的错误,只需在错误中的链接上 ctrl+点击,它会为您在 Firestore 中设置索引。