javascript - 从反应钩子返回回调的最佳方法
问题描述
我想制作一个自定义挂钩来响应某些元素的单击事件并进行一些状态更新:
function useCustom() {
// a sample state within the hook
const [counter, setCounter] = useState(0);
// the callback I'm talking about
const onClick = event => {
console.log(`Hey! counter is ${counter}`);
setCounter(val=>val+1);
}
// returning the callback to be used in the component
return {onClick};
}
现在使用这个钩子的组件
function MyComp(props) {
const cbs = useCustom();
// component onclick handler
const onClick = event => cbs.onClick(event);
return <button onClick={onClick}>Click me!</button>
}
这种方法存在一些问题。首先,钩子中的 callabck 函数 onClick 以某种方式连接到该钩子的第一个闭包,这意味着其中的 counter 的值始终为 0(它在每次单击时都会记录“嘿!计数器为 0”)。好吧,为了解决这个问题,我需要使用 useRef ...我知道该怎么做,但我主要关心的是这个回调以某种方式连接到钩子的早期(和过时)闭包,所以它保持这个闭包(及其所有变量)活着(不是垃圾收集),因此它是一种内存泄漏(??我在这里犯错了吗) - 所以一般来说,从钩???
一个看起来更反应的替代解决方案是使用状态变量而不是钩子内的回调并返回该状态的设置状态函数:
function useCustom() {
// a sample state within the hook
const [counter, setCounter] = useState(0);
// state to hold the last click event
const [clickEvent, setClickEvent] = useState(null);
// replacing the callback with an effect callback
useEffect(() => {
console.log(`Hey! counter is ${counter}`);
setCounter(val=>val+1);
}, [clickEvent]);
// returning the set state function instead of callback
return {setClickEvent};
}
然后在组件中:
function MyComp(props) {
const cbs = useCustom();
// component onclick handler
const onClick = event => cbs.setClickEvent(event);
return <button onClick={onClick}>Click me!</button>
}
请注意,这样做的不利之处在于,我们将在单击时对钩子进行两次渲染(即执行)(一个是由于 setClickEvent 而下一个是由于 setCounter 在 clickEvent 的效果内...)。与回调方法相比,这可能是它的权衡。
那么谁赢了呢?回调方法或 setState 方法,或...??
解决方案
setState
是异步的。你可以在 React 的这个文档中看到:
- https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-valuejs
- https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous
您可以通过添加一个将在计数器更改时触发的值来记录该useEffect
值counter
:
React.useEffect(() => {
console.log(`Hey! counter is ${counter}`); // <= log when counter changes
}, [counter]);
工作示例:
function useCustom() {
// a sample state within the hook
const [counter, setCounter] = React.useState(0);
// the callback I'm talking about
const onClick = (event) => {
setCounter((val) => val + 1);
};
React.useEffect(() => {
console.log(`Hey! counter is ${counter}`);
}, [counter]);
// returning the callback to be used in the component
return { onClick };
}
function App() {
const cbs = useCustom();
// component onclick handler
const onClick = (event) => cbs.onClick(event);
return <button onClick={onClick}>Click me!</button>;
}
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
推荐阅读
- .net-5 - 引用的程序集是使用较新版本的 Microsoft.Windows.SDK.NET.dll 编译的
- python - pyrevit 脚本键盘快捷键
- r - 当我尝试在 r 中列出向量时,为什么会出现错误?
- sql - OLAP Cube Parallelism 未按预期工作
- c# - 如何在 DataGridView 的现有项目中添加数量?
- visual-studio-code - VS Code terraform 扩展错误:无法启动客户端语言服务
- python - Python 使用 OpenCV 将 Numpy 数组映射到类中
- python - 为什么 numba 中的 guvectorize 返回奇怪的输出?
- material-ui - 在materialui中,为了设置边距,有没有办法同时使用theme.spacing和auto?
- vue.js - Vue vuetify Dialog如何在不点击的情况下触发对话框组件