如何使用钩子将React类组件转换为功能组件以获取Firebase数据

时间:2019-03-31 22:51:52

标签: javascript reactjs firebase firebase-realtime-database react-hooks

我有一个正常工作的React类组件,我想将其转换为功能性组件以将钩子用于状态等。我正在学习React钩子。类组件版本运行良好,功能组件是我需要帮助的地方。

数据结构由一个带有三个“客户端”的客户端列表组成。图像在这里:

enter image description here

我要做的就是获取此数据,对其进行迭代,然后向用户显示每个名称键的数据。很简单。

问题是从我的组件调用firebase导致行为不稳定,因为无法正确检索数据。持续调用最后一个客户端名称,它冻结了浏览器。 :)

以下是结果的图片:

enter image description here

代码如下:



import React, {Component,useContext,useEffect, useState} from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import firebase from 'firebase/app';
import {Consumer,Context} from '../../PageComponents/Context';


const styles = theme => ({
    root: {
        flexGrow: 1,
    },
    paper: {
        padding: theme.spacing.unit * 2,
        textAlign: 'center',
        color: theme.palette.text.secondary,
    },
});


const FetchData = (props) =>{
    const [state, setState] = useState(["hi there"]);
    const userID = useContext(Context).userID;

    useEffect(() => {
        let clientsRef = firebase.database().ref('clients');
        clientsRef.on('child_added', snapshot => {
            const client = snapshot.val();
            client.key = snapshot.key;
            setState([...state, client])
         });
    });

    //____________________________________________________BEGIN NOTE: I am emulating this code from my class component and trying to integrate it

    // this.clientsRef.on('child_added', snapshot => {
    //     const client = snapshot.val();
    //     client.key = snapshot.key;
    //     this.setState({ clients: [...this.state.clients, client]})
    //  });

    //___________________________________________________END NOTE


     console.log(state)

        return (

            <ul>

                {
                    state.map((val,index)=>{

                        return <a key={index} > <li>{val.name}</li>   </a>

                    }) 
                }

            </ul>
        )


}  


FetchData.propTypes = {
  classes: PropTypes.object.isRequired
}

export default withStyles(styles)(FetchData)

3 个答案:

答案 0 :(得分:3)

默认情况下,useEffect回调在每个完成的渲染(see docs)之后运行,并且每次这样的调用都要设置一个新的firebase侦听器。因此,当Firebase发出事件时,每个此类侦听器都会接收到数据快照,并且每个侦听器都会向状态添加一个接收到的值。

相反,您需要在组件安装后设置一次侦听器,可以通过提供一个空的依赖项数组([])作为useEffect的第二个参数来实现:

useEffect(() => {
  // your code here
}, []) // an empty array as a second argument

这将告诉React该效果没有任何依赖关系,因此无需多次运行它。

但是还有另一个重要时刻。由于您设置了侦听器,因此当您不再需要它时,需要对其进行清理。这是由另一个回调完成的,您应该在传递给useEffect的函数中 返回

useEffect(() => {
  let clientsRef = firebase.database().ref('clients');
  clientsRef.on('child_added', snapshot => {
    const client = snapshot.val();
    client.key = snapshot.key;
    setState([...state, client])
  });
  return () => clientsRef.off('child_added') // unsubscribe on component unmount
}, []);

基本上,此返回的清理函数将在调用每个新效果之前以及组件卸载之前see docs之前被调用,因此只有此清理函数才能自行解决您的解决方案,但无需调用您的效果在每次渲染后都将[]作为第二个参数。

答案 1 :(得分:2)

默认情况下,效果在每次渲染后运行,并且设置状态会导致渲染。 Any effect that updates state needs to have a dependency array specified,否则您将只有一个无限的update-render-update-render循环。

此外,remember to clean up any subscriptions that effects create.在这里,您可以通过返回调用.off(...)并删除侦听器的函数来实现。

然后,make sure to use the function form of state update,以确保下一个状态总是(em)依赖于当前状态,而不是绑定事件时闭包值恰好是当前状态。 Consider using useReducer if your component's state becomes more complex.

const [clients, setClients] = useState([])

useEffect(() => {
  const clientsRef = firebase.database().ref("clients")

  const handleChildAdded = (snapshot) => {
    const client = snapshot.val()
    client.key = snapshot.key
    setClients(clients => [...clients, client])
  }

  clientsRef.on("child_added", handleChildAdded)
  return () => clientsRef.off('child_added', handleChildAdded)
}, [])

另请参阅:

答案 2 :(得分:2)

您的问题是,默认情况下,useEffect()将在每次呈现组件时运行。发生的情况是,您的效果触发了组件中的更改,这将触发效果再次运行,最终您会得到近似于无限循环的效果。

幸运的是,reactly使我们可以控制何时以数组形式运行效果挂钩,您可以将数组作为附加参数传入。以您的情况为例:

    useEffect(() => {
        let clientsRef = firebase.database().ref('clients');
        clientsRef.on('child_added', snapshot => {
            const client = snapshot.val();
            client.key = snapshot.key;
            setState([...state, client])
         });
    }, []);//An empty array here means  this will run only once.

该数组告诉反应要监视哪些属性。只要这些属性之一发生更改,它将运行清除功能并重新运行效果。如果您提交一个空数组,那么它将只运行一次(因为没有要监视的属性)。例如,如果要添加[userId],则效果将在userId变量每次更改时运行。

说到清理功能,您不会在效果挂钩中返回任何一个。我对Firebase不太熟悉,无法知道在销毁组件时是否需要清理任何内容(例如,删除“ child_added”事件绑定)。最好返回一个方法作为使用效果的最后一部分。最终代码如下所示:

 useEffect(() => {
        let clientsRef = firebase.database().ref('clients');
        clientsRef.on('child_added', snapshot => {
            const client = snapshot.val();
            client.key = snapshot.key;
            setState([...state, client])
         });
        return () => { /*  CLEANUP CODE HERE */ };
    }, []);//An empty array here means  this will run only once.