我想在卸载组件时将状态保存到localStorage
。
这曾经在componentWillUnmount
中起作用。
我尝试用useEffect
钩子做同样的事情,但是在useEffect
的返回函数中状态似乎不正确。
那是为什么?如何不使用类就可以保存状态?
这是一个虚拟的例子。当您按关闭时,结果始终为0。
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function Example() {
const [tab, setTab] = useState(0);
return (
<div>
{tab === 0 && <Content onClose={() => setTab(1)} />}
{tab === 1 && <div>Why is count in console always 0 ?</div>}
</div>
);
}
function Content(props) {
const [count, setCount] = useState(0);
useEffect(() => {
// TODO: Load state from localStorage on mount
return () => {
console.log("count:", count);
};
}, []);
return (
<div>
<p>Day: {count}</p>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => props.onClose()}>close</button>
</div>
);
}
ReactDOM.render(<Example />, document.querySelector("#app"));
答案 0 :(得分:7)
我试图对useEffect挂钩执行相同的操作,但是在useEffect的返回函数中状态似乎不正确。
其原因是由于关闭。闭包是函数在其范围内对变量的引用。您的useEffect
回调仅在组件装入时运行一次,因此return回调引用的初始计数值为0。
这里给出的答案是我的建议。我建议@Jed Richard将[count]
传递到useEffect
的答案,只有在计数改变时才有写入localStorage
的作用。这比在每次更新中完全不传递任何内容的方法要好。除非您非常频繁地(每隔几毫秒)更改计数,否则就不会出现性能问题,并且只要localStorage
发生变化,都可以写入count
。
useEffect(() => { ... }, [count]);
如果您坚持只在卸载时写入localStorage
,则可以使用一个丑陋的hack /解决方案-参考。基本上,您将创建一个在组件的整个生命周期中都存在的变量,您可以从组件中的任何位置进行引用。但是,您必须手动将状态与该值同步,这非常麻烦。引用不会给您上述关闭问题,因为引用是具有current
字段的对象,并且多次调用useRef
将返回相同的对象。只要您更改.current
的值,您的useEffect
就可以始终(仅)读取最新的值。
const {useState, useEffect, useRef} = React;
function Example() {
const [tab, setTab] = useState(0);
return (
<div>
{tab === 0 && <Content onClose={() => setTab(1)} />}
{tab === 1 && <div>Count in console is not always 0</div>}
</div>
);
}
function Content(props) {
const value = useRef(0);
const [count, setCount] = useState(value.current);
useEffect(() => {
return () => {
console.log('count:', value.current);
};
}, []);
return (
<div>
<p>Day: {count}</p>
<button
onClick={() => {
value.current -= 1;
setCount(value.current);
}}
>
-1
</button>
<button
onClick={() => {
value.current += 1;
setCount(value.current);
}}
>
+1
</button>
<button onClick={() => props.onClose()}>close</button>
</div>
);
}
ReactDOM.render(<Example />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
答案 1 :(得分:4)
您的useEffect回调函数将显示初始计数,这是因为useEffect在初始渲染上仅运行一次,并且该回调使用初始渲染期间存在的count值存储为零。
相反,您要做的是
useEffect(() => {
// TODO: Load state from localStorage on mount
return () => {
console.log("count:", count);
};
});
在react文档中,您会找到为什么这样定义的原因
React何时确切地清除效果?当组件卸载时,React进行清除。但是,正如我们先前所了解的, 效果针对每个渲染运行,而不仅仅是一次。这就是为什么React也 在运行效果之前清除前一个渲染的效果 下次。
阅读Why Effects Run on Each Update
上的react文档
它确实可以在每个渲染上运行,要对其进行优化,可以使其在count
更改下运行。但这是useEffect
的当前建议行为,正如文档中也提到的那样,在实际实现中可能会发生变化。
useEffect(() => {
// TODO: Load state from localStorage on mount
return () => {
console.log("count:", count);
};
}, [count]);
答案 2 :(得分:2)
另一个答案是正确的。为何不将[count]
传递给useEffect,并在count
发生更改时保存到localStorage?这样调用localStorage并不会造成实际的性能损失。
答案 3 :(得分:1)
这将起作用-使用React的useRef-但效果不佳:
function Content(props) {
const [count, setCount] = useState(0);
const countRef = useRef();
// this effect fires every time count is changed
useEffect(() => () => {
countRef.current = count;
},[count]);
// this effect fires as per a true componentWillUnmount
useEffect(() => () => {
console.log("count:", countRef.current);
}, []);
}
请注意,对于useEffect来说,稍微更可以忍受的(我认为!)“返回函数的函数”代码构造。
问题在于useEffect会在合成时复制道具和状态,因此永远不要重新评估它们-这无助于此用例,但实际上不是useEffects的目的。
答案 4 :(得分:0)
您可以使用useEffect来更新引用,而不是像接受的答案那样手动跟踪状态更改。
function Content(props) {
const [count, setCount] = useState(0);
const currentCountRef = useRef(count);
// update the ref if the counter changes
useEffect(() => {
currentCountRef.current = count;
}, [count]);
// use the ref on unmount
useEffect(
() => () => {
console.log("count:", currentCountRef.current);
},
[]
);
return (
<div>
<p>Day: {count}</p>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => props.onClose()}>close</button>
</div>
);
}
答案 5 :(得分:0)
正在发生的是第一次useEffect运行,它创建了对您传递的状态值的封闭;那么如果您想获得实际的而不是第一个..您有两个选择:
如果您执行以下操作:
useEffect(() => {
return () => {
setCount((current)=>{ console.log('count:', current); return current; });
};
}, []);
我正在添加此解决方案,以防万一有人来这里寻求将旧值更新为useEffect而不进行重新加载的问题。