堆栈为:
我有一个应用程序,其中创建了2个钩子和一个用于上/下投票的组件。
第一个自定义钩子是通用的Firebase文档处理程序。 每次我更改页面或重新加载页面时,都会越来越多地调用此循环。
我有这个警告:
无法在已卸载的组件上执行React状态更新。这是无操作,但表示您的应用程序中发生内存泄漏。要修复,请取消使用useEffect清理功能中的所有订阅和异步任务。
const useDocument = (ref: DocumentReference, create = false): UseDocumentType => {
const [documentData, setDocumentData] = useState<DocumentData>({})
const [documentExists, setDocumentExists] = useState<'untested' | boolean>('untested')
const [updating, setUpdating] = useState(false)
// Listen to the user post data
useEffect(() => {
// Declare the function that will be used to subscribe
const subscriber = (): void => {
try {
ref?.onSnapshot((documentSnapshot: DocumentSnapshot) => {
if (documentSnapshot && documentSnapshot.exists) {
setUpdating(false)
setDocumentExists(true)
const data = documentSnapshot.data() || {}
data.id = documentSnapshot.id
setDocumentData(data)
// The screen is covered by the above console.log, while not infinite
console.log(`DEBUG > useDocument > subscriber > document ${ref.path} data fetched`)
} else if (create) {
// Create the document with a single field
updateDocument({ createdOn: new Date() })
console.log(`DEBUG > useDocument > subscriber > document ${ref.path} created by subscriber`)
} else {
setDocumentExists(false)
console.log(`WARN > useDocument > subscriber > the document ${ref.path || '<unknown ref>'} does not exists in Firebase`)
}
})
} catch (error) {
throw functionError(error, 'subscriber', '', 'useDocument')
}
}
subscriber()
// Stop listening for updates when no longer required
return (): void => subscriber()
// Disabling ESLint rule since passing refs in hooks dependencies create infinite rerenders
// Refs are functions and shall be wrapped into useCallback to avoid that.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [create, shallExist])
// Function used to update documents
const updateDocument = async (data: Partial<DocumentData>): Promise<void> => {
try {
setUpdating(true)
await ref?.set(data, { merge: true })
// This function can also create the document
setDocumentExists(true)
} catch (error) {
throw functionError(error, 'updateDocument', data, 'useDocument')
}
}
return { documentExists, documentData, updating, setUpdating, updateDocument }
}
然后由useFeedback自定义钩子调用:
const useFeedback = (user: AuthUser, postId: string): UseFeedbackType => {
// State
const [upvoted, setUpvoted] = useState<boolean>(false)
const [downvoted, setDownvoted] = useState<boolean>(false)
// Reference to the document handling user feedback for this post
// Shall it be passed into a useCallback hook?
const ref: DocumentReference<DocumentData> = userPostDoc(user, postId)
// useDocument provides tooling necessary to work with the document
const { documentExists, documentData, updating, setUpdating, updateDocument, toggleDocumentField, withTransaction } = useDocument(ref)
// Update the state when the document data change
useEffect(() => {
console.log(`DEBUG > useFeedback > useEffect > liked=${documentData?.upvoted}, muted=${documentData?.downvoted}`)
if (user) {
setUpvoted(documentData?.upvoted || false)
setMuted(documentData?.downvoted|| false)
}
}, [documentData, user])
const onUpvote = async (): Promise<void> => {
if (upvoted) {
await updateDocument({ upvoted: false })
setUpvoted(false)
} else {
await updateDocument({ upvoted: true})
setUpvoted(true)
}
}
const onMute = async (): Promise<void> => {
if (muted) {
await updateDocument({ downvoted: false })
setMuted(false)
} else {
await updateDocument({ downvoted: true })
setMuted(true)
}
}
return { onUpvote, onMute, upvoted, muted, updating }
}
然后将该组件调用到屏幕中:
const PostScreen: FC<Props> = ({ route, navigation }: Props) => {
const initialPostData = route?.params?.post
//imported: const postDoc = (postId: string) => postsCollection.doc(postId)
const ref = postDoc(initialPostData.id)
const [postData, setPostData] = useState(initialPostData)
const { documentData, documentExists } = useDocument(ref)
// Force re-render
useEffect(() => {
if (documentExists === true) setPostData(documentData)
// This console.log is also printed several time but "only" ~10 times
console.log(
`DEBUG > PostScreen > useEffect > documentExists=${documentExists} and postData=${objectToString(postData || initialPostData)}`,
)
}, [documentData, documentExists])
return (
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor="transparent" barStyle="dark-content" translucent={true} />
<ScrollView>
<FavoriteIcon data={postData} style={styles.bookmark} color={colors.white} size={50} />
<View style={styles.contentContainer}>
<Text style={styles.title}>{postData?.title}</Text>
<Text style={styles.description}>{postData?.description || ''}</Text>
<FeedbackIcons postId={postData?.id} />
</View>
</ScrollView>
</SafeAreaView>
)
}
我是React的新手。这实际上可行,但更新时间很长(尽管Firebase足够聪明,可以在上/下投票时使用它的缓存,但设置起来需要2秒),并且useEffect的日志记录很长,使我认为存在严重的优化问题...
感谢您的耐心和帮助!
编辑 :
重新启动Android模拟器后,每次我上/下一篇文章时,只有1个调用useFeedback和3个useDocument。
保存了几次代码后,我现在有10个对useFeedback的调用,每个之间有1到4个对useDocument的调用...并且每次我保存代码或更改页面时,它都会变得越来越糟。