предисловие
Сегодня в полдень, получив упакованный ланч, я ела филе глубоководной трески в томатном соусе, это было не передать словами. Вдруг товар подбежал и сказал: «А можно сегодня запустить спрос?» Я вдруг был в шоке, думая, что столкнулся с проблемой и не могу найти ее причину, поэтому робко ответил: «Да... Да ...», когда продукт услышал слово «можно», он напевал мелодию и ушел, оставив меня одного, лицом к несвежему филе глубоководной трески... Снова и снова думая о том, как решить проблема.. .
1. Начните с замыканий в JS
JS
Закрытие по существу происходит из двух моментов: лексической области видимости и передачи текущего значения функции.
Формирование замыкания очень простое, после завершения функции функция возврата или функция сохраняется, т.е. замыкание формируется.
о词法作用域
Для связанных точек знаний, вы можете обратиться к《你不知道的JavaScript》
Выяснить.
React Hooks
закрытия в и мы вJS
Замыкания, наблюдаемые в .
определить фабричную функциюcreateIncrement(i)
, возвращаетincrement
функция. каждый звонокincrement
функция, значение внутреннего счетчика увеличиваетсяi
.
function createIncrement(i) {
let value = 0
function increment() {
value += i
console.log(value)
}
return increment
}
const inc = createIncrement(10)
inc() // 10
inc() // 20
createIncrement(10)
Возвращает функцию приращения, которая присваиваетinc
Переменная. при звонкеinc()
час,value
Добавьте 10 к переменной.
первый звонокinc()
Возвращает 10, второй вызов возвращает 20 и так далее.
передачаinc()
без параметров,JS
все еще может получить токvalue
а такжеi
ПринципcreateIncrement()
середина. Замыкания происходят, когда функция возвращается к функции. Замыкания захватывают переменные в лексической области видимостиvalue
а такжеi
.
词法作用域是定义闭包的外部作用域
. В этом примереincrement()
Лексический объем словаcreateIncrement()
область , которая содержит переменнуюvalue
а такжеi
.
звони куда угодноinc()
, даже вcreateIncrement()
вне его области, он может получить доступvalue
а такжеi
.
Закрытый мешок - это тот, который вы можете词法作用域
Функции, которые запоминают и изменяют переменные, независимо от области выполнения.
2. Закрытие в реактивных крючках
Благодаря повторному использованию и побочным эффектам уменьшено управление состоянием,Hooks
Заменяет компоненты на основе классов. Кроме того, мы можем извлекать повторяющуюся логику в пользовательскиеHook
для повторного использования между приложениями.Hooks
сильно зависит отJS
Закрытие, но закрытие иногда может быть сложным.
Когда мы используем метод с несколькими побочными эффектами и управлением состояниемReact
Одна из проблем, с которой вы можете столкнуться при использовании компонентов, — это устаревшие замыкания, которые бывает трудно исправить.
3. Устаревшие затворы
заводская функцияcreateIncrement(i)
вернутьincrement
функция.increment
Функция параvalue
Увеличиватьi
, и возвращает текущую записьvalue
Функция
function createIncrement(i) {
let value = 0
function increment() {
value += i
console.log(value)
const message = `Current value is ${value}`
return function logValue() { // setState相当于logValue函数
console.log(message)
}
}
return increment
}
const inc = createIncrement(10)
const log = inc() // 10,将当前的value值固定
inc() // 20
inc() // 30
log() // "Current value is 10" 未能正确打印30
Также упомянуть здесьuseRef
, почему, когда вы используетеlet
объявленuseRef
не сталкивается с этой проблемой при использованииlet
Когда заявленная переменная, она встретится с этой проблемой, потому чтоuseRef
На самом деле это не переменная базового типа, а объект, каждый раз при изменении значенияНа самом деле вы изменяете значение объекта, а на объект ссылаются как на указатель., поэтому независимо от того, где берется значение, можно получить самое последнее значение!
function createIncrement(i) {
let value = 0
function increment() {
value += i
console.log(value)
const message = `Current value is ${value}`
return function logValue() { // setState相当于logValue函数
console.log(message)
}
}
return increment
}
const inc = createIncrement(1) // i被固定为1,输入几就被固定为几
inc() // 1
const log = inc() // 2
inc() // 3
log() // "Current value is 2" 未能正确打印3
Устаревшее закрытие захватывает переменные с устаревшими значениями.
В-четвертых, исправить проблему устаревших замыканий
(1) Используйте новое закрытие
Первый способ справиться с устаревшими замыканиями — найти замыкание, которое захватывает последнюю переменную.
найти последниеmessage
Закрытие переменной, то есть с последнего вызоваinc()
Возвращенное закрытие.
const inc = createIncrement(1)
inc() // 1
inc() // 2
const latestLog = inc()
latestLog() // "Current value is 3"
ВышеупомянутоеReact Hook
Способы обработки закрытия свежести.
Hooks
Hook
Последнее закрытие обратного вызова предоставлено (например,useEffect(callback))
Последняя переменная была получена из области действия функции компонента. То есть вuseEffect
второй параметр[]
Добавьте значение, которое прослушивает изменения, и каждый раз, когда оно изменяется, выполняйтеfunction
, чтобы получить последнее закрытие.
Переменная (2) закрыта изменена
Второй способ — позволитьlogValue()
Использовать напрямуюvalue
.
давайте переместим линиюconst message = ...;
прибытьlogValue()
В теле функции:
function createIncrementFixed(i) {
let value = 0;
function increment() {
value += i;
console.log(value);
return function logValue() {
const message = `Current value is ${value}`;
console.log(message);
};
}
return increment;
}
const inc = createIncrementFixed(1);
const log = inc(); // 打印 1
inc(); // 打印 2
inc(); // 打印 3
// 正常工作
log(); // 打印 "Current value is 3"
logValue()
закрытиеcreateIncrementFixed()
в рамкахvalue
Переменная.log()
Теперь печатается правильное сообщение.
5. Устаревшие замыкания в хуке
useEffect()
В использованииuseEffect Hook
Распространенная ситуация, когда происходят замыкания.
в компонентеWatchCount
середина,useEffect
отпечатков в секундуcount
ценность .
function WatchCount() {
const [count, setCount] = useState(0)
useEffect(function() {
setInterval(function log() {
console.log(`Count is: ${count}`)
}, 2000)
}, [])
return (
<div>
{count}
<button onClick={() => setCount(count + 1)}> 加1 </button>
</div>
)
}
Нажимаем кнопку плюс 1 несколько раз, видим из консоли, что каждые 2 секунды печатает какCount is: 0
На первом рендереlog()
захват среднего закрытияcount
значение переменной0
. Позже, даже еслиcount
Увеличивать,log()
по-прежнему является инициализированным значением, используемым в0
.log()
Обходной путь: сообщите useEffect(), что закрытие в log() зависит от количества:
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
const id = setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
return function() {
clearInterval(id);
}
}, [count]); // 看这里,这行是重点,count变化后重新渲染useEffect
return (
<div>
{count}
<button onClick={() => setCount(count + 1) }>
Increase
</button>
</div>
);
}
После установки зависимостей один разcount
Изменять,useEffect()
Просто обновите закрытие.
должным образом управляемыйHook
Зависимости — это ключ к решению проблемы устаревших замыканий. Рекомендуемая установкаeslint-plugin-react-hooksЭто может помочь обнаружить забытую нашу зависимость.
useState()
компонентыDelayedCount
имеет 2 кнопки
- Нажмите кнопку "
Increase async
Увеличивать счетчик с задержкой в 1 секунду в асинхронном режиме - В режиме синхронизации нажмите кнопку "
Increase sync
” немедленно увеличит счетчик
function DelayedCount() {
const [count, setCount] = useState(0);
function handleClickAsync() {
setTimeout(function delay() {
setCount(count + 1);
}, 1000);
}
function handleClickSync() {
setCount(count + 1);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
<button onClick={handleClickSync}>Increase sync</button>
</div>
)
}
Нажмите на "Increase async
", а затем нажмите сразу"Increase sync
" кнопка,count
обновить только до 1
.
Это потому чтоdelay()
является устаревшим закрытием.
Давайте посмотрим, что происходит в этом процессе:
Первоначальный рендеринг:count
значение0
.
нажмите 'Increase async
' кнопка.delay()
захват закрытияcount
значение0
.setTimeout()
Звонок через 1 секундуdelay()
.
Нажмите "Increase async
" кнопка.handleClickSync()
передачаsetCount(0 + 1)
Будуcount
устанавливается в 1, и компонент повторно визуализируется.
Через 1 секундуsetTimeout()
воплощать в жизньdelay()
функция. ноdelay()
Сохранение среднего закрытия count
начальное отображаемое значение 0, поэтому вызовsetState(0 + 1)
,результатcount
остаться на 1.
delay()
это устаревшее замыкание, которое использует устаревшее замыкание, захваченное во время первоначального рендерингаcount
Переменная.
Чтобы решить эту проблему, метод функции может быть использован для обновленияcount
условие:
function DelayedCount() {
const [count, setCount] = useState(0);
function handleClickAsync() {
setTimeout(function delay() {
setCount(count => count + 1); // 这行是重点
}, 1000);
}
function handleClickSync() {
setCount(count + 1);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
<button onClick={handleClickSync}>Increase sync</button>
</div>
);
}
СейчасsetCount(count => count + 1)
обновленdelay()
середина count
условие.React
Обязательно укажите последнее значение состояния в качестве аргумента функции обновления состояния, и проблема устаревших закрытий будет решена.
useLayoutEffect()
useLayoutEffect
можно рассматривать какuseEffect
синхронная версия.
useLayoutEffect
Его сигнатура функции такая же, какuseEffect
то же самое, но это будет работать во всехDOM
Синхронный вызов после измененияeffect
. можно использовать для чтенияDOM
Разметка и запуск повторного рендеринга синхронно. Прежде чем браузер выполнит рисование,useLayoutEffect
Внутреннее расписание обновлений будет обновляться синхронно.
Поэтому используйте стандартныйuseEffect
чтобы не блокировать визуальные обновления.потому чтоuseLayoutEffect
является синхронным, если бы мыuseLayoutEffect
Вызов обновлений состояния или выполнение некоторых очень трудоемких вычислений может привести кReact
Время работы слишком велико, что блокирует рендеринг браузера, что приводит к некоторым проблемам с заиканием..
Если вы перемещаете код изclass
Компоненты перенесены для использованияHook
функциональные компоненты, вы должны обратить внимание наuseLayoutEffect
а такжеcomponentDidMount
,componentDidUpdate
Этап вызова тот же. Тем не менее, мы рекомендуем начать сuseEffect
и попробуйте только использовать его, если это не удаетсяuseLayoutEffect
.
Если вы используете рендеринг на стороне сервера, имейте в виду, что независимо отuseLayoutEffect
ещеuseEffect
не может быть вJavascript
Выполняется до завершения загрузки кода. Вот почему он был представлен в компонентах, отображаемых на сервере.useLayoutEffect
сработает, когда код React
Тревога. Чтобы решить эту проблему, логика кода должна быть перемещенаuseEffect
in (если эта логика не нужна для первого рендеринга), или задержать отображение компонента до завершения рендеринга на стороне клиента (еслиuseLayoutEffect
перед казньюHTML
отображаются в беспорядке).
Оказать услугу с концаHTML
Демонстрацияeffect
компонентов можно получить, используяshowChild && <Child />
сделать условный рендеринг и использоватьuseEffect(() => { setShowChild(true); }, [])
Задержка представления компонентов. Таким образом, до завершения клиентского рендерингаUI
Он не будет выглядеть таким запутанным, как раньше.
Суммировать
Замыкание — это функция, которая захватывает переменную из того места, где она определена (или из ее лексической области видимости).
когда замыкание захватывает过时的变量
, возникает проблема устаревших замыканий.
Эффективный способ решения замыканий
- установить правильно
React Hook
зависимости - Для устаревших состояний используйте функцию для обновления состояния