【问题标题】:Why is adding a Firestore listener when a button is clicked so slow in React?为什么在 React 中单击按钮时添加 Firestore 侦听器如此缓慢?
【发布时间】:2020-10-17 15:48:34
【问题描述】:

我有一个将数据存储在 Cloud Firestore 中的 Electron/React 应用程序。我正在使用 Firebase Node.JS SDK。我的 Firestore 数据结构中有 3 个集合,如下所示:类别 >(类别集合)> 子类别 >(子类别集合)> 项目 >(项目集合)。我在组件的 componentDidMount 方法中向根类别集合添加了一个侦听器,该方法正在运行。我要做的是在用户单击类别时将侦听器添加到特定的子类别集合中。我为子类别侦听器重复了与类别侦听器相同的代码,但是即使根类别集合上的侦听器几乎立即触发,子类别侦听器也需要 15-20 秒才能触发。我已经用console.log 语句验证了onClick 处理程序在单击类别后立即被调用,但是onSnapshot 块内的代码直到单击类别后整整15-20 秒才会运行。此外,每个子类别集合的文档都比类别集合少,所以我知道这不是加载问题。我的代码在某些方面有问题吗?如果没有,有没有办法强制 Firestore 集合侦听器在初始化后立即触发?仅在根集合上使用侦听器并手动管理子集合的获取会更好吗?这是我的代码-

AllInventory.tsx(我们的根组件):

import React, { Component } from 'react';
import MaterialList from './MaterialList';
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import { firebaseConfig } from './PrivateKeys';

firebase.initializeApp(firebaseConfig);

//I have tried swapping out any "firestore.something" statements with 
//"firebase.firestore().something" statements but that didn't seem to make a difference
const firestore = firebase.firestore();

let categoriesListener = () => {};
let subcategoriesListener = () => {};
let itemsListener = () => {};

export default class AllInventory extends Component<any, any> {

  constructor(props: any) {
    super(props);

    this.state = {
      categoriesArr: [],
      subcategoriesArr: [],
      itemsArr: [],
      selectedCategory: "",
      selectedSubcategory: "",
      selectedItems: "",
    };
  }
  
  componentDidMount() {
    //This listener works and fires immediately
    categoriesListener = firestore.collection("Categories").onSnapshot(querySnapshot => {
      const categories: { name: string }[] = [];
      querySnapshot.docChanges().forEach(change => {
        if (change.type === 'added') {
          categories.push({name: change.doc.id.toString()});
        }
      })
      this.setState({categoriesArr: categories});
    });
  }

  componentWillUnmount() {
    //Remove all Firestore listeners
    categoriesListener();
    subcategoriesListener();
    itemsListener();
  }

  handleCategoryClicked = (category: string) => {
    console.log("This line executes right when the list item is clicked");
    //Remove current listener, if it exists
    subcategoriesListener();
    //Add new listener
    subcategoriesListener = firestore.collection("Categories").doc(category).collection("Subcategories").onSnapshot(querySnapshot => {
      console.log("This line takes 15-20 seconds to execute after the function is called");
      const subcategories: { name: string }[] = [];
      querySnapshot.docChanges().forEach(change => {
        if (change.type === 'added') {
          subcategories.push({name: change.doc.id.toString()});
        }
      })
      this.setState({
        selectedCategory: category,
        subcategoriesArr: subcategories
      });
    });
  }

  render() {
    const {
      categoriesArr,
      subcategoriesArr,
      itemsArr
    } = this.state;

    return (
      <div className="flex-box-horizontal-base-div">
        <div className="hRow stretchV">
          <MaterialList {...this.state} contentType="categories" onItemClick={this.handleCategoryClicked}/>
        </div>
        <div className="hRow stretchV">
          <MaterialList {...this.state} contentType="subcategories"/>
        </div>
        <div className="hRow stretchV last">
          <MaterialList {...this.state} contentType="items"/>
        </div>
      </div>
    );
  }
}

MaterialList.tsx:

import React from 'react';
import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
import MenuItem from '@material-ui/core/MenuItem';
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import Divider from '@material-ui/core/Divider';

//Theme and useStyles are only for changing the default font/color scheme for Material UI,
//so I haven't included them here as they are long functions

function MaterialListItem(props: any) {
  const classes = useStyles();
  const { name } = props;
  return (
    <div>
      <MenuItem style={{whiteSpace: 'normal'}} onClick={() => props.onItemClick(name)}>
        <ListItemText primary={name}/>
      </MenuItem>
      <Divider className={classes.dividerColor}/>
    </div>
  );
}

export default function MaterialList(props: any) {
  const classes = useStyles();
  const { contentType } = props;

  switch (contentType) {
    case "categories":
      if (props.categoriesArr.length !== 0) {
        return (
          <ThemeProvider theme={theme}>
            <List className={classes.root} component="nav">
              <Divider className={classes.dividerColor}/>
              {props.categoriesArr.map((item: { name: string }) => (
                <MaterialListItem name={item.name} key={item.name} onItemClick={props.onItemClick}/>
              ))}
            </List>
          </ThemeProvider>
        );
      } else {
        return (
          <ThemeProvider theme={theme}>
            <List className={classes.root} component="nav"/>
          </ThemeProvider>
        )
      }
    case "subcategories":
      if (props.subcategoriesArr.length !== 0) {
        return (
          <ThemeProvider theme={theme}>
            <List className={classes.root} component="nav">
              <Divider className={classes.dividerColor}/>
              {props.subcategoriesArr.map((item: { name: string }) => (
                <MaterialListItem name={item.name} key={item.name}/>
              ))}
            </List>
          </ThemeProvider>
        );
      } else {
        return (
          <ThemeProvider theme={theme}>
            <List className={classes.root} component="nav"/>
          </ThemeProvider>
        )
      }
    case "items":
      if (props.itemsArr.length !== 0) {
        return (
          <ThemeProvider theme={theme}>
            <List className={classes.root} component="nav">
              <Divider className={classes.dividerColor}/>
              {props.itemsArr.map((item: { name: string }) => (
                <MaterialListItem name={item.name} key={item.name}/>
              ))}
            </List>
          </ThemeProvider>
        );
      } else {
        return (
          <ThemeProvider theme={theme}>
            <List className={classes.root} component="nav"/>
          </ThemeProvider>
        )
      }
    default:
      return (
        <ThemeProvider theme={theme}>
          <List className={classes.root} component="nav"/>
        </ThemeProvider>
      )
  }
}

【问题讨论】:

标签: javascript reactjs typescript firebase google-cloud-firestore


【解决方案1】:

Firebase 支持帮助我解决了这个问题。事实证明,我使用的是一个过时的 Webpack 版本,它将 Firebase 打包为外部依赖项,而不是将它与我​​正在使用的所有其他库捆绑在一起。一旦我更新了 Webpack 并将其从我的外部依赖项列表中删除,我的 Firebase 加载时间长的问题就解决了。

【讨论】:

    猜你喜欢
    • 2018-08-01
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    • 2020-09-19
    • 2013-10-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多