React类组件改函数组件后报displayName未定义错误如何解决
问题原因及修复方案
1. 首要报错:Cannot read property 'displayName' of undefined
这个错误的核心原因是函数组件的setState(useState返回的更新函数)是全量替换状态,而不是类组件那样的增量合并:
你初始化state的时候包含了user、chats等多个字段,但后续调用setState({readError: null})、setState({chats})的时候,直接用新对象覆盖了整个state,导致原来的user字段直接丢失,所以state.user变成了undefined,访问displayName自然报错。
2. 内存泄漏警告:Can't perform a React state update on an unmounted component
原因是你给Firebase实时数据库绑定了value事件监听,但组件卸载时没有取消监听,当组件销毁后监听回调触发,还在尝试给已卸载的组件更新状态就会触发这个警告。
3. 其他隐藏问题
useEffect的catch块里还在用类组件的this.setState,函数组件没有this,这个写法会直接报错。- 模板里判断错误的字段写错了:你存的错误字段是
writeError,但模板里判断的是state.error,导致发送消息的错误永远不会显示。 - 没有处理
auth().currentUser初始为null的情况:Firebase Auth初始化是异步的,组件首次渲染时currentUser可能还没加载完成,本身就可能是null。
完整修复代码
import React, { useState, useEffect } from "react"; import { auth } from "../services/firebase"; import { db } from "../services/firebase"; import Header from "../components/Header"; import { writeChats } from "../helpers/db"; function Chat() { const [state, setState] = useState({ user: auth().currentUser, chats: [], content: "", readError: null, writeError: null, }); useEffect(() => { // 所有setState都要先合并旧状态,再更新指定字段 setState(prev => ({...prev, readError: null })); let unsubcribe = null; try { // 保存监听取消函数 unsubcribe = db.ref("messages").on("value", (snapshot) => { let chats = []; snapshot.forEach((snap) => { chats.push(snap.val()); }); setState(prev => ({...prev, chats })); }); } catch (err) { // 去掉this,同时合并状态 setState(prev => ({...prev, readError: err.message })); } // 返回清理函数,组件卸载时取消监听 return () => { if (unsubcribe) { db.ref("messages").off("value", unsubcribe); } }; }, []); function handleChange(event) { // 合并旧状态 setState(prev => ({ ...prev, content: event.target.value, })); } async function handleSubmit(event) { event.preventDefault(); setState(prev => ({...prev, writeError: null })); try { await writeChats({ name: state.user.displayName, content: state.content, timestamp: Date.now(), uid: state.user.uid, }); setState(prev => ({...prev, content: "" })); } catch (error) { setState(prev => ({...prev, writeError: error.message })); } } // 加user为空的兜底判断 if (!state.user) { return <div>登录信息加载中...</div> } return ( <div className="chatter"> <div className="header"> <Header /> </div> <div className="user-info"> Logged in as: <strong id="user-tag">{state.user.displayName}</strong> </div> <div className="chatbox"> <div className="chats"> {state.chats.map((chat) => { return ( <p key={chat.timestamp} style={{ fontSize: "1.2rem" }}> {chat.content} <code style={{ fontSize: "0.5rem" }}> {chat.name}</code> </p> ); })} </div> <div className="input-group"> <form className="textbar" onSubmit={handleSubmit}> <input style={{ borderRadius: "10px" }} size="50" onChange={handleChange} value={state.content} ></input> {/* 修正错误字段判断 */} {state.writeError ? <p>{state.writeError}</p> : null} <button className="send-btn" type="submit"> Send </button> </form> </div> </div> </div> ); } export default Chat;
额外优化建议
如果你的项目多个组件都需要用到用户登录信息,确实可以考虑用Context把用户状态抽离到上层,避免每个组件都单独获取auth().currentUser,也能更好处理auth异步初始化的逻辑。
内容的提问来源于stack exchange,提问作者Anand Thevar
相关产品推荐
相关产品推荐

