如何实现App组件响应Navbar组件的用户登录状态变更?
问题描述
我现在有一个React项目,App.js和Navbar组件的代码如下,我的设计思路是页面渲染Navbar组件,当用户已登录时渲染Questions组件,未登录时渲染其他内容,但现在App组件无法获取Navbar里变更的用户状态,不知道怎么实现状态同步。
App.js代码
import React, {Component} from 'react'; import logo from './logo.svg'; import './App.css'; import {BrowserRouter as Router} from 'react-router-dom'; import Navbar from "./components/nav"; import firebase from './firebase'; import Questions from './components/questions'; import Stuff from './components/stuff'; class App extends Component { constructor(p) { super(p); this.state = {user: null} } render() { return ( <div className="App"> <Navbar/> {this.state.user ? <Questions/> : <Stuff/>} </div> ); } } export default App;
Navbar组件代码
import React from 'react'; import {Navbar as Bar, Nav, NavItem, Modal, Button, FormControl} from 'react-bootstrap'; import {BrowserRouter as Router, Link, Route} from 'react-router-dom'; import firebase, {auth} from '../firebase'; class Navbar extends React.Component { constructor(props) { super(props); this.state = {}; this.login = this.login.bind(this); this.logout = this.logout.bind(this); this.openLogin = this.openLogin.bind(this); this.handleClose = this.handleClose.bind(this); } componentDidMount() { auth.onAuthStateChanged((user) => { if (user) { this.setState({user}); } }); } logout() { auth.signOut() .then(() => { this.setState({ user: null }); }); } login() { var email = document.getElementById('email').value; var password = document.getElementById('password').value; auth.signInWithEmailAndPassword(email, password) .then(result => { const user = result.user; this.setState({user}); document.getElementById('close').click(); } ).catch(e => console.log(e)); } openLogin() { this.setState({show: true}); } handleClose() { this.setState({show: false}); } render() { return ( <React.Fragment> <Router> <Bar> <Bar.Header> <Bar.Brand> <a href="/home">UczIchApp</a> </Bar.Brand> </Bar.Header> <Nav> { this.state.user ? <NavItem onClick={this.logout}>Wyloguj się</NavItem> : <NavItem onClick={this.openLogin}>Zaloguj się</NavItem> } </Nav> </Bar> </Router> <Modal show={this.state.show} onHide={this.handleClose}> <Modal.Header closeButton> <Modal.Title>Modal heading</Modal.Title> </Modal.Header> <Modal.Body> <form> <FormControl id="email" type="email" label="Email address" placeholder="Enter email" /> <FormControl id="password" label="Password" type="password"/> <Button onClick={this.login}>Zaloguj</Button> </form> </Modal.Body> <Modal.Footer> <Button id="close" onClick={this.handleClose}>Close</Button> </Modal.Footer> </Modal> </React.Fragment> ) } } export default Navbar;
解决方案
嘿,这个问题其实很常见——你现在把用户状态分别存在了Navbar和App两个组件里,导致它们的状态不同步。咱们可以用几种方法解决,下面给你讲两个最实用的方案:
方法一:状态提升(最直接的小项目方案)
核心思路是把用户状态和登录/登出逻辑都移到App组件里,让App作为状态的唯一管理者,Navbar只负责接收props并触发动作,这样两个组件就能共享同一套用户状态了。
步骤1:修改App.js,接管状态和逻辑
import React, {Component} from 'react'; import logo from './logo.svg'; import './App.css'; import {BrowserRouter as Router} from 'react-router-dom'; import Navbar from "./components/nav"; import firebase, {auth} from './firebase'; // 别忘了导入auth import Questions from './components/questions'; import Stuff from './components/stuff'; class App extends Component { constructor(p) { super(p); this.state = { user: null, showLoginModal: false // 把弹窗状态也移到这里 }; // 绑定方法到当前实例 this.login = this.login.bind(this); this.logout = this.logout.bind(this); this.openLogin = this.openLogin.bind(this); this.handleClose = this.handleClose.bind(this); } componentDidMount() { // 把Navbar里的auth监听移到App,统一监听用户状态变化 auth.onAuthStateChanged((user) => { this.setState({user}); }); } logout() { auth.signOut() .then(() => { this.setState({ user: null }); }); } login() { const email = document.getElementById('email').value; const password = document.getElementById('password').value; auth.signInWithEmailAndPassword(email, password) .then(result => { this.setState({user: result.user}); this.handleClose(); // 登录成功后关闭弹窗 }).catch(e => console.log(e)); } openLogin() { this.setState({showLoginModal: true}); } handleClose() { this.setState({showLoginModal: false}); } render() { return ( <div className="App"> {/* 把需要的状态和方法通过props传给Navbar */} <Navbar user={this.state.user} logout={this.logout} openLogin={this.openLogin} showLoginModal={this.state.showLoginModal} handleClose={this.handleClose} login={this.login} /> {this.state.user ? <Questions/> : <Stuff/>} </div> ); } } export default App;
步骤2:修改Navbar.js,用props替代自身状态
去掉Navbar里的state和auth逻辑,全部依赖App传递的props:
import React from 'react'; import {Navbar as Bar, Nav, NavItem, Modal, Button, FormControl} from 'react-bootstrap'; import {BrowserRouter as Router} from 'react-router-dom'; class Navbar extends React.Component { render() { // 解构props,让代码更简洁 const { user, logout, openLogin, showLoginModal, handleClose, login } = this.props; return ( <React.Fragment> <Router> <Bar> <Bar.Header> <Bar.Brand> <a href="/home">UczIchApp</a> </Bar.Brand> </Bar.Header> <Nav> {/* 直接用props里的user状态判断显示登录/登出按钮 */} { user ? <NavItem onClick={logout}>Wyloguj się</NavItem> : <NavItem onClick={openLogin}>Zaloguj się</NavItem> } </Nav> </Bar> </Router> {/* 弹窗的显示状态也用props里的值 */} <Modal show={showLoginModal} onHide={handleClose}> <Modal.Header closeButton> <Modal.Title>Modal heading</Modal.Title> </Modal.Header> <Modal.Body> <form> <FormControl id="email" type="email" label="Email address" placeholder="Enter email" /> <FormControl id="password" label="Password" type="password"/> <Button onClick={login}>Zaloguj</Button> </form> </Modal.Body> <Modal.Footer> <Button onClick={handleClose}>Close</Button> </Modal.Footer> </Modal> </React.Fragment> ) } } export default Navbar;
方法二:使用Context API(适合中大型项目)
如果以后你的项目组件层级变深,props层层传递会很麻烦,推荐用React的Context API来全局共享用户状态,这样任何组件都能直接获取用户信息,不用再手动传props。
步骤1:创建AuthContext
新建一个AuthContext.js文件,封装用户状态和相关方法:
import React, { createContext, useState, useEffect } from 'react'; import { auth } from './firebase'; // 创建Context export const AuthContext = createContext(); // 创建Provider组件,包裹需要共享状态的组件 export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); useEffect(() => { // 监听用户状态变化,组件卸载时清理订阅 const unsubscribe = auth.onAuthStateChanged(user => { setUser(user); }); return unsubscribe; }, []); // 封装登录方法 const login = (email, password) => { return auth.signInWithEmailAndPassword(email, password); }; // 封装登出方法 const logout = () => { return auth.signOut(); }; // 把状态和方法传给所有子组件 return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> ); };
步骤2:用AuthProvider包裹App
修改index.js(或者App的父组件),让整个App都能访问Context里的状态:
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { AuthProvider } from './AuthContext'; ReactDOM.render( <AuthProvider> <App /> </AuthProvider>, document.getElementById('root') );
步骤3:在App和Navbar里使用Context
修改App.js,用hooks获取用户状态:
import React, { useContext } from 'react'; import logo from './logo.svg'; import './App.css'; import {BrowserRouter as Router} from 'react-router-dom'; import Navbar from "./components/nav"; import Questions from './components/questions'; import Stuff from './components/stuff'; import { AuthContext } from './AuthContext'; // 改成函数组件更方便使用hooks function App() { const { user } = useContext(AuthContext); return ( <div className="App"> <Navbar/> {user ? <Questions/> : <Stuff/>} </div> ); } export default App;
修改Navbar.js,同样用hooks获取Context里的状态和方法:
import React, { useContext, useState } from 'react'; import {Navbar as Bar, Nav, NavItem, Modal, Button, FormControl} from 'react-bootstrap'; import {BrowserRouter as Router} from 'react-router-dom'; import { AuthContext } from '../AuthContext'; function Navbar() { const { user, login, logout } = useContext(AuthContext); // 弹窗状态还是放在Navbar自己的state里,因为只有它用得到 const [showLoginModal, setShowLoginModal] = useState(false); const openLogin = () => setShowLoginModal(true); const handleClose = () => setShowLoginModal(false); const handleLogin = () => { const email = document.getElementById('email').value; const password = document.getElementById('password').value; login(email, password) .then(() => { handleClose(); // 登录成功后关闭弹窗 }) .catch(e => console.log(e)); }; return ( <React.Fragment> <Router> <Bar> <Bar.Header> <Bar.Brand> <a href="/home">UczIchApp</a> </Bar.Brand> </Bar.Header> <Nav> { user ? <NavItem onClick={logout}>Wyloguj się</NavItem> : <NavItem onClick={openLogin}>Zaloguj się</NavItem> } </Nav> </Bar> </Router> <Modal show={showLoginModal} onHide={handleClose}> <Modal.Header closeButton> <Modal.Title>Modal heading</Modal.Title> </Modal.Header> <Modal.Body> <form> <FormControl id="email" type="email" label="Email address" placeholder="Enter email" /> <FormControl id="password" label="Password" type="password"/> <Button onClick={handleLogin}>Zaloguj</Button> </form> </Modal.Body> <Modal.Footer> <Button onClick={handleClose}>Close</Button> </Modal.Footer> </Modal> </React.Fragment> ) } export default Navbar;
额外小建议
- 尽量避免用
document.getElementById获取输入值,推荐用React的受控组件(把输入值存在组件state里),这样更符合React的单向数据流,也更容易维护。 - 记得在组件卸载时清理firebase的auth订阅(比如方法二里的
return unsubscribe),避免内存泄漏。
内容的提问来源于stack exchange,提问作者Alex Ironside
相关产品推荐
相关产品推荐

