我有一个正常工作的React类组件,我想将其转换为功能性组件以将钩子用于状态等。我正在学习React钩子。类组件版本运行良好,功能组件是我需要帮助的地方。
数据结构由一个带有三个“客户端”的客户端列表组成。图像在这里:
我要做的就是获取此数据,对其进行迭代,然后向用户显示每个名称键的数据。很简单。
问题是从我的组件调用firebase导致行为不稳定,因为无法正确检索数据。持续调用最后一个客户端名称,它冻结了浏览器。 :)
以下是结果的图片:
代码如下:
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)
答案 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.