如何将 firestore 中的动态数据放入函数 where() 中,并使用 snap.size 来计算要在图表中传递的总查询数?

JS3*_*JS3 7 javascript firebase reactjs google-cloud-firestore

我有来自 firestore 的数据,我想使用 a 动态检索它,where()但这是我收到的错误:

类型错误:疫苗不是函数

用户集合:

[![在此处输入图像描述][1]][1]

下面是代码:

 const Vaccine = () => {
      const [vaccines, setVaccines] = useState([]);
      useEffect(() => {
        const unsubscribe = firestore
          .collection("vaccines")
          .onSnapshot((snapshot) => {
            const arr = [];
            snapshot.forEach((doc) =>
              arr.push({
                ...doc.data(),
                id: doc.id,
              })
            );
            setVaccines(arr);
          });
    
        return () => {
          unsubscribe();
        };
      }, []);

 
Run Code Online (Sandbox Code Playgroud)

sam*_*man 3

前言

\n

正如对原始问题的评论中强调的那样,不建议使用此查询结构,因为它需要对包括/users私人医疗数据在内的敏感用户数据进行读取访问。

\n

请勿在生产/商业环境中使用此代码。不注意此警告将导致有人起诉您违反隐私法规。

\n

它仅适用于学校项目(尽管我会让学生因为这样的安全漏洞而失败)或使用模拟数据进行概念验证。下面包含的代码用于教育目的,用于解决您的特定查询并展示在 React 中处理动态查询的策略。

\n

从性能角度来看,在最坏的情况下(缓存未命中),您将在每次刷新时为每个至少接种一剂任何疫苗的用户按一次读取付费。即使您的代码不使用任何用户文档的内容,您的代码也必须下载所有这些数据,因为客户端 SDK 不支持运算select()

\n

为了获得更好的安全性和性能,请在服务器端执行此逻辑(例如云函数、您自己计算机上的脚本等)并将结果保存到可供所有用户重复使用的单个文档中。这将使您能够正确收紧对/users. 它还显着简化了在客户端显示图表和实时统计数据所需的代码。

\n

useEffect

\n

正如 React 文档中关于hooks 规则所述:

\n
\n

仅在顶层调用挂钩

\n

不要\xe2\x80\x99t 在循环、条件或嵌套函数内调用 Hook。相反,在任何早期返回之前,始终在 React 函数的顶层使用 Hooks。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。这\xe2\x80\x99s 使得 React 能够在多个useStateuseEffect调用之间正确保存 Hooks 的状态。

\n
\n

该文档进一步阐述了React依赖于调用 Hook 的顺序,这意味着您不能在条件逻辑后面有钩子定义,因为它们的顺序和数量在渲染之间会发生变化。如果您的挂钩依赖于某些条件逻辑,则必须在挂钩的声明内定义它。

\n

例如,如果您有一个依赖于其他数据的效果,则逻辑如下:

\n
const [userProfile, setUserProfile] = useState();\nconst [userPosts, setUserPosts] = useState(null);\n\nuseEffect(() => {\n  // get user profile data and store in userProfile\n}, []);\n\nif (userProfile) {\n  useEffect(() => {\n    // get user post list and store in userPosts\n  }, [userProfile]);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

你需要改用:

\n
const [userProfile, setUserProfile] = useState();\nconst [userPosts, setUserPosts] = useState(null);\n\nuseEffect(() => {\n  // get user profile data and store in userProfile\n}, []);\n\nuseEffect(() => {\n  if (!userProfile) {\n    // not ready yet/signed out\n    setUserPosts(null);\n    return;\n  }\n  \n  // get user post list and store in userPosts\n}, [userProfile]);\n
Run Code Online (Sandbox Code Playgroud)\n

同样,对于数组:

\n
someArray && someArray.forEach((entry) => {\n  useEffect(() => {\n    // do something with entry to define the effect\n  }, /* variable change hooks */);\n});\n
Run Code Online (Sandbox Code Playgroud)\n

应该是:

\n
useEffect(() => {\n  if (!someArray) {\n    // not ready yet\n    return;\n  }\n  \n  const cleanupFunctions = [];\n  someArray.forEach((entry) => {\n    // do something with entry to define an effect\n\n    cleanupFunctions.push(() => {\n      // clean up the effect\n    });\n  });\n\n  // return function to cleanup the effects created here\n  return () => {\n    cleanupFunctions.forEach(cleanup => cleanup());\n  }\n}, /* variable change hooks */);\n
Run Code Online (Sandbox Code Playgroud)\n

因为这看起来很像生命周期管理,所以实际上最好用嵌套组件替换它,而不是使用钩子,如下所示:

\n
return (\n  <> // tip: React.Fragment shorthand (used for multiple top-level elements)\n    {\n      someArray && someArray\n        .map(entry => {\n          return <Entry key={entry.key} data={entry.data} />\n        })\n    }\n  </>\n);\n
Run Code Online (Sandbox Code Playgroud)\n

适应您的代码

\n

注意:此处的代码不用于onSnapshot统计信息,因为每次将新用户添加到数据库时都会导致重新渲染。

\n
const getVaccineStats = (vaccineName) => {\n  const baseQuery = firestore\n    .collection("users")\n    .where("doses.selectedVaccine", "==", vaccine);\n      \n  const oneDoseQueryPromise = baseQuery\n    .where("doses.dose1", "==", true)\n    .where("doses.dose2", "==", false)\n    .get()\n    .then(querySnapshot => querySnapshot.size);\n\n  const twoDoseQueryPromise = baseQuery\n    .where("doses.dose1", "==", true)\n    .where("doses.dose2", "==", true)\n    .get()\n    .then(querySnapshot => querySnapshot.size);\n\n  return Promise.all([oneDoseQueryPromise, twoDoseQueryPromise])\n    .then(([oneDoseCount, twoDoseCount]) => ({ // tip: used "destructuring syntax" instead of `results[0]` and `results[1]`\n      withOneDose: oneDoseCount,\n      withTwoDoses: twoDoseCount\n    }));\n};\n\n\nconst Vaccine = () => {\n  const [vaccines, setVaccines] = useState();\n  const [vaccineStatsArr, setVaccineStatsArr] = useState([]);\n  \n  // Purpose: Collect vaccine definitions and store in `vaccines`\n  useEffect(() => {\n    return firestore  // tip: you can return the unsubscribe function from `onSnapshot` directly\n      .collection("vaccines")\n      .onSnapshot({ // tip: using the Observer-like syntax, allows you to handle errors\n        next: (querySnapshot) => {\n          const vaccineData = []; // tip: renamed `arr` to indicate what the data contains\n          querySnapshot.forEach((doc) =>\n            vaccineData.push({\n              ...doc.data(),\n              id: doc.id,\n            });\n          );\n          setVaccines(vaccineData);\n        }),\n        error: (err) => {\n          // TODO: Handle database errors (e.g. no permission, no connection)\n        }\n      });\n  }, []);\n\n  // Purpose: For each vaccine definition, fetch relevant statistics\n  //          and store in `vaccineStatsArr`\n  useEffect(() => {\n    if (!vaccines || vaccines.length === 0) {\n      return; // no definitions ready, exit early\n    }\n\n    const getVaccineStatsPromises = vaccines\n      .map(({ vaccine }) => [vaccine, getVaccineStats(vaccine)]);\n    // tip: used "destructuring syntax" on above line\n    //      (same as `.map(vaccineInfo => [vaccineInfo.vaccine, getVaccineStats(vaccineInfo.vaccine)]);`)\n    \n    let unsubscribed = false;\n      \n    Promise.all(getVaccineStatsPromises)\n      .then(newVaccineStatsArr => {\n        if (unsubscribed) return; // unsubscribed? do nothing\n        setVaccineStatsArr(newVaccineStatsArr);\n      })\n      .catch(err => {\n        if (unsubscribed) return; // unsubscribed? do nothing\n        // TODO: handle errors\n      });\n\n    return () => unsubscribed = true;\n  }, [vaccines]);\n\n  if (!vaccines) // not ready? hide element\n    return null;\n\n  if (vaccines.length === 0) // no vaccines found? show error\n    return (<span class="error">No vaccines found in database</span>);\n\n  if (vaccineStatsArr.length === 0) // no stats yet? show loading message\n    return (<span>Loading statistics...</span>);\n\n  return (<> // tip: React.Fragment shorthand\n    {\n      vaccineStatsArr.map(([name, stats]) => {\n        // this is an example component, find something suitable\n        // the `key` property is required\n        return (<BarGraph\n          key={name}\n          title={`${name} Statistics`}\n          columns={["One Dose", "Two Doses"]}\n          data={[stats.withOneDose, stats.withTwoDoses]}\n        />);\n      });\n    }\n  </>);\n};\n\nexport default Vaccine;\n
Run Code Online (Sandbox Code Playgroud)\n

实时统计

\n

如果您希望实时更新图表,则需要将两个快照侦听器“压缩”为一个,类似于运算rxjs combineLatest。下面是一个实现示例:

\n
const onVaccineStatsSnapshot => (vaccine, observerOrSnapshotCallback, errorCallback = undefined) => {\n  const observer = typeof observerOrCallback === \'function\'\n    ? { next: observerOrSnapshotCallback, error: errorCallback }\n    : observerOrSnapshotCallback;\n  \n  let latestWithOneDose,\n    latestWithTwoDoses,\n    oneDoseReady = false,\n    twoDosesReady = false;\n\n  const fireNext = () => {\n    // don\'t actually fire event until both counts have come in\n    if (oneDoseReady && twoDosesReady) {\n      observer.next({\n        withOneDose: latestWithOneDose,\n        withTwoDoses: latestWithTwoDoses\n      });\n    }\n  };\n  const fireError = observer.error || (err) => console.error(err);\n\n  const oneDoseUnsubscribe = baseQuery\n    .where("doses.dose1", "==", true)\n    .where("doses.dose2", "==", false)\n    .onSnapshot({\n      next: (querySnapshot) => {\n        latestWithOneDose = querySnapshot.size;\n        oneDoseReady = true;\n        fireNext();\n      },\n      error: fireError\n    });\n\n  const twoDoseUnsubscribe = baseQuery\n    .where("doses.dose1", "==", true)\n    .where("doses.dose2", "==", true)\n    .onSnapshot({\n      next: (querySnapshot) => {\n        latestWithTwoDoses = querySnapshot.size;\n        twoDosesReady = true;\n        fireNext();\n      },\n      error: fireError\n    });\n\n  return () => {\n    oneDoseUnsubscribe();\n    twoDoseUnsubscribe();\n  };\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您可以重写上述函数来使用useState,但这会不必要地导致组件在不需要时重新渲染。

\n

用法(直接):

\n
const unsubscribe = onVaccineStatsSnapshot(vaccineName, {\n  next: (statsSnapshot) => {\n    // do something with { withOneDose, withTwoDoses } object\n  },\n  error: (err) => {\n    // TODO: error handling\n  }\n);\n
Run Code Online (Sandbox Code Playgroud)\n

或者

\n
const unsubscribe = onVaccineStatsSnapshot(vaccineName, (statsSnapshot) => {\n  // do something with { withOneDose, withTwoDoses } object\n});\n
Run Code Online (Sandbox Code Playgroud)\n

用法(作为组件):

\n
const VaccineStatsGraph = (vaccineName) => {\n  const [stats, setStats] = useState(null);\n\n  useEffect(() => onVaccineStatsSnapshot(vaccineName, {\n    next: (newStats) => setStats(newStats),\n    error: (err) => {\n      // TODO: Handle errors\n    }\n  }, [vaccineName]);\n\n  if (!stats)\n    return (<span>Loading graph for {vaccineName}...</span>);\n\n  return (\n    <BarGraph\n      title={`${name} Statistics`}\n      columns={["One Dose", "Two Doses"]}\n      data={[stats.withOneDose, stats.withTwoDoses]}\n    />\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n