Mar*_*rek 2 javascript reactjs react-hooks
有一段时间,我想开始使用带有React hooks 的 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>
)
}
Run Code Online (Sandbox Code Playgroud)
有一个handleNameChange声明用作输入的更改处理程序。让我们想象一下,Greeting由于某种原因,组件更新非常频繁。每次渲染时更改句柄都会初始化吗?从 JavaScript 的角度来看,这有多糟糕?
每次渲染时更改句柄都会初始化吗?
是的。这就是上钩的原因之一useCallback。
从 JavaScript 的角度来看,这有多糟糕?
从根本上来说,它只是创建一个新对象。函数对象和底层函数代码不是一回事。函数的底层代码仅解析一次,通常解析为字节码或简单、快速的编译版本。如果该函数使用得足够频繁,它就会被积极地编译。
因此,每次创建一个新的函数对象都会产生一些内存搅动,但在现代 JavaScript 编程中,我们一直在创建和释放对象,因此 JavaScript 引擎经过高度优化,可以在我们这样做时处理它。
但是 usinguseCallback可以避免不必要地重新创建它(嗯,有点,继续阅读),只需在依赖项发生变化时更新我们使用的那个。您需要列出的依赖项(在数组中,这是 的第二个参数useCallback)是handleNameChange可以更改的关闭项。在这种情况下,handleNameChange不会关闭任何更改。它唯一关闭的是setName,React 保证它不会改变(请参阅 上的“注释” useState)。它确实使用来自输入的值,但它通过参数接收输入,它不会关闭它。因此,handleNameChange您可以通过将空数组作为第二个参数传递给useCallback. (在某个阶段,可能会有一些东西自动检测这些依赖关系;现在,您可以声明它们。)
敏锐的人会注意到,即使使用useCallback,您每次仍然会创建一个新函数(作为第一个参数传递给 的函数useCallback)。但useCallback如果先前版本的依赖项与新版本的依赖项匹配,则将返回它的先前版本(在这种情况下,它们总是会这样handleNameChange,因为没有任何依赖项)。这意味着您作为第一个参数传入的函数可立即用于垃圾回收。JavaScript 引擎在垃圾收集对象(包括函数)方面特别高效,这些对象是在函数调用(调用Greeting)期间创建的,但在该调用返回时没有在任何地方引用,这就是原因之一useCallback。(与普遍的看法相反,现代引擎可以在可能的情况下在堆栈上创建对象。)此外,在 props 中重用相同的函数input可以让 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>
)
}
Run Code Online (Sandbox Code Playgroud)
这是一个类似的示例,但它还包含第二个回调 ( incrementTicks),该回调确实使用了它所关闭的内容 ( ticks)。请注意何时handleNameChange以及incrementTicks实际发生更改(由代码标记):
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>
)
}
Run Code Online (Sandbox Code Playgroud)
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")
);Run Code Online (Sandbox Code Playgroud)
当您运行它时,您会看到handleNameChange并且incrementTicks都已创建。现在,改个名字吧。请注意,没有重新创建任何内容(好吧,新的没有被使用并且可以立即被 GC 回收)。现在单击[+]刻度旁边的按钮。请注意,它incrementTicks已重新创建(因为ticks它关闭的函数已过时,因此useCallback返回了我们创建的新函数),但handleNameChange仍然相同。
| 归档时间: |
|
| 查看次数: |
1337 次 |
| 最近记录: |