如何将 React class 组件转换为带钩子的函数组件以获取 firebase 数据
How to convert a React class component to a function component with hooks to get firebase data
我有一个正在运行的 React class 组件,我想将其转换为功能组件以使用钩子来处理状态等。我正在学习 React 钩子。 class 组件版本工作正常,功能组件是我需要帮助的地方。
数据结构由一个客户端列表和三个"clients"组成。它的图片在这里:
我想要做的就是获取这些数据,对其进行迭代并向用户显示每个名称键的数据。很简单。
问题是从我的组件调用 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)
效果,默认情况下,运行在每次渲染后,设置状态会导致渲染。 Any effect that updates state needs to have a dependency array specified,否则你只会有一个无限的更新-渲染-更新-渲染循环。
此外,remember to clean up any subscriptions that effects create. 在这里,您可以通过返回调用 .off(...)
并删除侦听器的函数来做到这一点。
然后,make sure to use the function form of state update, to make sure the next state always relies on the current state, instead of whatever the closure value happened to be when binding the event. 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)
}, [])
另见:
您的问题是,默认情况下,useEffect()
将在您的组件每次呈现时 运行。正在发生的事情是,您的效果触发了组件中的更改,这将再次触发效果 运行ning 并且您最终得到近似于无限循环的东西。
幸运的是,React 让我们可以控制何时 运行 可以作为附加参数传入的数组形式的效果挂钩。例如你的情况:
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.
数组告诉 React 要观察哪些属性。每当这些属性之一发生变化时,它将 运行 清理功能并重新 运行 效果。如果您提交一个空数组,那么它只会 运行 一次(因为没有要观察的属性)。例如,如果您要添加 [userId]
,每次 userId
变量更改时效果都会 运行。
说到清理功能,您 return 没有在效果挂钩中使用它。我对 firebase 不够熟悉,不知道当组件被销毁时是否需要清理任何东西(例如删除 'child_added' 事件绑定)。最好将 return 方法作为您使用效果的最后一部分。最终代码如下所示:
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.
默认情况下,useEffect
回调是在每次完成渲染 (see docs) 之后 运行,并且您在每次此类调用时都会设置一个新的 firebase 侦听器。因此,当 Firebase 发出事件时,每个此类侦听器都会收到数据快照,并且每个侦听器都会将接收到的值添加到状态中。
相反,您需要在安装组件后设置一次侦听器,您可以通过提供一个空的依赖项数组 ([]
) 作为 useEffect
的第二个参数来实现:
useEffect(() => {
// your code here
}, []) // an empty array as a second argument
这将告诉 React 这个效果没有任何依赖性,所以不需要 运行 它不止一次。
但是还有一个重要的时刻。由于您设置了一个侦听器,因此您需要在不再需要它时清理它。这是通过另一个回调完成的,您应该在传递给 useEffect
:[=19 的函数中 return =]
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
}, []);
基本上这个 returned 清理函数将在每个新效果被调用之前和组件卸载之前被调用 (see docs) 所以只有这个清理函数应该自己解决你的解决方案,但是无论如何都不需要在每次渲染后调用你的效果,因此 []
作为第二个参数。
我有一个正在运行的 React class 组件,我想将其转换为功能组件以使用钩子来处理状态等。我正在学习 React 钩子。 class 组件版本工作正常,功能组件是我需要帮助的地方。
数据结构由一个客户端列表和三个"clients"组成。它的图片在这里:
我想要做的就是获取这些数据,对其进行迭代并向用户显示每个名称键的数据。很简单。
问题是从我的组件调用 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)
效果,默认情况下,运行在每次渲染后,设置状态会导致渲染。 Any effect that updates state needs to have a dependency array specified,否则你只会有一个无限的更新-渲染-更新-渲染循环。
此外,remember to clean up any subscriptions that effects create. 在这里,您可以通过返回调用 .off(...)
并删除侦听器的函数来做到这一点。
然后,make sure to use the function form of state update, to make sure the next state always relies on the current state, instead of whatever the closure value happened to be when binding the event. 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)
}, [])
另见:
您的问题是,默认情况下,useEffect()
将在您的组件每次呈现时 运行。正在发生的事情是,您的效果触发了组件中的更改,这将再次触发效果 运行ning 并且您最终得到近似于无限循环的东西。
幸运的是,React 让我们可以控制何时 运行 可以作为附加参数传入的数组形式的效果挂钩。例如你的情况:
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.
数组告诉 React 要观察哪些属性。每当这些属性之一发生变化时,它将 运行 清理功能并重新 运行 效果。如果您提交一个空数组,那么它只会 运行 一次(因为没有要观察的属性)。例如,如果您要添加 [userId]
,每次 userId
变量更改时效果都会 运行。
说到清理功能,您 return 没有在效果挂钩中使用它。我对 firebase 不够熟悉,不知道当组件被销毁时是否需要清理任何东西(例如删除 'child_added' 事件绑定)。最好将 return 方法作为您使用效果的最后一部分。最终代码如下所示:
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.
默认情况下,useEffect
回调是在每次完成渲染 (see docs) 之后 运行,并且您在每次此类调用时都会设置一个新的 firebase 侦听器。因此,当 Firebase 发出事件时,每个此类侦听器都会收到数据快照,并且每个侦听器都会将接收到的值添加到状态中。
相反,您需要在安装组件后设置一次侦听器,您可以通过提供一个空的依赖项数组 ([]
) 作为 useEffect
的第二个参数来实现:
useEffect(() => {
// your code here
}, []) // an empty array as a second argument
这将告诉 React 这个效果没有任何依赖性,所以不需要 运行 它不止一次。
但是还有一个重要的时刻。由于您设置了一个侦听器,因此您需要在不再需要它时清理它。这是通过另一个回调完成的,您应该在传递给 useEffect
:[=19 的函数中 return =]
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
}, []);
基本上这个 returned 清理函数将在每个新效果被调用之前和组件卸载之前被调用 (see docs) 所以只有这个清理函数应该自己解决你的解决方案,但是无论如何都不需要在每次渲染后调用你的效果,因此 []
作为第二个参数。