如何在React中监听本地存储值的更改?

33
当用户登录时,我想显示一个按钮。如果用户未登录,则不会显示按钮。当用户登录时,我将设置本地存储值。当我在登录组件中设置本地存储时,头部组件必须监听该事件并显示按钮。我使用addEventListener来进行监听。但它不起作用。
我不知道在头部组件中应该监听哪个事件。
// HeaderComponent(header.js):
class HeaderComponent extends Component {
    componentDidMount(){
        if(typeof window!='undefined'){
            console.log(localStorage.getItem("token"));
            window.addEventListener("storage",function(e){
               this.setState({ auth: true});
            })
        }
    } 
    render() {

    return (
        <div className="header">
            <div className="container">
                <div className="header-content">
                    <img src={logo} alt="logo"></img>
                    <div className="nav-links" >
                        <ul >
                            <li>Home</li>
                            <li>About</li>
                            <li>Services</li>
                            <li><NavLink activeClassName="active" to="/upload" >Upload</NavLink></li>
                            <li><NavLink activeClassName="active" to="/signup"> Sign Up</NavLink></li>


                           { this.state.auth? <li onClick={this.onLogout}>Logout</li> :null}

                        </ul>
                    </div>
                </div>
            </div>

        </div>
    );
   }  
}

//登录组件(login.js)

class LoginComponent extends Component {
    constructor(props) {
        super(props);
        this.onSubmit = this.onSubmit.bind(this);
    }
    onSubmit(event) {
        const data = {
            username: document.getElementById('name').value,
            password: document.getElementById('password').value
        }
        axios.post(`http://localhost:4000/user/login`, data).then(res => {
            this.props.history.push("/");
            localStorage.setItem("token",res.data.token);
            localStorage.setItem("auth",true);
        }).catch(err => console.log(err));
    }


    render() {
        return (
            <section class="log-in">
                <div class="card-col">
                    <form>
                        <h3>LOG IN</h3>
                        <div class="form-controls">
                            <input id="name" type="text" placeholder="username" class="input"></input>
                        </div>
                        <div class="form-controls">
                            <input id="password" type="password" placeholder="password" class="input"></input>
                        </div>
                        <button type="submit" onClick={this.onSubmit} class="button" >Log in</button>
                    </form>
                </div>

           </section>

        )
    }

}

这两个组件是否有共同的父级?如果是这样,请让 LoginComponent 在该父级中设置 auth 状态,然后将其作为 prop 传递给 HeaderComponent - sallf
如果它们在一个组件中,只需调用setState。但是,如果它们在不同的组件中,您可以将操作分派给Reducer并将包含按钮的组件连接到Redux。 如果您感到困惑,请随时询问! - Cherik
@Lobster 有些混淆,请您提供示例代码。 - Karthi
3个回答

71

当前的答案忽略了一个非常简单和安全的选项:window.dispatchEvent

在您设置localStorage项时,如果同时分派事件,则同一浏览器选项卡中的eventListener(无需打开另一个或混乱状态)也会捕获它:

const handleLocalStorage = () => {
    window.localStorage.setItem("isThisInLocalStorage", "true");
    window.dispatchEvent(new Event("storage"));
};
window.addEventListener('storage', () => {
    console.log("Change to local storage!");
    // ...
})

编辑:

因为这似乎很有帮助,我还建议检查来自usehooks-ts团队的useLocalStorage钩子。您不需要将其安装为软件包;您只需完全复制钩子即可。此钩子利用了我最初分享的解决方案,但增加了更多复杂的逻辑。


7
这个答案对我很有帮助。 - Ahmet Firat Keler
3
你不知道这有多帮助我了。非常感谢你,你真的拯救了我的应用程序(或者至少防止了页面重新加载)。谢谢! - Andy
3
这篇与编程有关的内容非常有用,如果能获得上千个赞,那就太好了。谢谢。 - Layhout

19

请注意两件事:

  1. storage事件仅在两个浏览器选项卡中打开相同应用程序时起作用 (它用于在同一应用程序的不同选项卡之间交换信息)。当同一页面上显示两个组件时,存储事件 将不会被触发

  2. 添加事件监听器时,您传递的是 function(),而不是箭头函数。 function()未捕获this,因此您应明确使用bind(this)或将其更改为箭头函数。

    例如

    window.addEventListener("storage",(function(e){
           this.setState({ auth: true});
        }).bind(this));
    

    或者使用箭头函数

    window.addEventListener("storage",(e) => {
           this.setState({ auth: true});
        });
    
    这里是一个简单的示例

    请确保在两个选项卡中打开它(相同的链接)。在一个选项卡中存储值,并在另一个选项卡中查看此值。


2
当我在两个选项卡中打开时,它可以正常工作。但是我希望它只在单个选项卡中监听。 - Karthi
2
存储事件在单个选项卡中打开时不会触发。存储事件的目标是在标签之间交换信息。要在单个页面上的组件之间交换信息,请使用提取状态向上方法 https://reactjs.org/docs/lifting-state-up.html。因此,`this.state.auth` 将位于 LoginComponentHeaderComponent 的父组件中,并作为属性向下传递。 - Fyodor Yemelyanenko
1
我们能在React中使用Redux实现这个吗? - Karthi
是的,Redux 也可以管理状态,但是它是在中央管理的。我建议从 React state 开始,但要将其提升。这比使用 Redux 更容易。 - Fyodor Yemelyanenko

4
我发现了一个非常糟糕的方法来实现这个:

我有一个工具栏和一个登录组件,其中工具栏组件侦听localStorage中的更改,并在登录组件更新本地存储(如果身份验证成功)时显示已登录用户的名称。

工具栏组件

(类似于你的情况中的标题组件)

const [loggedInName, setLoggedInName] = useState(null);

    useEffect(() => {
        console.log("Toolbar hi from useEffect")
        setLoggedInName(localStorage.getItem('name') || null)
        window.addEventListener('storage', storageEventHandler, false);

    }, []);

    function storageEventHandler() {
        console.log("hi from storageEventHandler")
        setLoggedInName(localStorage.getItem('name') || null)
    }

    function testFunc() {  
        console.log("hi from test function")
        storageEventHandler();
    }

在您的工具栏(Toolbar)组件中添加一个隐藏按钮。当点击此隐藏按钮时,将调用testFunc()函数,该函数将在本地存储更新后立即更新已登录用户的名称。
   <button style={{ display: 'none' }} onClick={testFunc} id="hiddenBtn">Hidden Button</button>

现在,在您的登录组件中

   .
   .
   .
   //login was successful, update local storage
   localStorage.setItem("name",someName)


   //now click the hidden button using Javascript
   document.getElementById("hiddenBtn").click();
   .

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接