更改反应钩子中的处理程序

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 的角度来看,这有多糟糕?

T.J*_*der 5

每次渲染时更改句柄都会初始化吗?

是的。这就是上钩的原因之一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仍然相同。