有一段时间我想开始使用带有 React Hook 的React 函数组件,而不是类扩展 React 组件,但有一件事让我气馁。这是 React Hooks 的第一个介绍中的示例:
import React, { useState } from 'react'
import Row from './Row'
export default function Greeting(props) {
const [name, setName] = useState('Mary');
function handleNameChange(e) {
setName(e.target.value);
}
return (
<section>
<Row label="Name">
<input
value={name}
onChange={handleNameChange}
/>
</Row>
</section>
)
}
有一个 handleNameChange
声明用作输入的更改处理程序。假设 Greeting
组件由于某种原因更新得非常频繁。更改句柄是否每次都在每次渲染时初始化?从 JavaScript 的 Angular 来看,这有多糟糕?
最佳答案
Does change handle initialize every time on every render?
是的。这是 useCallback
的原因之一钩子(Hook)。
From JavaScript aspect of view how bad is that?
从根本上说,它只是创建一个新对象。函数对象和底层函数代码不是一回事。函数的底层代码只被解析一次,通常是字节码或简单、快速的编译版本。如果该函数的使用频率足够高,它会被积极地编译。
因此,每次创建一个新的函数对象都会造成一些内存困惑,但在现代 JavaScript 编程中,我们一直都在创建和释放对象,因此 JavaScript 引擎经过高度优化,可以在我们这样做时处理它。
但使用 useCallback
避免不必要地重新创建它(好吧,有点,请继续阅读),仅在其依赖项发生变化时更新我们使用的那个。您需要列出的依赖项(在 useCallback
的第二个参数的数组中)是 handleNameChange
关闭的可以更改的内容。在这种情况下,handleNameChange
不会关闭任何更改。它唯一关闭的是 setName
,React 保证它不会改变(参见 useState
上的“注释”)。它确实使用来自输入的值,但它通过参数接收输入,它不会关闭它。因此,对于 handleNameChange
,您可以将一个空数组作为第二个参数传递给 useCallback
,从而将依赖项留空。 (在某个阶段,可能会有一些东西可以自动检测这些依赖关系;现在,您声明它们。)
眼尖的人会注意到,即使使用 useCallback
,您仍然每次都创建一个新函数(作为第一个参数传递给 使用回调
)。但是 useCallback
将返回它的以前版本,而不是如果以前版本的依赖项与新版本的依赖项匹配(在 handleNameChange
情况下它们总是会这样,因为没有任何).这意味着您作为第一个参数传入的函数可立即用于垃圾回收。 JavaScript 引擎在垃圾收集对象(包括函数)方面特别高效,这些对象是在函数调用(调用 Greeting
)期间创建的,但在该调用返回时未在任何地方引用,这就是为什么 useCallback
有意义的部分原因。 (与流行的看法相反,现代引擎可以在可能的情况下在堆栈上创建对象。)此外,在 input
的 props 中重用相同的函数可能允许 React 更有效地渲染树(通过最小化差异)。
该代码的 useCallback
版本是:
import React, { useState, useCallback } from 'react' // ***
import Row from './Row'
export default function Greeting(props) {
const [name, setName] = useState('Mary');
const handleNameChange = useCallback(e => { // ***
setName(e.target.value) // ***
}, []) // *** empty dependencies array
return (
<section>
<Row label="Name">
<input
value={name}
onChange={handleNameChange}
/>
</Row>
</section>
)
}
这是一个类似的示例,但它还包括第二个回调 (incrementTicks
),确实使用它关闭的内容 (ticks
)。请注意 handleNameChange
和 incrementTicks
实际更改时(由代码标记):
const { useState, useCallback } = React;
let lastNameChange = null;
let lastIncrementTicks = null;
function Greeting(props) {
const [name, setName] = useState(props.name || "");
const [ticks, setTicks] = useState(props.ticks || 0);
const handleNameChange = useCallback(e => {
setName(e.target.value)
}, []); // <=== No dependencies
if (lastNameChange !== handleNameChange) {
console.log(`handleNameChange ${lastNameChange === null ? "" : "re"}created`);
lastNameChange = handleNameChange;
}
const incrementTicks = useCallback(e => {
setTicks(ticks + 1);
}, [ticks]); // <=== Note the dependency on `ticks`
if (lastIncrementTicks !== incrementTicks) {
console.log(`incrementTicks ${lastIncrementTicks === null ? "" : "re"}created`);
lastIncrementTicks = incrementTicks;
}
return (
<div>
<div>
<label>
Name: <input value={name} onChange={handleNameChange} />
</label>
</div>
<div>
<label>
Ticks: {ticks} <button onClick={incrementTicks}>+</button>
</label>
</div>
</div>
)
}
ReactDOM.render(
<Greeting name="Mary Somerville" ticks={1} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
当您运行它时,您会看到 handleNameChange
和 incrementTicks
都已创建。现在,更改名称。请注意,没有重新创建任何内容(嗯,好吧,新的没有被使用并且可以立即被 GC 调用)。现在点击 ticks 旁边的 [+]
按钮。请注意,incrementTicks
已重新创建(因为它关闭的 ticks
已过时,因此 useCallback
返回了我们创建的新函数),但是 handleNameChange
还是一样。
关于javascript - 更改 react 钩子(Hook)中的处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58787917/