javascript - 更改 react 钩子(Hook)中的处理程序

标签 javascript reactjs react-hooks

有一段时间我想开始使用带有 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)。请注意 handleNameChangeincrementTicks 实际更改时(由代码标记):

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>

当您运行它时,您会看到 handleNameChangeincrementTicks 都已创建。现在,更改名称。请注意,没有重新创建任何内容(嗯,好吧,新的没有被使用并且可以立即被 GC 调用)。现在点击 ticks 旁边的 [+] 按钮。请注意,incrementTicks 已重新创建(因为它关闭的 ticks 已过时,因此 useCallback 返回了我们创建的新函数),但是 handleNameChange 还是一样。

关于javascript - 更改 react 钩子(Hook)中的处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58787917/

相关文章:

javascript - 如何知道表单输入是否为空(React Hooks)

javascript - 使用 useState hook 时 React 组件渲染两次

javascript - 为什么JSON全部大写

javascript - 将数字而不是字符串传递给 parseFloat()

javascript - 使用 Javascript 在中间添加一个时重新编号数字排序的 div ID

javascript - ReactJs 不更新状态

javascript - 将 git 存储库重新安装为 React 包

javascript - 更改 $scope 时,带有 ng-options 的 EDIT<select> 不会刷新所选内容

javascript - 无法从 Spring Boot 过滤器在 http header 中发送 "Unauthorized"

reactjs - React 自定义 Hook 使用 useRef 在第一次调用组件加载时返回 null?