Инициализируется ли ручка изменения каждый раз при каждом рендере?
Да. Это одна из причин для useCallback
хука.
С точки зрения JavaScript, насколько это плохо?
По сути, это просто созданиеновый объектОбъект функции и базовый код функции - это не одно и то же. Базовый код функции анализируется только один раз, обычно в байт-код или в простую быструю версию компиляции. Если функция используется достаточно часто, она будет агрессивно скомпилирована.
Поэтому создание нового объекта функции каждый раз создает некоторый отток памяти, но в современном программировании JavaScript мы создаем и выпускаем объекты все время поэтому движки JavaScript в высшей степени оптимизированы для обработки, когда мы это делаем.
Но использование useCallback
позволяет избежать ненужного повторного его создания (ну, вроде как, продолжайте читать) , только обновляятот, который мы используем, когда его зависимости меняются. Зависимости, которые вам нужно перечислить (в массиве, который является вторым аргументом useCallback
), - это то, что handleNameChange
закрывает и может измениться. В этом случае handleNameChange
не закрывает все, что изменяется. Единственное, что закрывается, это setName
, гарантия React не изменится (см. «Примечание» на useState
). Он использует значение из входных данных, но получает входные данные через аргументы, но не закрывает их. Таким образом, для handleNameChange
вы можете оставить зависимости пустыми, передав пустой массив в качестве второго аргумента useCallback
. (На каком-то этапе может быть что-то, что автоматически обнаруживает эти зависимости; сейчас вы объявляете их.)
Острые глаза заметят, что даже с useCallback
вы все еще создание новой функции каждый раз (той, которую вы передаете в качестве первого аргумента useCallback
). Но useCallback
вернет предыдущую версию, вместо этого, если зависимости предыдущей версии совпадают с зависимостями новой версии (что они всегда будут в случае handleNameChange
, потому что их нет). Это означает, что функция, которую вы передаете в качестве первого аргумента, сразу же доступна для сборки мусора. Механизмы JavaScript , в особенности , эффективны при сборке мусора объектов (включая функции), которые создаются во время вызова функции (вызов Greeting
), но нигде не упоминаются при возврате этого вызова, что является частью того, почемуuseCallback
имеет смысл. (Вопреки распространенному мнению, объекты могут и создаются в стеке, когда это возможно, современными движками.) Кроме того, повторное использование этой же функции в подпорках на 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>
)
}
Вот аналогичный пример, но он также включает второй обратный вызов (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'able). Теперь нажмите кнопку [+]
рядом с галочками. Обратите внимание, что incrementTicks
воссоздан (так как ticks
, который он закрывает, устарел, поэтому useCallback
вернул созданную нами новую функцию), но handleNameChange
остается тем же.